@@ -162,6 +162,112 @@ public function testChannelFilterDisabledWhenNull()
162162 Queue::assertPushed (WebhookDeliveryJob::class);
163163 }
164164
165+ public function testChannelEndsWithFilterSkipsNonMatchingChannel ()
166+ {
167+ Queue::fake ();
168+
169+ $ app = $ this ->makeApp (webhooks: [
170+ 'url ' => 'https://example.com/webhook ' ,
171+ 'events ' => ['channel_occupied ' ],
172+ 'filter ' => ['channel_name_ends_with ' => '-chat ' ],
173+ ]);
174+
175+ $ dispatcher = new HttpWebhookDispatcher ;
176+ $ dispatcher ->dispatch ($ app , 'channel_occupied ' , ['channel ' => 'tenant-1-notifications ' ]);
177+
178+ Queue::assertNotPushed (WebhookDeliveryJob::class);
179+ }
180+
181+ public function testChannelEndsWithFilterAllowsMatchingChannel ()
182+ {
183+ Queue::fake ();
184+
185+ $ app = $ this ->makeApp (webhooks: [
186+ 'url ' => 'https://example.com/webhook ' ,
187+ 'events ' => ['channel_occupied ' ],
188+ 'filter ' => ['channel_name_ends_with ' => '-chat ' ],
189+ ]);
190+
191+ $ dispatcher = new HttpWebhookDispatcher ;
192+ $ dispatcher ->dispatch ($ app , 'channel_occupied ' , ['channel ' => 'tenant-1-chat ' ]);
193+
194+ Queue::assertPushed (WebhookDeliveryJob::class);
195+ }
196+
197+ public function testChannelEndsWithFilterDisabledWhenNull ()
198+ {
199+ Queue::fake ();
200+
201+ $ app = $ this ->makeApp (webhooks: [
202+ 'url ' => 'https://example.com/webhook ' ,
203+ 'events ' => ['channel_occupied ' ],
204+ 'filter ' => ['channel_name_ends_with ' => null ],
205+ ]);
206+
207+ $ dispatcher = new HttpWebhookDispatcher ;
208+ $ dispatcher ->dispatch ($ app , 'channel_occupied ' , ['channel ' => 'any-channel ' ]);
209+
210+ Queue::assertPushed (WebhookDeliveryJob::class);
211+ }
212+
213+ public function testBothFiltersAppliedAsAnd ()
214+ {
215+ Queue::fake ();
216+
217+ $ app = $ this ->makeApp (webhooks: [
218+ 'url ' => 'https://example.com/webhook ' ,
219+ 'events ' => ['channel_occupied ' ],
220+ 'filter ' => [
221+ 'channel_name_starts_with ' => 'tenant-1- ' ,
222+ 'channel_name_ends_with ' => '-chat ' ,
223+ ],
224+ ]);
225+
226+ $ dispatcher = new HttpWebhookDispatcher ;
227+
228+ // Matches both — should pass
229+ $ dispatcher ->dispatch ($ app , 'channel_occupied ' , ['channel ' => 'tenant-1-chat ' ]);
230+ Queue::assertPushed (WebhookDeliveryJob::class);
231+ }
232+
233+ public function testBothFiltersRejectWhenOnlyPrefixMatches ()
234+ {
235+ Queue::fake ();
236+
237+ $ app = $ this ->makeApp (webhooks: [
238+ 'url ' => 'https://example.com/webhook ' ,
239+ 'events ' => ['channel_occupied ' ],
240+ 'filter ' => [
241+ 'channel_name_starts_with ' => 'tenant-1- ' ,
242+ 'channel_name_ends_with ' => '-chat ' ,
243+ ],
244+ ]);
245+
246+ $ dispatcher = new HttpWebhookDispatcher ;
247+ $ dispatcher ->dispatch ($ app , 'channel_occupied ' , ['channel ' => 'tenant-1-notifications ' ]);
248+
249+ Queue::assertNotPushed (WebhookDeliveryJob::class);
250+ }
251+
252+ public function testBothFiltersRejectWhenOnlySuffixMatches ()
253+ {
254+ Queue::fake ();
255+
256+ $ app = $ this ->makeApp (webhooks: [
257+ 'url ' => 'https://example.com/webhook ' ,
258+ 'events ' => ['channel_occupied ' ],
259+ 'filter ' => [
260+ 'channel_name_starts_with ' => 'tenant-1- ' ,
261+ 'channel_name_ends_with ' => '-chat ' ,
262+ ],
263+ ]);
264+
265+ $ dispatcher = new HttpWebhookDispatcher ;
266+ $ dispatcher ->dispatch ($ app , 'channel_occupied ' , ['channel ' => 'tenant-2-chat ' ]);
267+
268+ Queue::assertNotPushed (WebhookDeliveryJob::class);
269+ }
270+
165271 public function testCustomHeadersPassedToJob ()
166272 {
167273 Queue::fake ();
0 commit comments