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