1# frozen_string_literal: true
2
3require "test_helper"
4require_relative "../sgx-bwmsgsv2"
5
6def panic(e)
7 $panic = e
8end
9
10class WebhookHandlerTest < Minitest::Test
11 def setup
12 ARGV[0] = 'component'
13 SGXbwmsgsv2.instance_variable_set(:@written, [])
14
15 def SGXbwmsgsv2.write_to_stream(s)
16 @written ||= []
17 @written << s
18 end
19
20 REDIS.reset!
21 REDIS.set("catapult_jid-", "HERE")
22 REDIS.set("catapult_jid-+15550000000", "test@example.com")
23 REDIS.set("catapult_cred-test@example.com", [
24 'account', 'user', 'password', '+15550000000'
25 ])
26 end
27
28 def invoke_webhook(payload, extra_env: {})
29 with_stubs([
30 [
31 SGXbwmsgsv2,
32 :write,
33 ->(data) { SGXbwmsgsv2.write_to_stream(data) }
34 ]
35 ]) do
36 handler = WebhookHandler.new
37 env = {
38 "REQUEST_URI" => "/",
39 "REQUEST_METHOD" => "POST",
40 "params" => {"_json" => [payload]}
41 }.merge(extra_env)
42 handler.instance_variable_set(:@env, env)
43 def handler.params
44 @env["params"]
45 end
46
47 EMPromise.resolve(nil).then {
48 handler.response(env)
49 }.sync
50 end
51 end
52
53 def test_inbound_sms_emits_to_stream
54 payload = {
55 "type" => "message-received",
56 "to" => "+15550000000",
57 "message" => {
58 "id" => "bw-in-123",
59 "direction" => "in",
60 "owner" => "+15550000000",
61 "from" => "+15551234567",
62 "to" => ["+15550000000"],
63 "time" => "2025-01-13T10:00:00Z",
64 "text" => "Hello from outside"
65 }
66 }
67
68 invoke_webhook(payload)
69
70 entries = REDIS.stream_entries("messages").sync
71 assert_equal 1, entries.length
72
73 event = entries.first[:fields]
74 assert_equal "in", event["event"]
75 assert_equal "+15551234567", event["from"]
76 assert_equal JSON.dump(["+15550000000"]), event["to"]
77 assert_equal "bw-in-123", event["bandwidth_id"]
78 assert_equal "+15550000000", event["owner"]
79 assert_equal "Hello from outside", event["body"]
80 assert_equal JSON.dump([]), event["media_urls"]
81 end
82 em :test_inbound_sms_emits_to_stream
83
84 def test_inbound_mms_emits_to_stream_and_filters_smil
85 payload = {
86 "type" => "message-received",
87 "to" => "+15550000000",
88 "message" => {
89 "id" => "bw-mms-456",
90 "direction" => "in",
91 "owner" => "+15550000000",
92 "from" => "+15551234567",
93 "to" => ["+15550000000"],
94 "time" => "2025-01-13T10:05:00Z",
95 "text" => "Check this out",
96 "media" => [
97 "https://example.com/image.jpg",
98 "https://example.com/file.smil",
99 "https://example.com/data.txt",
100 "https://example.com/meta.xml"
101 ]
102 }
103 }
104
105 invoke_webhook(payload)
106
107 entries = REDIS.stream_entries("messages").sync
108 assert_equal 1, entries.length
109
110 event = entries.first[:fields]
111 assert_equal "in", event["event"]
112 assert_equal JSON.dump(["https://example.com/image.jpg"]), event["media_urls"]
113 end
114 em :test_inbound_mms_emits_to_stream_and_filters_smil
115
116 def test_message_delivered_emits_to_stream
117 payload = {
118 "type" => "message-delivered",
119 "to" => "+15550000000",
120 "message" => {
121 "id" => "bw-out-789",
122 "direction" => "out",
123 "owner" => "+15550000000",
124 "from" => "+15550000000",
125 "to" => ["+15551234567"],
126 "time" => "2025-01-13T10:10:00Z",
127 "tag" => "stanza-id-abc extra-data"
128 }
129 }
130
131 invoke_webhook(payload)
132
133 entries = REDIS.stream_entries("messages").sync
134 assert_equal 1, entries.length
135
136 event = entries.first[:fields]
137 assert_equal "delivered", event["event"]
138 assert_equal "stanza-id-abc", event["stanza_id"]
139 assert_equal "bw-out-789", event["bandwidth_id"]
140 assert_equal "2025-01-13T10:10:00Z", event["timestamp"]
141 end
142 em :test_message_delivered_emits_to_stream
143
144 def test_message_failed_emits_to_stream
145 payload = {
146 "type" => "message-failed",
147 "to" => "+15551234567",
148 "message" => {
149 "id" => "bw-out-999",
150 "direction" => "out",
151 "owner" => "+15550000000",
152 "from" => "+15550000000",
153 "to" => ["+15551234567"],
154 "time" => "2025-01-13T10:15:00Z",
155 "tag" => "failed-stanza-xyz extra",
156 "errorCode" => 4720,
157 "description" => "Carrier rejected message"
158 }
159 }
160
161 invoke_webhook(payload)
162
163 entries = REDIS.stream_entries("messages").sync
164 assert_equal 1, entries.length
165
166 event = entries.first[:fields]
167 assert_equal "failed", event["event"]
168 assert_equal "failed-stanza-xyz", event["stanza_id"]
169 assert_equal "bw-out-999", event["bandwidth_id"]
170 assert_equal "4720", event["error_code"]
171 assert_equal "Carrier rejected message", event["error_description"]
172 assert_equal "2025-01-13T10:15:00Z", event["timestamp"]
173 end
174 em :test_message_failed_emits_to_stream
175
176 def test_resend_emits_resend_event_instead_of_in
177 payload = {
178 "type" => "message-received",
179 "message" => {
180 "id" => "bw-in-resend-001",
181 "direction" => "in",
182 "owner" => "+15550000000",
183 "from" => "+15551234567",
184 "to" => ["+15550000000"],
185 "time" => "2025-01-13T10:00:00Z",
186 "text" => "Resent message"
187 }
188 }
189
190 invoke_webhook(
191 payload,
192 extra_env: { "HTTP_X_JMP_RESEND_OF" => "1736762400000-0" }
193 )
194
195 entries = REDIS.stream_entries("messages").sync
196 assert_equal 1, entries.length
197
198 event = entries.first[:fields]
199 assert_equal "resend", event["event"]
200 assert_equal "bwmsgsv2", event["source"]
201 assert_equal "+15550000000", event["owner"]
202 assert_equal "1736762400000-0", event["original_stream_id"]
203 assert_equal "bw-in-resend-001", event["original_bandwidth_id"]
204 refute event.key?("from"), "Resend events should not duplicate message fields"
205 refute event.key?("body"), "Resend events should not duplicate message fields"
206 end
207 em :test_resend_emits_resend_event_instead_of_in
208end