test_helper.rb

  1# frozen_string_literal: true
  2
  3require "simplecov"
  4SimpleCov.start do
  5	add_filter "/test/"
  6	enable_coverage :branch
  7	command_name ENV.fetch("COVERAGE_NAME", "tests")
  8end
  9
 10require "minitest/autorun"
 11require "webmock/minitest"
 12require "fakefs/safe"
 13
 14MMS_PROXY = "https://proxy.test.example.com/"
 15BW_MESSAGES_URL = "https://messaging.bandwidth.com/api/v2/users/account/messages"
 16
 17_saved_argv = ARGV.dup
 18
 19ARGV[0] = "component"
 20ARGV[6] = MMS_PROXY
 21
 22require_relative "../sgx-bwmsgsv2"
 23ARGV.replace(_saved_argv)
 24
 25LOG.level = :fatal
 26
 27def SGXbwmsgsv2.write_to_stream(s)
 28	@written ||= []
 29	@written << s
 30end
 31
 32def reset_stanzas!
 33	SGXbwmsgsv2.instance_variable_set(:@written, [])
 34end
 35
 36def reset_redis!(tn: "+15550000000", jid: "test@example.com")
 37	REDIS.reset!
 38	REDIS.set("catapult_jid-", "HERE")
 39	REDIS.set("catapult_jid-#{tn}", jid)
 40	REDIS.set("catapult_cred-#{jid}", [
 41		'account', 'user', 'password', tn
 42	])
 43end
 44
 45def written
 46	SGXbwmsgsv2.instance_variable_get(:@written)
 47end
 48
 49def invoke_webhook(payload, extra_env: {})
 50	with_stubs([
 51		[
 52			SGXbwmsgsv2,
 53			:write,
 54			->(data) { SGXbwmsgsv2.write_to_stream(data) }
 55		 ]
 56	]) do
 57		handler = WebhookHandler.new
 58		env = {
 59			"REQUEST_URI" => "/",
 60			"REQUEST_METHOD" => "POST",
 61			"params" => { "_json" => [payload] }
 62		}.merge(extra_env)
 63		handler.instance_variable_set(:@env, env)
 64		def handler.params
 65			@env["params"]
 66		end
 67		EMPromise.resolve(nil).then {
 68			handler.response(env)
 69		}.sync
 70	end
 71end
 72
 73def process_stanza(s)
 74	SGXbwmsgsv2.send(:client).receive_data(s)
 75	raise $panic if $panic
 76end
 77
 78def xmpp_error_name(error)
 79	error.find_first(
 80		"child::*[name()!='text']",
 81		Blather::StanzaError::STANZA_ERR_NS
 82	).element_name
 83end
 84
 85def xmpp_error_text(error)
 86	error.find_first("ns:text", ns: Blather::StanzaError::STANZA_ERR_NS)&.text
 87end
 88
 89def with_mms_env(path: "/mms", url: "https://mms.test.example.com")
 90	old_path = ENV["MMS_PATH"]
 91	old_url = ENV["MMS_URL"]
 92	ENV["MMS_PATH"] = path
 93	ENV["MMS_URL"] = url
 94	yield
 95ensure
 96	ENV["MMS_PATH"] = old_path
 97	ENV["MMS_URL"] = old_url
 98end
 99
