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