@@ -336,3 +336,110 @@ Deno.test('requires bearer token when auth token configured', async () => {
336336 ) ;
337337 assertEquals ( authorized . status , 200 ) ;
338338} ) ;
339+
340+ Deno . test ( 'publish and poll with topic=issue' , async ( ) => {
341+ const handler = createMemoryRelayHandler ( { requireSignatures : false } ) ;
342+
343+ const publish = await handler (
344+ new Request ( 'http://relay.local/api/v1/publish?room=main&sender=bit&topic=issue&id=i1' , {
345+ method : 'POST' ,
346+ headers : { 'content-type' : 'application/json' } ,
347+ body : JSON . stringify ( { kind : 'issue.created' , title : 'bug report' } ) ,
348+ } ) ,
349+ ) ;
350+ assertEquals ( publish . status , 200 ) ;
351+ assertObjectMatch ( await publish . json ( ) , { ok : true , accepted : true } ) ;
352+
353+ const poll = await handler (
354+ new Request ( 'http://relay.local/api/v1/poll?room=main&after=0&limit=10' ) ,
355+ ) ;
356+ assertEquals ( poll . status , 200 ) ;
357+ const body = await poll . json ( ) ;
358+ assertEquals ( body . envelopes . length , 1 ) ;
359+ assertObjectMatch ( body . envelopes [ 0 ] , {
360+ topic : 'issue' ,
361+ payload : { kind : 'issue.created' , title : 'bug report' } ,
362+ } ) ;
363+ } ) ;
364+
365+ Deno . test ( 'publish with dotted topic succeeds' , async ( ) => {
366+ const handler = createMemoryRelayHandler ( { requireSignatures : false } ) ;
367+
368+ const publish = await handler (
369+ new Request (
370+ 'http://relay.local/api/v1/publish?room=main&sender=bit&topic=issue.created&id=d1' ,
371+ {
372+ method : 'POST' ,
373+ headers : { 'content-type' : 'application/json' } ,
374+ body : JSON . stringify ( { title : 'test' } ) ,
375+ } ,
376+ ) ,
377+ ) ;
378+ assertEquals ( publish . status , 200 ) ;
379+ assertObjectMatch ( await publish . json ( ) , { ok : true , accepted : true } ) ;
380+
381+ const poll = await handler (
382+ new Request ( 'http://relay.local/api/v1/poll?room=main&after=0&limit=10' ) ,
383+ ) ;
384+ const body = await poll . json ( ) ;
385+ assertEquals ( body . envelopes [ 0 ] . topic , 'issue.created' ) ;
386+ } ) ;
387+
388+ Deno . test ( 'publish rejects invalid topic - empty string' , async ( ) => {
389+ const handler = createMemoryRelayHandler ( { requireSignatures : false } ) ;
390+
391+ const publish = await handler (
392+ new Request ( 'http://relay.local/api/v1/publish?room=main&sender=bit&topic=&id=e1' , {
393+ method : 'POST' ,
394+ headers : { 'content-type' : 'application/json' } ,
395+ body : JSON . stringify ( { data : 1 } ) ,
396+ } ) ,
397+ ) ;
398+ // empty topic falls back to 'notify' default
399+ assertEquals ( publish . status , 200 ) ;
400+ } ) ;
401+
402+ Deno . test ( 'publish rejects invalid topic - special characters' , async ( ) => {
403+ const handler = createMemoryRelayHandler ( { requireSignatures : false } ) ;
404+
405+ const publish = await handler (
406+ new Request (
407+ 'http://relay.local/api/v1/publish?room=main&sender=bit&topic=foo%2Fbar&id=s1' ,
408+ {
409+ method : 'POST' ,
410+ headers : { 'content-type' : 'application/json' } ,
411+ body : JSON . stringify ( { data : 1 } ) ,
412+ } ,
413+ ) ,
414+ ) ;
415+ assertEquals ( publish . status , 400 ) ;
416+ assertObjectMatch ( await publish . json ( ) , { ok : false , error : 'invalid topic: foo/bar' } ) ;
417+ } ) ;
418+
419+ Deno . test ( 'publish rejects invalid topic - starts with digit' , async ( ) => {
420+ const handler = createMemoryRelayHandler ( { requireSignatures : false } ) ;
421+
422+ const publish = await handler (
423+ new Request ( 'http://relay.local/api/v1/publish?room=main&sender=bit&topic=1abc&id=n1' , {
424+ method : 'POST' ,
425+ headers : { 'content-type' : 'application/json' } ,
426+ body : JSON . stringify ( { data : 1 } ) ,
427+ } ) ,
428+ ) ;
429+ assertEquals ( publish . status , 400 ) ;
430+ assertObjectMatch ( await publish . json ( ) , { ok : false , error : 'invalid topic: 1abc' } ) ;
431+ } ) ;
432+
433+ Deno . test ( 'publish rejects invalid topic - uppercase' , async ( ) => {
434+ const handler = createMemoryRelayHandler ( { requireSignatures : false } ) ;
435+
436+ const publish = await handler (
437+ new Request ( 'http://relay.local/api/v1/publish?room=main&sender=bit&topic=Notify&id=u1' , {
438+ method : 'POST' ,
439+ headers : { 'content-type' : 'application/json' } ,
440+ body : JSON . stringify ( { data : 1 } ) ,
441+ } ) ,
442+ ) ;
443+ assertEquals ( publish . status , 400 ) ;
444+ assertObjectMatch ( await publish . json ( ) , { ok : false , error : 'invalid topic: Notify' } ) ;
445+ } ) ;
0 commit comments