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