1# frozen_string_literal: true
2
3require "test_helper"
4require_relative "../../sgx-bwmsgsv2"
5require "rantly/minitest_extensions"
6require_relative "generators/webhook"
7require_relative "generators/message"
8
9def panic(e)
10 $panic = e
11end
12
13MMS_PROXY = "https://proxy.test.example.com/"
14
15class WebhookPropertyTest < Minitest::Test
16 def setup
17 reset_stanzas!
18 reset_redis!
19 end
20
21 def test_single_recipient_message_delivered_sends_one_receipt
22 property_of {
23 Webhook
24 .new(REDIS)
25 .type { "message-delivered" }
26 .message { |registered, jid, dir, top_level_to|
27 Message
28 .new(REDIS)
29 .to { [top_level_to] }
30 .generate(registered, jid, dir)
31 }
32 .generate
33 }.check { |metadata, example|
34 result = invoke_webhook(example)
35 assert_equal 200, result[0]
36 assert_equal 1, written.length, "Should only send the delivery receipt"
37 receipt = written.shift
38 assert_equal(
39 receipt.to,
40 metadata["jid"],
41 "Should send receipt to customer's jid"
42 )
43 assert_equal(
44 receipt.from.to_s,
45 "#{example["to"]}@#{ARGV[0]}",
46 "Should send receipt from sender's Cheogram jid"
47 )
48 }
49 end
50 em :test_single_recipient_message_delivered_sends_one_receipt
51
52 def test_multi_recipient_outbound_sends_no_receipts
53 property_of {
54 Webhook
55 .new(REDIS)
56 .type { choose(*Webhook::OUTBOUND_TYPES) }
57 .message { |registered, jid, dir, top_level_to|
58 Message
59 .new(REDIS)
60 .to {
61 array(integer(2)) { nanpa_phone } +
62 [top_level_to] +
63 array(range(1, 3)) { nanpa_phone }
64 }
65 .generate(registered, jid, dir)
66 }
67 .generate
68 }.check { |metadata, example|
69 result = invoke_webhook(example)
70 assert_equal 200, result[0]
71 assert_equal 0, written.length, "Should not group chat receipts"
72 }
73 end
74 em :test_multi_recipient_outbound_sends_no_receipts
75
76 def test_single_recipient_message_failed_sends_one_error
77 property_of {
78 Webhook
79 .new(REDIS)
80 .type { "message-failed" }
81 .message { |registered, jid, dir, top_level_to|
82 Message
83 .new(REDIS)
84 .to { [top_level_to] }
85 .generate(registered, jid, dir)
86 }
87 .generate
88 }.check { |metadata, example|
89 result = invoke_webhook(example)
90 assert_equal 200, result[0]
91 assert_equal(
92 written.length,
93 1,
94 "Message failed should only send one notification"
95 )
96 assert_kind_of(
97 ::Blather::StanzaError,
98 written.shift,
99 "Should not notify message-failed"
100 )
101 }
102 end
103 em :test_single_recipient_message_failed_sends_one_error
104
105 def test_outbound_unregistered_returns_403
106 property_of {
107 Webhook
108 .new(REDIS)
109 .registered {
110 false
111 }
112 .type {
113 choose(*Webhook::OUTBOUND_TYPES)
114 }
115 .generate
116 }.check { |metadata, example|
117 result = invoke_webhook(example)
118 assert_equal [403, {}, "Customer not found\n"], result
119 assert_empty written
120 entries = REDIS.stream_entries("messages").sync
121 assert_empty entries
122 }
123 end
124 em :test_outbound_unregistered_returns_403
125
126 def test_unknown_outbound_returns_200
127 property_of {
128 Webhook
129 .new(REDIS)
130 .type { "unknown" }
131 .direction { "out" }
132 .generate
133 }.check { |metadata, example|
134 result = invoke_webhook(example)
135 assert_equal [200, {}, "OK"], result
136 assert_empty written
137 entries = REDIS.stream_entries("messages").sync
138 assert_empty entries
139 }
140 end
141 em :test_unknown_outbound_returns_200
142
143 def test_unknown_direction_returns_400_ok_except_when_message_failed
144 property_of {
145 Webhook
146 .new(REDIS)
147 .direction { "unknown" }
148 .type {
149 choose(*(Webhook::INBOUND_TYPES << Webhook::OUTBOUND_TYPES)
150 .reject { |ty|
151 ty == "message-failed"
152 }
153 )
154 }
155 .generate
156 }.check { |metadata, example|
157 result = invoke_webhook(example)
158 assert_equal [400, {}, "Missing params\n"], result
159 assert_empty written
160 entries = REDIS.stream_entries("messages").sync
161 assert_empty entries
162 }
163 end
164 em :test_unknown_direction_returns_400_ok_except_when_message_failed
165
166 def test_unknown_direction_returns_400_missing_params_when_message_failed
167 property_of {
168 Webhook
169 .new(REDIS)
170 .direction { "unknown" }
171 .type {
172 "message-failed"
173 }
174 .generate
175 }.check { |metadata, example|
176 result = invoke_webhook(example)
177 assert_equal [400, {}, "Missing params\n"], result
178 assert_empty written
179 entries = REDIS.stream_entries("messages").sync
180 assert_empty entries
181 }
182 end
183 em :test_unknown_direction_returns_400_missing_params_when_message_failed
184
185 def test_delivered_emits_correct_stream_event
186 property_of {
187 Webhook
188 .new(REDIS)
189 .type { "message-delivered" }
190 .generate
191 }.check { |metadata, example|
192 invoke_webhook(example)
193
194 entries = REDIS.stream_entries("messages").sync
195 assert_equal 1, entries.length
196
197 fields = entries.first[:fields]
198 expected_keys = %w[
199 event source timestamp stanza_id bandwidth_id
200 ].sort
201 assert_equal expected_keys, fields.keys.sort
202
203 assert_equal "delivered", fields["event"]
204 assert_equal "bwmsgsv2", fields["source"]
205
206 assert_equal metadata["stanza_id"], fields["stanza_id"]
207 assert_equal example["message"]["id"], fields["bandwidth_id"]
208 assert_equal example["message"]["time"], fields["timestamp"]
209 }
210 end
211 em :test_delivered_emits_correct_stream_event
212
213 def test_failed_emits_correct_stream_event
214 property_of {
215 Webhook
216 .new(REDIS)
217 .type { "message-failed" }
218 .generate
219 }.check { |metadata, example|
220 invoke_webhook(example)
221
222 entries = REDIS.stream_entries("messages").sync
223 assert_equal 1, entries.length
224
225 fields = entries.first[:fields]
226 expected_keys = %w[
227 event source timestamp stanza_id bandwidth_id
228 error_code error_description
229 ].sort
230 assert_equal expected_keys, fields.keys.sort
231
232 assert_equal "failed", fields["event"]
233 assert_equal "bwmsgsv2", fields["source"]
234
235 tag_parts = example["message"]["tag"].split(/ /, 2)
236 expected_stanza_id = WEBrick::HTTPUtils.unescape(tag_parts[0])
237 assert_equal expected_stanza_id, fields["stanza_id"]
238 assert_equal example["message"]["id"], fields["bandwidth_id"]
239 assert_equal example["message"]["time"], fields["timestamp"]
240 assert_equal example["message"]["errorCode"].to_s, fields["error_code"]
241 assert_equal example["message"]["description"].to_s, fields["error_description"]
242 }
243 end
244 em :test_failed_emits_correct_stream_event
245
246 def test_inbound_received_emits_correct_in_stream_event
247 property_of {
248 Webhook
249 .new(REDIS)
250 .message { |registered, jid, dir, top_level_to|
251 Message
252 .new(REDIS)
253 .to {
254 array(integer(2)) { nanpa_phone } +
255 [top_level_to] +
256 array(integer(2)) { nanpa_phone }
257 }
258 .text { message_body(nil_pct: 0, empty_pct: 0) }
259 .generate(registered, jid, dir)
260 }
261 .type { "message-received" }
262 .generate
263 }.check { |metadata, example|
264 result = invoke_webhook(example)
265 assert_equal 200, result[0]
266
267 entries = REDIS.stream_entries("messages").sync
268 assert_equal 1, entries.length
269
270 fields = entries.first[:fields]
271 expected_keys = %w[
272 event source timestamp owner from to
273 bandwidth_id body media_urls
274 ].sort
275 assert_equal expected_keys, fields.keys.sort
276
277 assert_equal "in", fields["event"]
278 assert_equal "bwmsgsv2", fields["source"]
279 assert_equal example["message"]["time"], fields["timestamp"]
280 assert_equal example["message"]["owner"], fields["owner"]
281 assert_equal example["message"]["from"], fields["from"]
282 assert_equal JSON.dump(example["message"]["to"]), fields["to"]
283 assert_equal example["message"]["id"], fields["bandwidth_id"]
284 assert_equal example["message"]["text"].to_s, fields["body"]
285
286 expected_media = Array(example["message"]["media"]).reject { |u|
287 u.end_with?(".smil", ".txt", ".xml")
288 }
289 assert_equal JSON.dump(expected_media), fields["media_urls"]
290 }
291 end
292 em :test_inbound_received_emits_correct_in_stream_event
293
294 def test_inbound_resend_emits_correct_resend_stream_event
295 property_of {
296 Webhook
297 .new(REDIS)
298 .type { "message-received" }
299 .message { |registered, jid, dir, top_level_to|
300 Message
301 .new(REDIS)
302 .to {
303 array(integer(2)) { nanpa_phone } +
304 [top_level_to] +
305 array(integer(2)) { nanpa_phone }
306 }
307 .text { message_body(nil_pct: 0, empty_pct: 0) }
308 .generate(registered, jid, dir)
309 }
310 .generate
311 }.check { |metadata, example|
312 result = invoke_webhook(
313 example,
314 extra_env: { "HTTP_X_JMP_RESEND_OF" => metadata["resend_id"] }
315 )
316 assert_equal 200, result[0]
317
318 entries = REDIS.stream_entries("messages").sync
319 assert_equal 1, entries.length
320
321 fields = entries.first[:fields]
322 expected_keys = %w[
323 event source original_stream_id owner
324 original_bandwidth_id
325 ].sort
326 assert_equal expected_keys, fields.keys.sort
327
328 assert_equal "resend", fields["event"]
329 assert_equal "bwmsgsv2", fields["source"]
330 assert_equal metadata["resend_id"], fields["original_stream_id"]
331 assert_equal example["message"]["owner"], fields["owner"]
332 assert_equal example["message"]["id"], fields["original_bandwidth_id"]
333 }
334 end
335 em :test_inbound_resend_emits_correct_resend_stream_event
336
337 def test_inbound_empty_or_nil_text_no_media_returns_400
338 property_of {
339 Webhook
340 .new(REDIS)
341 .type { "message-received" }
342 .message { |registered, jid, dir, _|
343 Message
344 .new(REDIS)
345 .to { [nanpa_phone] }
346 .text { choose(nil, "") }
347 .media { nil }
348 .generate(registered, jid, dir)
349 }
350 .generate
351 }.check { |metadata, example|
352 result = invoke_webhook(example)
353 assert_equal [400, {}, "Missing params\n"], result
354 assert_empty written
355 entries = REDIS.stream_entries("messages").sync
356 assert_empty entries
357 }
358 end
359 em :test_inbound_empty_or_nil_text_no_media_returns_400
360
361 def test_inbound_unknown_type_sends_notification
362 property_of {
363 Webhook
364 .new(REDIS)
365 .type { "unknown" }
366 .direction { "in" }
367 .message { |registered, jid, dir, top_level_to|
368 Message
369 .new(REDIS)
370 .to { [nanpa_phone] }
371 .generate(registered, jid, dir)
372 }
373 .generate
374 }.check { |metadata, example|
375 result = invoke_webhook(example)
376 assert_equal 200, result[0]
377 assert_equal 1, written.length
378
379 msg = written.shift
380 assert_kind_of Blather::Stanza::Message, msg
381 assert_equal(
382 metadata["jid"],
383 msg.to.to_s,
384 "Notification should be sent to customer's jid"
385 )
386
387 expected_from = example["message"]["from"]
388 unless expected_from.start_with?("+")
389 expected_from += ";phone-context=ca-us.phone-context.soprani.ca"
390 end
391 assert_equal(
392 "#{expected_from}@#{ARGV[0]}",
393 msg.from.to_s,
394 "Notification should be from sender's Cheogram jid"
395 )
396
397 expected_text = "unknown type (unknown)" \
398 " with text: #{example["message"]["text"]}"
399 assert_equal(
400 expected_text,
401 msg.body,
402 "Body should contain the unknown type and original text"
403 )
404
405 entries = REDIS.stream_entries("messages").sync
406 assert_empty entries, "Unknown inbound type should not emit a stream event"
407 }
408 end
409 em :test_inbound_unknown_type_sends_notification
410
411 def test_request_with_empty_params_produces_no_output
412 property_of {
413 Webhook.new(REDIS).generate
414 }.check { |metadata, example|
415 result = invoke_webhook(example, extra_env: { "params" => {} })
416 assert_equal [200, {}, "OK"], result
417 assert_empty written
418 }
419 end
420 em :test_request_with_empty_params_produces_no_output
421
422 def test_request_with_non_root_uri_produces_no_output
423 property_of {
424 Webhook.new(REDIS).generate
425 }.check { |metadata, example|
426 result = invoke_webhook(example, extra_env: { "REQUEST_URI" => "/wrong" })
427 assert_equal [200, {}, "OK"], result
428 assert_empty written
429 }
430 end
431 em :test_request_with_non_root_uri_produces_no_output
432
433 def test_request_with_non_post_method_produces_no_output
434 property_of {
435 Webhook.new(REDIS).generate
436 }.check { |metadata, example|
437 result = invoke_webhook(example, extra_env: { "REQUEST_METHOD" => "GET" })
438 assert_equal [200, {}, "OK"], result
439 assert_empty written
440 }
441 end
442 em :test_request_with_non_post_method_produces_no_output
443
444 def test_payload_without_message_or_type_returns_400
445 property_of {
446 metadata, example = Webhook.new(REDIS).generate
447 [choose("message", "type"), metadata, example]
448 }.check { |key, metadata, example|
449 example.delete(key)
450 result = invoke_webhook(example)
451 assert_equal [400, {}, "Missing params\n"], result
452 assert_empty written
453 }
454 end
455 em :test_payload_without_message_or_type_returns_400
456
457 def test_message_with_non_array_to_returns_400
458 property_of {
459 Webhook
460 .new(REDIS)
461 .message { |registered, jid, dir, top_level_to|
462 Message
463 .new(REDIS)
464 .to { top_level_to }
465 .owner { top_level_to }
466 .generate(registered, jid, dir)
467 }
468 .generate
469 }.check { |metadata, example|
470 result = invoke_webhook(example)
471 assert_equal [400, {}, "Missing params\n"], result
472 assert_empty written
473 }
474 end
475 em :test_message_with_non_array_to_returns_400
476
477 def test_message_with_empty_to_returns_400
478 property_of {
479 Webhook
480 .new(REDIS)
481 .message { |registered, jid, dir, top_level_to|
482 Message
483 .new(REDIS)
484 .to { [] }
485 .owner { top_level_to }
486 .generate(registered, jid, dir)
487 }
488 .generate
489 }.check { |metadata, example|
490 result = invoke_webhook(example)
491 assert_equal [400, {}, "Missing params\n"], result
492 assert_empty written
493 }
494 end
495 em :test_message_with_empty_to_returns_400
496
497 def test_inbound_nil_text_with_media_multi_recipient_writes_empty_body
498 property_of {
499 Webhook
500 .new(REDIS)
501 .type { "message-received" }
502 .message { |registered, jid, dir, top_level_to|
503 Message
504 .new(REDIS)
505 .to {
506 array(range(1, 3)) { nanpa_phone } +
507 [top_level_to] +
508 array(integer(2)) { nanpa_phone }
509 }
510 .text { nil }
511 .media { array(range(1, 3)) { media_url } }
512 .generate(registered, jid, dir)
513 }
514 .generate
515 }.check { |metadata, example|
516 result = invoke_webhook(example)
517 assert_equal 200, result[0]
518 assert_operator written.length, :>=, 1
519
520 msg = written.last
521 assert_kind_of Blather::Stanza::Message, msg
522 assert(
523 msg.body.to_s.empty?,
524 "Body should be nil/empty when text is nil"
525 )
526 }
527 end
528 em :test_inbound_nil_text_with_media_multi_recipient_writes_empty_body
529end