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