@@ -561,6 +561,126 @@ describe('Helix Server', () => {
561561 }
562562 } ) ;
563563
564+ it ( 'transforms plain text 401 responses into Chrome-compatible HTML with meta tag' , async ( ) => {
565+ const cwd = await setupProject ( path . join ( __rootdir , 'test' , 'fixtures' , 'project' ) , testRoot ) ;
566+ const project = new HelixProject ( )
567+ . withCwd ( cwd )
568+ . withHttpPort ( 0 )
569+ . withProxyUrl ( 'http://main--foo--bar.aem.page' ) ;
570+
571+ await project . init ( ) ;
572+ project . log . level = 'silly' ;
573+
574+ // Simulate real-world scenario: pipeline returns plain text for 401
575+ nock ( 'http://main--foo--bar.aem.page' )
576+ . get ( '/protected' )
577+ . reply ( 401 , 'Unauthorized' , {
578+ 'content-type' : 'text/plain' ,
579+ } ) ;
580+
581+ try {
582+ await project . start ( ) ;
583+ const resp = await getFetch ( ) ( `http://127.0.0.1:${ project . server . port } /protected` , {
584+ cache : 'no-store' ,
585+ redirect : 'manual' ,
586+ } ) ;
587+ assert . strictEqual ( resp . status , 401 ) ;
588+ assert . ok ( resp . headers . get ( 'content-type' ) . startsWith ( 'text/html' ) ) ;
589+
590+ const body = await resp . text ( ) ;
591+ // Verify Chrome-compatible structure
592+ assert . ok ( body . includes ( '<html><head>' ) ) ;
593+ assert . ok ( body . includes ( '<meta name="color-scheme" content="light dark">' ) ) ;
594+ assert . ok ( body . includes ( '<meta property="hlx:proxyUrl" content="http://main--foo--bar.aem.page/protected">' ) ) ;
595+ assert . ok ( body . includes ( '</head><body>' ) ) ;
596+ assert . ok ( body . includes ( '<pre style="word-wrap: break-word; white-space: pre-wrap;">401 Unauthorized</pre>' ) ) ;
597+ assert . ok ( body . includes ( '</body></html>' ) ) ;
598+
599+ // Verify exact structure that sidekick expects
600+ assert . ok ( body . match ( / < b o d y > < p r e [ ^ > ] * > 4 0 1 U n a u t h o r i z e d < \/ p r e > < \/ b o d y > / ) ) ;
601+ } finally {
602+ await project . stop ( ) ;
603+ }
604+ } ) ;
605+
606+ it ( 'transforms plain text 403 responses into Chrome-compatible HTML with meta tag' , async ( ) => {
607+ const cwd = await setupProject ( path . join ( __rootdir , 'test' , 'fixtures' , 'project' ) , testRoot ) ;
608+ const project = new HelixProject ( )
609+ . withCwd ( cwd )
610+ . withHttpPort ( 0 )
611+ . withProxyUrl ( 'http://main--foo--bar.aem.page' ) ;
612+
613+ await project . init ( ) ;
614+ project . log . level = 'silly' ;
615+
616+ // Simulate real-world scenario: pipeline returns plain text for 403
617+ nock ( 'http://main--foo--bar.aem.page' )
618+ . get ( '/forbidden' )
619+ . reply ( 403 , 'Forbidden' , {
620+ 'content-type' : 'text/plain' ,
621+ } ) ;
622+
623+ try {
624+ await project . start ( ) ;
625+ const resp = await getFetch ( ) ( `http://127.0.0.1:${ project . server . port } /forbidden` , {
626+ cache : 'no-store' ,
627+ redirect : 'manual' ,
628+ } ) ;
629+ assert . strictEqual ( resp . status , 403 ) ;
630+ assert . ok ( resp . headers . get ( 'content-type' ) . startsWith ( 'text/html' ) ) ;
631+
632+ const body = await resp . text ( ) ;
633+ // Verify Chrome-compatible structure
634+ assert . ok ( body . includes ( '<html><head>' ) ) ;
635+ assert . ok ( body . includes ( '<meta name="color-scheme" content="light dark">' ) ) ;
636+ assert . ok ( body . includes ( '<meta property="hlx:proxyUrl" content="http://main--foo--bar.aem.page/forbidden">' ) ) ;
637+ assert . ok ( body . includes ( '</head><body>' ) ) ;
638+ assert . ok ( body . includes ( '<pre style="word-wrap: break-word; white-space: pre-wrap;">403 Forbidden</pre>' ) ) ;
639+ assert . ok ( body . includes ( '</body></html>' ) ) ;
640+
641+ // Verify exact structure that sidekick expects
642+ assert . ok ( body . match ( / < b o d y > < p r e [ ^ > ] * > 4 0 3 F o r b i d d e n < \/ p r e > < \/ b o d y > / ) ) ;
643+ } finally {
644+ await project . stop ( ) ;
645+ }
646+ } ) ;
647+
648+ it ( 'escapes special characters in URL when transforming 401 responses' , async ( ) => {
649+ const cwd = await setupProject ( path . join ( __rootdir , 'test' , 'fixtures' , 'project' ) , testRoot ) ;
650+ const project = new HelixProject ( )
651+ . withCwd ( cwd )
652+ . withHttpPort ( 0 )
653+ . withProxyUrl ( 'http://main--foo--bar.aem.page' ) ;
654+
655+ await project . init ( ) ;
656+ project . log . level = 'silly' ;
657+
658+ // URL with special characters that need escaping
659+ nock ( 'http://main--foo--bar.aem.page' )
660+ . get ( '/path?param=value&other="quoted"' )
661+ . reply ( 401 , 'Unauthorized' , {
662+ 'content-type' : 'text/plain' ,
663+ } ) ;
664+
665+ try {
666+ await project . start ( ) ;
667+ const resp = await getFetch ( ) ( `http://127.0.0.1:${ project . server . port } /path?param=value&other="quoted"` , {
668+ cache : 'no-store' ,
669+ redirect : 'manual' ,
670+ } ) ;
671+ assert . strictEqual ( resp . status , 401 ) ;
672+
673+ const body = await resp . text ( ) ;
674+ // URL is already percent-encoded by the time it reaches the server
675+ // Just verify the meta tag is present and ampersands are escaped
676+ assert . ok ( body . includes ( '<meta property="hlx:proxyUrl" content="http://main--foo--bar.aem.page/path?param=value&other=%22quoted%22">' ) ) ;
677+ // Verify structure is Chrome-compatible
678+ assert . ok ( body . includes ( '<pre style="word-wrap: break-word; white-space: pre-wrap;">401 Unauthorized</pre>' ) ) ;
679+ } finally {
680+ await project . stop ( ) ;
681+ }
682+ } ) ;
683+
564684 it ( 'receives site token, saves it and uses it' , async ( ) => {
565685 const siteToken = `hlxtst_${ new UnsecuredJWT ( { email : '[email protected] ' } ) . encode ( ) } ` ; 566686
0 commit comments