test_helper.rb

  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