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 sgx_creds: {
92 route_value: {
93 username: "test_sgx_user",
94 password: "test_sgx_password",
95 account: "test_sgx_account"
96 }
97 },
98 notify_from: "notify_from@example.org",
99 activation_amount: 1,
100 activation_amount_accept: 1,
101 plans: [
102 {
103 name: "test_usd",
104 currency: :USD,
105 monthly_price: 10000,
106 messages: :unlimited,
107 minutes: { included: 10440, price: 87 },
108 allow_register: true
109 },
110 {
111 name: "USD",
112 currency: :USD,
113 monthly_price: 10000,
114 messages: :unlimited,
115 minutes: { included: 10440, price: 87 },
116 allow_register: true
117 },
118 {
119 name: "test_bad_currency",
120 currency: :BAD
121 },
122 {
123 name: "test_usd_no_register",
124 currency: :USD,
125 monthly_price: 10000,
126 messages: :unlimited,
127 minutes: { included: 10440, price: 87 },
128 allow_register: false
129 },
130 {
131 name: "test_cad",
132 currency: :CAD,
133 monthly_price: 10000
134 }
135 ],
136 braintree: {
137 merchant_accounts: {
138 USD: "merchant_usd"
139 }
140 },
141 sip: {
142 realm: "sip.example.com",
143 app: "sipappid"
144 },
145 xep0157: [
146 { var: "support-addresses", value: "xmpp:tel@cheogram.com" }
147 ],
148 credit_card_url: ->(*) { "http://creditcard.example.com?" },
149 electrum_notify_url: ->(*) { "http://notify.example.com" },
150 sims: {
151 sim: {
152 USD: { price: 500, plan: "1GB" },
153 CAD: { price: 600, plan: "1GB" }
154 },
155 esim: {
156 USD: { price: 300, plan: "500MB" },
157 CAD: { price: 400, plan: "500MB" }
158 }
159 },
160 keep_area_codes: [
161 { area_code: "556", premium_price: 10 },
162 { area_code: "557", premium_price: nil }
163 ],
164 keep_area_codes_in: {
165 account: "moveto",
166 site_id: "movetosite",
167 sip_peer_id: "movetopeer"
168 },
169 upstream_domain: "example.net",
170 approved_domains: {
171 "approved.example.com": nil,
172 "refer.example.com": "refer_to"
173 },
174 parented_domains: {
175 "parented.example.com" => {
176 customer_id: "1234",
177 plan_name: "test_usd"
178 }
179 },
180 offer_codes: {
181 pplus: "xmpp:pplus"
182 },
183 bandwidth_site: "test_site",
184 bandwidth_peer: "test_peer",
185 keepgo: { api_key: "keepgokey", access_token: "keepgotoken" },
186 snikket_hosting_api: "snikket.example.com",
187 onboarding_domain: "onboarding.example.com",
188 adr: "A Mailing Address",
189 interac: "interac@example.com",
190 support_link: ->(*) { "https://support.com" },
191 bulk_order_tokens: {
192 sometoken: { customer_id: "bulkcustomer", peer_id: "bulkpeer" },
193 lowtoken: { customer_id: "customerid_low", peer_id: "lowpeer" }
194 },
195 public_onboarding_url: "xmpp:example.com?register"
196}.freeze
197
198def panic(e)
199 raise e
200end
201
202LOG = Class.new {
203 def child(*)
204 Minitest::Mock.new
205 end
206
207 def debug(*); end
208
209 def info(*); end
210
211 def error(*); end
212}.new.freeze
213
214def log
215 LOG
216end
217
218BLATHER = Class.new {
219 def <<(*); end
220}.new.freeze
221
222def execute_command(
223 iq=Blather::Stanza::Iq::Command.new.tap { |i| i.from = "test@example.com" },
224 blather: BLATHER,
225 &blk
226)
227 Command::Execution.new(
228 Minitest::Mock.new,
229 blather,
230 :to_s.to_proc,
231 iq
232 ).execute(&blk).sync
233end
234
235class NotLoaded < BasicObject
236 def inspect
237 "<NotLoaded #{@name}>"
238 end
239end
240
241class Matching
242 def initialize(&block)
243 @block = block
244 end
245
246 def ===(other)
247 @block.call(other)
248 end
249end
250
251class PromiseMock < Minitest::Mock
252 def then(succ=nil, _=nil)
253 if succ
254 succ.call(self)
255 else
256 yield self
257 end
258 end
259
260 def is_a?(_klass)
261 false
262 end
263end
264
265class FakeTelSelections
266 def initialize
267 @selections = {}
268 end
269
270 def set(jid, tel)
271 @selections[jid] = EMPromise.resolve(
272 TelSelections::HaveTel.new(tel.pending_value)
273 )
274 end
275
276 def delete(jid)
277 @selections.delete(jid)
278 EMPromise.resolve("OK")
279 end
280
281 def [](customer)
282 @selections.fetch(customer.jid) do
283 TelSelections::ChooseTel.new(
284 customer,
285 redis: FakeRedis.new,
286 db: FakeDB.new,
287 memcache: FakeMemcache.new
288 )
289 end
290 end
291end
292
293class FakeRedis
294 def initialize(values={})
295 @values = values
296 end
297
298 def set(key, value)
299 @values[key] = value
300 EMPromise.resolve("OK")
301 end
302
303 def setex(key, _expiry, value)
304 set(key, value)
305 end
306
307 def del(key)
308 @values.delete(key)
309 EMPromise.resolve("OK")
310 end
311
312 def mget(*keys)
313 EMPromise.all(keys.map(&method(:get)))
314 end
315
316 def get(key)
317 EMPromise.resolve(@values[key])
318 end
319
320 def getbit(key, bit)
321 get(key).then { |v| v.to_i.to_s(2)[bit].to_i }
322 end
323
324 def bitfield(key, *ops)
325 get(key).then do |v|
326 bits = v.to_i.to_s(2)
327 ops.each_slice(3).map do |(op, encoding, offset)|
328 raise "unsupported bitfield op" unless op == "GET"
329 raise "unsupported bitfield op" unless encoding == "u1"
330
331 bits[offset].to_i
332 end
333 end
334 end
335
336 def hget(key, field)
337 EMPromise.resolve(@values.dig(key, field))
338 end
339
340 def hexists(key, field)
341 hget(key, field).nil? ? 0 : 1
342 end
343
344 def hincrby(key, field, incrby)
345 @values[key] ||= {}
346 @values[key][field] ||= 0
347 @values[key][field] += incrby
348 end
349
350 def sadd(key, member)
351 @values[key] ||= Set.new
352 @values[key] << member
353 end
354
355 def srem(key, member)
356 @values[key]&.delete(member)
357 end
358
359 def scard(key)
360 @values[key]&.size || 0
361 end
362
363 def smembers(key)
364 @values[key]&.to_a || []
365 end
366
367 def sismember(key, value)
368 smembers(key).include?(value)
369 end
370
371 def expire(_, _); end
372
373 def exists(*keys)
374 EMPromise.resolve(
375 @values.select { |k, _| keys.include? k }.size
376 )
377 end
378
379 def lindex(key, index)
380 get(key).then { |v| v&.fetch(index) }
381 end
382
383 def incr(key)
384 get(key).then { |v|
385 n = v ? v + 1 : 0
386 set(key, n).then { n }
387 }
388 end
389
390 def reset!
391 @values = {}
392 end
393end
394
395class FakeDB
396 class MultiResult
397 def initialize(*args)
398 @results = args
399 end
400
401 def to_a
402 @results.shift
403 end
404 end
405
406 def initialize(items={})
407 @items = items
408 end
409
410 def transaction
411 yield
412 end
413
414 def exec(_, args)
415 @items.fetch(args, []).to_a
416 end
417
418 def query_defer(sql, args)
419 EMPromise.resolve(exec(sql, args))
420 end
421
422 def query_one(_, *args, field_names_as: :symbol, default: nil)
423 row = @items.fetch(args, []).to_a.first
424 row = row.transform_keys(&:to_sym) if row && field_names_as == :symbol
425 EMPromise.resolve(row || default)
426 end
427
428 def exec_defer(_, _)
429 EMPromise.resolve(nil)
430 end
431end
432
433class FakeMemcache
434 def initialize(data={})
435 @data = data
436 end
437
438 def set(k, v, _expires=nil)
439 raise "No spaces" if k =~ /\s/
440
441 @data[k] = v
442 end
443
444 def get(k)
445 yield @data[k]
446 end
447end
448
449class FakeLog
450 def initialize
451 @logs = []
452 end
453
454 def respond_to_missing?(*)
455 true
456 end
457
458 def method_missing(*args)
459 @logs << args
460 end
461end
462
463class FakeIBRRepo
464 def initialize(registrations={})
465 @registrations = registrations
466 end
467
468 def registered?(jid, from:)
469 @registrations.dig(jid.to_s, from.to_s) || false
470 end
471end
472
473module EventMachine
474 class << self
475 # Patch EM.add_timer to be instant in tests
476 alias old_add_timer add_timer
477 def add_timer(*args, &block)
478 args[0] = 0
479 old_add_timer(*args, &block)
480 end
481 end
482end
483
484module Minitest
485 class Test
486 def self.property(m, &block)
487 define_method("test_#{m}") do
488 property_of(&block).check { |args| send(m, *args) }
489 end
490 end
491
492 def self.em(m)
493 alias_method "raw_#{m}", m
494 define_method(m) do
495 EM.run do
496 Fiber.new {
497 begin
498 send("raw_#{m}")
499 ensure
500 EM.stop
501 end
502 }.resume
503 end
504 end
505 end
506 end
507end
508
509require "lazy_object"
510require "porting_step_repo"
511
512class MockOutputs < PortingStepRepo::Outputs
513 def initialize(mock)
514 @mock = mock
515 end
516
517 def info(id, key, msg)
518 @mock.info(id, key, msg)
519 end
520
521 def warn(id, key, msg)
522 @mock.warn(id, key, msg)
523 end
524
525 def error(id, key, e_or_msg)
526 @mock.error(id, key, e_or_msg)
527 end
528
529 def to_customer(id, key, tel, msg)
530 @mock.to_customer(id, key, tel, msg)
531 end
532
533 def verify
534 @mock.verify
535 end
536end