100
101begin
102	require "pry-byebug"
103
104	module Minitest
105		class Test
106			alias old_capture_exceptions capture_exceptions
107			def capture_exceptions
108				old_capture_exceptions do
109					yield
110				rescue Minitest::Skip => e
111					failures << e
112				end
113			end
114		end
115	end
116rescue LoadError, NameError
117	# Just helpers for dev, no big deal if missing
118	nil
119end
120
121# @param body [String]
122# @param dests [Array<String>]
123# @param from [String]
124# @return [Blather::Stanza::Message]
125def build_group_mms(body, dests, from: "test@example.com", id: nil)
126	m = Blather::Stanza::Message.new(ARGV[0], body)
127	m.from = from
128	m.id = id if id
129	addrs = Nokogiri::XML::Node.new('addresses', m.document)
130	addrs['xmlns'] = 'http://jabber.org/protocol/address'
131	dests.each do |dest|
132		addr = Nokogiri::XML::Node.new('address', m.document)
133		addr['type'] = 'to'
134		addr['uri'] = "sms:#{dest}"
135		addrs.add_child(addr)
136	end
137	m.add_child(addrs)
138	m
139end
140
141# @param body [String, nil]
142# @param dests [Array<String>]
143# @param from [String]
144# @param id [String, nil]
145# @return [Blather::Stanza::Message]
146def build_outbound_message(body, dests, from: "test@example.com", id: nil)
147	if dests.length == 1 || dests.is_a?(String)
148		dests = [dests] if dests.is_a?(String)
149
150		m = Blather::Stanza::Message.new("#{dests.first}@#{ARGV[0]}")
151		m.body = body if body
152		m.from = from
153		m.id = id if id
154		m
155	else
156		build_group_mms(body, dests, from: from, id: id)
157	end
158end
159
160
161$VERBOSE = nil
162
163class FakeRedis
164	def initialize(values={})
165		@values = values
166	end
167
168	def reset!(values={})
169		@values = values
170	end
171
172	def set(key, value, *)
173		@values[key] = value
174		EMPromise.resolve("OK")
175	end
176
177	def setex(key, _expiry, value)
178		set(key, value)
179	end
180
181	def del(*keys)
182		keys.each { |key| @values.delete(key) }
183	end
184
185	def mget(*keys)
186		EMPromise.all(keys.map(&method(:get)))
187	end
188
189	def get(key)
190		EMPromise.resolve(@values[key])
191	end
192
193	def getbit(key, bit)
194		get(key).then { |v| v.to_i.to_s(2)[bit].to_i }
195	end
196
197	def bitfield(key, *ops)
198		get(key).then do |v|
199			bits = v.to_i.to_s(2)
200			ops.each_slice(3).map do |(op, encoding, offset)|
201				raise "unsupported bitfield op" unless op == "GET"
202				raise "unsupported bitfield op" unless encoding == "u1"
203
204				bits[offset].to_i
205			end
206		end
207	end
208
209	def hget(key, field)
210		@values.dig(key, field)
211	end
212
213	def hincrby(key, field, incrby)
214		@values[key] ||= {}
215		@values[key][field] ||= 0
216		@values[key][field] += incrby
217	end
218
219	def sadd(key, member)
220		@values[key] ||= Set.new
221		@values[key] << member
222	end
223
224	def srem(key, member)
225		@values[key].delete(member)
226	end
227
228	def scard(key)
229		@values[key]&.size || 0
230	end
231
232	def expire(_, _); end
233
234	def exists(*keys)
235		EMPromise.resolve(
236			@values.select { |k, _| keys.include? k }.size.to_s
237		)
238	end
239
240	def lindex(key, index)
241		get(key).then { |v| v&.fetch(index) }
242	end
243
244	def lrange(key, sindex, eindex)
245		get(key).then { |v| v ? v[sindex..eindex] : [] }
246	end
247
248	def rpush(key, *values)
249		@values[key] ||= []
250		values.each { |v| @values[key].push(v) }
251	end
252
253	def multi
254	end
255
256	def exec
257	end
258
259	def discard
260	end
261
262	def xadd(stream, id, *args)
263		@values[stream] ||= []
264		entry_id = id == "*" ? "#{Time.now.to_i}-0" : id
265		fields = Hash[*args]
266		@values[stream] << { id: entry_id, fields: fields }
267		EMPromise.resolve(entry_id)
268	end
269
270	def stream_entries(stream)
271		EMPromise.resolve(@values[stream] || [])
272	end
273end
274
275REDIS = FakeRedis.new
276
277module Minitest
278	class Test
279		def with_stubs(stubs, &block)
280			if stubs.empty?
281				block.call
282			else
283				obj, method, value = stubs.first
284				obj.stub(method, value) do
285					with_stubs(stubs[1..], &block)
286				end
287			end
288		end
289
290		def self.em(m)
291			alias_method "raw_#{m}", m
292			define_method(m) do
293				$panic = nil
294				e = nil
295				FakeFS do
296					FakeFS::FileSystem.clear
297					FileUtils.mkdir_p("/mms")
298					EM.run do
299						Fiber.new {
300							with_mms_env do
301								ARGV[0] = "component"
302								ARGV[6] = MMS_PROXY
303								begin
304									send("raw_#{m}")
305								rescue
306									e = $!
307								ensure
308									EM.stop
309								end
310							end # with_mms_env do
311						}.resume
312					end # EM.run do
313				end # FakeFs do
314				if e
315					LOG.error("Error in test: #{m}", e)
316					raise e
317				end
318				raise e if e
319			end
320		end
321	end
322end