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