1# frozen_string_literal: true
2
3begin
4 require "simplecov"
5 SimpleCov.start do
6 add_filter "/test/"
7 enable_coverage :branch
8 end
9rescue LoadError
10 nil
11end
12
13require "em_promise"
14require "fiber"
15require "minitest/autorun"
16require "rantly/minitest_extensions"
17require "sentry-ruby"
18require "webmock/minitest"
19begin
20 require "pry-rescue/minitest"
21 require "pry-reload"
22
23 module Minitest
24 class Test
25 alias old_capture_exceptions capture_exceptions
26 def capture_exceptions
27 old_capture_exceptions do
28 yield
29 rescue Minitest::Skip => e
30 failures << e
31 end
32 end
33 end
34 end
35rescue LoadError
36 # Just helpers for dev, no big deal if missing
37 nil
38end
39
40require "backend_sgx"
41require "tel_selections"
42
43$VERBOSE = nil
44Sentry.init
45
46def mksgx(customer_id="bogus", **kwargs)
47 kwargs.delete(:sgx) || BackendSgx.new(
48 jid: Blather::JID.new(CONFIG[:sgx]),
49 creds: CONFIG[:creds],
50 from_jid: ProxiedJID.proxy(
51 "customer_#{customer_id}",
52 CONFIG[:component][:jid]
53 ).__getobj__,
54 ogm_url: NotLoaded.new("ogm_url"),
55 fwd: NotLoaded.new("fwd"),
56 transcription_enabled: NotLoaded.new("transcription_enabled"),
57 registered?: NotLoaded.new("registered?"),
58 **kwargs
59 )
60end
61
62def customer(
63 customer_id="test",
64 plan_name: nil,
65 jid: Blather::JID.new("#{customer_id}@example.net"),
66 expires_at: Time.now,
67 auto_top_up_amount: 0,
68 **kwargs
69)
70 Customer.extract(
71 customer_id,
72 jid,
73 sgx: kwargs.delete(:sgx) || mksgx(customer_id),
74 plan_name: plan_name,
75 expires_at: expires_at,
76 auto_top_up_amount: auto_top_up_amount,
77 **kwargs
78 )
79end
80
81CONFIG = {
82 sgx: "sgx",
83 component: {
84 jid: "component"
85 },
86 creds: {
87 account: "test_bw_account",
88 username: "test_bw_user",
89 password: "test_bw_password"
90 },
91 notify_from: "notify_from@example.org",
92 activation_amount: 1,
93 activation_amount_accept: 1,
94 plans: [
95 {
96 name: "test_usd",
97 currency: :USD,
98 monthly_price: 10000,
99 messages: :unlimited,
100 minutes: { included: 10440, price: 87 },
101 allow_register: true
102 },
103 {
104 name: "test_bad_currency",
105 currency: :BAD
106 },
107 {
108 name: "test_usd_no_register",
109 currency: :USD,
110 monthly_price: 10000,
111 messages: :unlimited,
112 minutes: { included: 10440, price: 87 },
113 allow_register: false
114 },
115 {
116 name: "test_cad",
117 currency: :CAD,
118 monthly_price: 10000
119 }
120 ],
121 braintree: {
122 merchant_accounts: {
123 USD: "merchant_usd"
124 }
125 },
126 sip: {
127 realm: "sip.example.com",
128 app: "sipappid"
129 },
130 xep0157: [
131 { var: "support-addresses", value: "xmpp:tel@cheogram.com" }
132 ],
133 credit_card_url: ->(*) { "http://creditcard.example.com?" },
134 electrum_notify_url: ->(*) { "http://notify.example.com" },
135 keep_area_codes: ["556"],
136 keep_area_codes_in: {
137 account: "moveto",
138 site_id: "movetosite",
139 sip_peer_id: "movetopeer"
140 },
141 upstream_domain: "example.net",
142 approved_domains: {
143 "approved.example.com": nil,
144 "refer.example.com": "refer_to"
145 },
146 parented_domains: {
147 "parented.example.com" => {
148 customer_id: "1234",
149 plan_name: "test_usd"
150 }
151 },
152 bandwidth_site: "test_site",
153 bandwidth_peer: "test_peer",
154 keepgo: { api_key: "keepgokey", access_token: "keepgotoken" },
155 snikket_hosting_api: "snikket.example.com",
156 onboarding_domain: "onboarding.example.com",
157 adr: "A Mailing Address",
158 interac: "interac@example.com",
159 support_link: ->(*) { "https://support.com" }
160}.freeze
161
162def panic(e)
163 raise e
164end
165
166LOG = Class.new {
167 def child(*)
168 Minitest::Mock.new
169 end
170
171 def debug(*); end
172
173 def info(*); end
174
175 def error(*); end
176}.new.freeze
177
178def log
179 LOG
180end
181
182BLATHER = Class.new {
183 def <<(*); end
184}.new.freeze
185
186def execute_command(
187 iq=Blather::Stanza::Iq::Command.new.tap { |i| i.from = "test@example.com" },
188 blather: BLATHER,
189 &blk
190)
191 Command::Execution.new(
192 Minitest::Mock.new,
193 blather,
194 :to_s.to_proc,
195 iq
196 ).execute(&blk).sync
197end
198
199class NotLoaded < BasicObject
200 def inspect
201 "<NotLoaded #{@name}>"
202 end
203end
204
205class Matching
206 def initialize(&block)
207 @block = block
208 end
209
210 def ===(other)
211 @block.call(other)
212 end
213end
214
215class PromiseMock < Minitest::Mock
216 def then(succ=nil, _=nil)
217 if succ
218 succ.call(self)
219 else
220 yield self
221 end
222 end
223
224 def is_a?(_klass)
225 false
226 end
227end
228
229class FakeTelSelections
230 def initialize
231 @selections = {}
232 end
233
234 def set(jid, tel)
235 @selections[jid] = EMPromise.resolve(
236 TelSelections::HaveTel.new(tel.pending_value)
237 )
238 end
239
240 def delete(jid)
241 @selections.delete(jid)
242 EMPromise.resolve("OK")
243 end
244
245 def [](jid)
246 @selections.fetch(jid) do
247 TelSelections::ChooseTel.new(db: FakeDB.new, memcache: FakeMemcache.new)
248 end
249 end
250end
251
252class FakeRedis
253 def initialize(values={})
254 @values = values
255 end
256
257 def set(key, value)
258 @values[key] = value
259 EMPromise.resolve("OK")
260 end
261
262 def setex(key, _expiry, value)
263 set(key, value)
264 end
265
266 def del(key)
267 @values.delete(key)
268 EMPromise.resolve("OK")
269 end
270
271 def mget(*keys)
272 EMPromise.all(keys.map(&method(:get)))
273 end
274
275 def get(key)
276 EMPromise.resolve(@values[key])
277 end
278
279 def getbit(key, bit)
280 get(key).then { |v| v.to_i.to_s(2)[bit].to_i }
281 end
282
283 def bitfield(key, *ops)
284 get(key).then do |v|
285 bits = v.to_i.to_s(2)
286 ops.each_slice(3).map do |(op, encoding, offset)|
287 raise "unsupported bitfield op" unless op == "GET"
288 raise "unsupported bitfield op" unless encoding == "u1"
289
290 bits[offset].to_i
291 end
292 end
293 end
294
295 def hget(key, field)
296 @values.dig(key, field)
297 end
298
299 def hexists(key, field)
300 hget(key, field).nil? ? 0 : 1
301 end
302
303 def hincrby(key, field, incrby)
304 @values[key] ||= {}
305 @values[key][field] ||= 0
306 @values[key][field] += incrby
307 end
308
309 def sadd(key, member)
310 @values[key] ||= Set.new
311 @values[key] << member
312 end
313
314 def srem(key, member)
315 @values[key]&.delete(member)
316 end
317
318 def scard(key)
319 @values[key]&.size || 0
320 end
321
322 def smembers(key)
323 @values[key]&.to_a || []
324 end
325
326 def sismember(key, value)
327 smembers(key).include?(value)
328 end
329
330 def expire(_, _); end
331
332 def exists(*keys)
333 EMPromise.resolve(
334 @values.select { |k, _| keys.include? k }.size
335 )
336 end
337
338 def lindex(key, index)
339 get(key).then { |v| v&.fetch(index) }
340 end
341
342 def incr(key)
343 get(key).then { |v|
344 n = v ? v + 1 : 0
345 set(key, n).then { n }
346 }
347 end
348end
349
350class FakeDB
351 class MultiResult
352 def initialize(*args)
353 @results = args
354 end
355
356 def to_a
357 @results.shift
358 end
359 end
360
361 def initialize(items={})
362 @items = items
363 end
364
365 def query_defer(_, args)
366 EMPromise.resolve(@items.fetch(args, []).to_a)
367 end
368
369 def query_one(_, *args, field_names_as: :symbol, default: nil)
370 row = @items.fetch(args, []).to_a.first
371 row = row.transform_keys(&:to_sym) if row && field_names_as == :symbol
372 EMPromise.resolve(row || default)
373 end
374
375 def exec_defer(_, _)
376 EMPromise.resolve(nil)
377 end
378end
379
380class FakeMemcache
381 def initialize(data={})
382 @data = data
383 end
384
385 def set(k, v, _expires=nil)
386 raise "No spaces" if k =~ /\s/
387
388 @data[k] = v
389 end
390
391 def get(k)
392 yield @data[k]
393 end
394end
395
396class FakeLog
397 def initialize
398 @logs = []
399 end
400
401 def respond_to_missing?(*)
402 true
403 end
404
405 def method_missing(*args)
406 @logs << args
407 end
408end
409
410class FakeIBRRepo
411 def initialize(registrations={})
412 @registrations = registrations
413 end
414
415 def registered?(jid, from:)
416 @registrations.dig(jid.to_s, from.to_s) || false
417 end
418end
419
420module EventMachine
421 class << self
422 # Patch EM.add_timer to be instant in tests
423 alias old_add_timer add_timer
424 def add_timer(*args, &block)
425 args[0] = 0
426 old_add_timer(*args, &block)
427 end
428 end
429end
430
431module Minitest
432 class Test
433 def self.property(m, &block)
434 define_method("test_#{m}") do
435 property_of(&block).check { |args| send(m, *args) }
436 end
437 end
438
439 def self.em(m)
440 alias_method "raw_#{m}", m
441 define_method(m) do
442 EM.run do
443 Fiber.new {
444 begin
445 send("raw_#{m}")
446 ensure
447 EM.stop
448 end
449 }.resume
450 end
451 end
452 end
453 end
454end