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