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
10end
11
12require "em_promise"
13require "fiber"
14require "minitest/autorun"
15require "rantly/minitest_extensions"
16require "sentry-ruby"
17require "webmock/minitest"
18begin
19 require "pry-rescue/minitest"
20 require "pry-reload"
21
22 module Minitest
23 class Test
24 alias old_capture_exceptions capture_exceptions
25 def capture_exceptions
26 old_capture_exceptions do
27 yield
28 rescue Minitest::Skip => e
29 failures << e
30 end
31 end
32 end
33 end
34rescue LoadError
35 # Just helpers for dev, no big deal if missing
36 nil
37end
38
39require "backend_sgx"
40require "tel_selections"
41
42$VERBOSE = nil
43Sentry.init
44
45def customer(
46 customer_id="test",
47 plan_name: nil,
48 jid: Blather::JID.new("#{customer_id}@example.net"),
49 expires_at: Time.now,
50 auto_top_up_amount: 0,
51 **kwargs
52)
53 Customer.extract(
54 customer_id,
55 jid,
56 plan_name: plan_name,
57 expires_at: expires_at,
58 auto_top_up_amount: auto_top_up_amount,
59 **kwargs
60 )
61end
62
63CONFIG = {
64 sgx: "sgx",
65 component: {
66 jid: "component"
67 },
68 creds: {
69 account: "test_bw_account",
70 username: "test_bw_user",
71 password: "test_bw_password"
72 },
73 notify_from: "notify_from@example.org",
74 activation_amount: 1,
75 activation_amount_accept: 1,
76 plans: [
77 {
78 name: "test_usd",
79 currency: :USD,
80 monthly_price: 10000,
81 messages: :unlimited,
82 minutes: { included: 10440, price: 87 },
83 allow_register: true
84 },
85 {
86 name: "test_bad_currency",
87 currency: :BAD
88 },
89 {
90 name: "test_usd_no_register",
91 currency: :USD,
92 monthly_price: 10000,
93 messages: :unlimited,
94 minutes: { included: 10440, price: 87 },
95 allow_register: false
96 },
97 {
98 name: "test_cad",
99 currency: :CAD,
100 monthly_price: 10000
101 }
102 ],
103 braintree: {
104 merchant_accounts: {
105 USD: "merchant_usd"
106 }
107 },
108 sip: {
109 realm: "sip.example.com",
110 app: "sipappid"
111 },
112 xep0157: [
113 { var: "support-addresses", value: "xmpp:tel@cheogram.com" }
114 ],
115 credit_card_url: ->(*) { "http://creditcard.example.com?" },
116 electrum_notify_url: ->(*) { "http://notify.example.com" },
117 keep_area_codes: ["556"],
118 keep_area_codes_in: {
119 account: "moveto",
120 site_id: "movetosite",
121 sip_peer_id: "movetopeer"
122 },
123 upstream_domain: "example.net",
124 approved_domains: {
125 "approved.example.com": nil,
126 "refer.example.com": "refer_to"
127 },
128 parented_domains: {
129 "parented.example.com" => {
130 customer_id: "1234",
131 plan_name: "test_usd"
132 }
133 },
134 bandwidth_site: "test_site",
135 bandwidth_peer: "test_peer",
136 keepgo: { api_key: "keepgokey", access_token: "keepgotoken" },
137 snikket_hosting_api: "snikket.example.com",
138 onboarding_domain: "onboarding.example.com",
139 adr: "A Mailing Address",
140 interac: "interac@example.com",
141 support_link: ->(*) { "https://support.com" }
142}.freeze
143
144def panic(e)
145 raise e
146end
147
148LOG = Class.new {
149 def child(*)
150 Minitest::Mock.new
151 end
152
153 def debug(*); end
154
155 def info(*); end
156
157 def error(*); end
158}.new.freeze
159
160def log
161 LOG
162end
163
164BLATHER = Class.new {
165 def <<(*); end
166}.new.freeze
167
168def execute_command(
169 iq=Blather::Stanza::Iq::Command.new.tap { |i| i.from = "test@example.com" },
170 blather: BLATHER,
171 &blk
172)
173 Command::Execution.new(
174 Minitest::Mock.new,
175 blather,
176 :to_s.to_proc,
177 iq
178 ).execute(&blk).sync
179end
180
181class NotLoaded < BasicObject
182 def inspect
183 "<NotLoaded #{@name}>"
184 end
185end
186
187class Matching
188 def initialize(&block)
189 @block = block
190 end
191
192 def ===(other)
193 @block.call(other)
194 end
195end
196
197class PromiseMock < Minitest::Mock
198 def then(succ=nil, _=nil)
199 if succ
200 succ.call(self)
201 else
202 yield self
203 end
204 end
205
206 def is_a?(_klass)
207 false
208 end
209end
210
211class FakeTelSelections
212 def initialize
213 @selections = {}
214 end
215
216 def set(jid, tel)
217 @selections[jid] = EMPromise.resolve(
218 TelSelections::HaveTel.new(tel.pending_value)
219 )
220 end
221
222 def delete(jid)
223 @selections.delete(jid)
224 EMPromise.resolve("OK")
225 end
226
227 def [](jid)
228 @selections.fetch(jid) do
229 TelSelections::ChooseTel.new(db: FakeDB.new, memcache: FakeMemcache.new)
230 end
231 end
232end
233
234class FakeRedis
235 def initialize(values={})
236 @values = values
237 end
238
239 def set(key, value)
240 @values[key] = value
241 EMPromise.resolve("OK")
242 end
243
244 def setex(key, _expiry, value)
245 set(key, value)
246 end
247
248 def del(key)
249 @values.delete(key)
250 EMPromise.resolve("OK")
251 end
252
253 def mget(*keys)
254 EMPromise.all(keys.map(&method(:get)))
255 end
256
257 def get(key)
258 EMPromise.resolve(@values[key])
259 end
260
261 def getbit(key, bit)
262 get(key).then { |v| v.to_i.to_s(2)[bit].to_i }
263 end
264
265 def bitfield(key, *ops)
266 get(key).then do |v|
267 bits = v.to_i.to_s(2)
268 ops.each_slice(3).map do |(op, encoding, offset)|
269 raise "unsupported bitfield op" unless op == "GET"
270 raise "unsupported bitfield op" unless encoding == "u1"
271
272 bits[offset].to_i
273 end
274 end
275 end
276
277 def hget(key, field)
278 @values.dig(key, field)
279 end
280
281 def hexists(key, field)
282 hget(key, field).nil? ? 0 : 1
283 end
284
285 def hincrby(key, field, incrby)
286 @values[key] ||= {}
287 @values[key][field] ||= 0
288 @values[key][field] += incrby
289 end
290
291 def sadd(key, member)
292 @values[key] ||= Set.new
293 @values[key] << member
294 end
295
296 def srem(key, member)
297 @values[key]&.delete(member)
298 end
299
300 def scard(key)
301 @values[key]&.size || 0
302 end
303
304 def smembers(key)
305 @values[key]&.to_a || []
306 end
307
308 def sismember(key, value)
309 smembers(key).include?(value)
310 end
311
312 def expire(_, _); end
313
314 def exists(*keys)
315 EMPromise.resolve(
316 @values.select { |k, _| keys.include? k }.size
317 )
318 end
319
320 def lindex(key, index)
321 get(key).then { |v| v&.fetch(index) }
322 end
323
324 def incr(key)
325 get(key).then { |v|
326 n = v ? v + 1 : 0
327 set(key, n).then { n }
328 }
329 end
330end
331
332class FakeDB
333 class MultiResult
334 def initialize(*args)
335 @results = args
336 end
337
338 def to_a
339 @results.shift
340 end
341 end
342
343 def initialize(items={})
344 @items = items
345 end
346
347 def query_defer(_, args)
348 EMPromise.resolve(@items.fetch(args, []).to_a)
349 end
350
351 def query_one(_, *args, field_names_as: :symbol, default: nil)
352 row = @items.fetch(args, []).to_a.first
353 row = row.transform_keys(&:to_sym) if row && field_names_as == :symbol
354 EMPromise.resolve(row || default)
355 end
356
357 def exec_defer(_, _)
358 EMPromise.resolve(nil)
359 end
360end
361
362class FakeMemcache
363 def initialize(data={})
364 @data = data
365 end
366
367 def set(k, v, _expires=nil)
368 raise "No spaces" if k =~ /\s/
369
370 @data[k] = v
371 end
372
373 def get(k)
374 yield @data[k]
375 end
376end
377
378class FakeLog
379 def initialize
380 @logs = []
381 end
382
383 def respond_to_missing?(*)
384 true
385 end
386
387 def method_missing(*args)
388 @logs << args
389 end
390end
391
392class FakeIBRRepo
393 def initialize(registrations={})
394 @registrations = registrations
395 end
396
397 def registered?(jid, from:)
398 @registrations.dig(jid.to_s, from.to_s) || false
399 end
400end
401
402module EventMachine
403 class << self
404 # Patch EM.add_timer to be instant in tests
405 alias old_add_timer add_timer
406 def add_timer(*args, &block)
407 args[0] = 0
408 old_add_timer(*args, &block)
409 end
410 end
411end
412
413module Minitest
414 class Test
415 def self.property(m, &block)
416 define_method("test_#{m}") do
417 property_of(&block).check { |args| send(m, *args) }
418 end
419 end
420
421 def self.em(m)
422 alias_method "raw_#{m}", m
423 define_method(m) do
424 EM.run do
425 Fiber.new {
426 begin
427 send("raw_#{m}")
428 ensure
429 EM.stop
430 end
431 }.resume
432 end
433 end
434 end
435 end
436end