test_webhook_handler.rb

  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