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