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 mget(*keys)
246 EMPromise.all(keys.map(&method(:get)))
247 end
248
249 def get(key)
250 EMPromise.resolve(@values[key])
251 end
252
253 def getbit(key, bit)
254 get(key).then { |v| v.to_i.to_s(2)[bit].to_i }
255 end
256
257 def bitfield(key, *ops)
258 get(key).then do |v|
259 bits = v.to_i.to_s(2)
260 ops.each_slice(3).map do |(op, encoding, offset)|
261 raise "unsupported bitfield op" unless op == "GET"
262 raise "unsupported bitfield op" unless encoding == "u1"
263
264 bits[offset].to_i
265 end
266 end
267 end
268
269 def hget(key, field)
270 @values.dig(key, field)
271 end
272
273 def hexists(key, field)
274 hget(key, field).nil? ? 0 : 1
275 end
276
277 def hincrby(key, field, incrby)
278 @values[key] ||= {}
279 @values[key][field] ||= 0
280 @values[key][field] += incrby
281 end
282
283 def sadd(key, member)
284 @values[key] ||= Set.new
285 @values[key] << member
286 end
287
288 def srem(key, member)
289 @values[key].delete(member)
290 end
291
292 def scard(key)
293 @values[key]&.size || 0
294 end
295
296 def smembers(key)
297 @values[key]&.to_a || []
298 end
299
300 def sismember(key, value)
301 smembers(key).include?(value)
302 end
303
304 def expire(_, _); end
305
306 def exists(*keys)
307 EMPromise.resolve(
308 @values.select { |k, _| keys.include? k }.size
309 )
310 end
311
312 def lindex(key, index)
313 get(key).then { |v| v&.fetch(index) }
314 end
315
316 def incr(key)
317 get(key).then { |v|
318 n = v ? v + 1 : 0
319 set(key, n).then { n }
320 }
321 end
322end
323
324class FakeDB
325 class MultiResult
326 def initialize(*args)
327 @results = args
328 end
329
330 def to_a
331 @results.shift
332 end
333 end
334
335 def initialize(items={})
336 @items = items
337 end
338
339 def query_defer(_, args)
340 EMPromise.resolve(@items.fetch(args, []).to_a)
341 end
342
343 def query_one(_, *args, field_names_as: :symbol, default: nil)
344 row = @items.fetch(args, []).to_a.first
345 row = row.transform_keys(&:to_sym) if row && field_names_as == :symbol
346 EMPromise.resolve(row || default)
347 end
348
349 def exec_defer(_, _)
350 EMPromise.resolve(nil)
351 end
352end
353
354class FakeMemcache
355 def initialize(data={})
356 @data = data
357 end
358
359 def set(k, v, _expires=nil)
360 raise "No spaces" if k =~ /\s/
361
362 @data[k] = v
363 end
364
365 def get(k)
366 yield @data[k]
367 end
368end
369
370class FakeLog
371 def initialize
372 @logs = []
373 end
374
375 def respond_to_missing?(*)
376 true
377 end
378
379 def method_missing(*args)
380 @logs << args
381 end
382end
383
384class FakeIBRRepo
385 def initialize(registrations={})
386 @registrations = registrations
387 end
388
389 def registered?(jid, from:)
390 @registrations.dig(jid.to_s, from.to_s) || false
391 end
392end
393
394module EventMachine
395 class << self
396 # Patch EM.add_timer to be instant in tests
397 alias old_add_timer add_timer
398 def add_timer(*args, &block)
399 args[0] = 0
400 old_add_timer(*args, &block)
401 end
402 end
403end
404
405module Minitest
406 class Test
407 def self.property(m, &block)
408 define_method("test_#{m}") do
409 property_of(&block).check { |args| send(m, *args) }
410 end
411 end
412
413 def self.em(m)
414 alias_method "raw_#{m}", m
415 define_method(m) do
416 EM.run do
417 Fiber.new {
418 begin
419 send("raw_#{m}")
420 ensure
421 EM.stop
422 end
423 }.resume
424 end
425 end
426 end
427 end
428end