@@ -17,6 +17,7 @@ interface TaskLogContext {
1717 queueName : string ;
1818 retryAttempt ?: number ;
1919 maxRetries ?: number ;
20+ baseDelay ?: number ;
2021}
2122
2223interface StartupContext {
@@ -615,3 +616,189 @@ Deno.test('FancyFormatter - taskFailed at DEBUG level includes identifiers', ()
615616 restore ( ) ;
616617 }
617618} ) ;
619+
620+ // ============================================================
621+ // Phase 5: Retry Information Display Tests
622+ // ============================================================
623+
624+ Deno . test ( 'FancyFormatter - taskFailed shows retry pending info when retries remaining' , ( ) => {
625+ const consoleSpy = spy ( console , 'log' ) ;
626+
627+ try {
628+ const factory = createLoggingFactory ( {
629+ SUPABASE_ANON_KEY : 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' ,
630+ } ) ;
631+ const logger = factory . createLogger ( 'test' ) ;
632+
633+ const ctx : TaskLogContext = {
634+ flowSlug : 'greetUser' ,
635+ stepSlug : 'sendEmail' ,
636+ msgId : '1044' ,
637+ runId : 'run-123' ,
638+ workerId : 'worker-1' ,
639+ workerName : 'greet-user-worker' ,
640+ queueName : 'orders' ,
641+ retryAttempt : 1 , // First attempt (read_ct=1)
642+ maxRetries : 3 , // 3 retries allowed
643+ baseDelay : 5 , // 5 second base delay
644+ } ;
645+
646+ const error = new Error ( 'Request failed' ) ;
647+ logger . taskFailed ( ctx , error ) ;
648+
649+ assertSpyCalls ( consoleSpy , 1 ) ;
650+ const output = consoleSpy . calls [ 0 ] . args [ 0 ] as string ;
651+
652+ // Should contain retry icon and info
653+ assertStringIncludes ( output , '↻' ) ;
654+ assertStringIncludes ( output , 'retry 1/3' ) ;
655+ assertStringIncludes ( output , '5s' ) ; // baseDelay * 2^0 = 5s for first attempt
656+ } finally {
657+ restore ( ) ;
658+ }
659+ } ) ;
660+
661+ Deno . test ( 'FancyFormatter - taskFailed calculates exponential backoff delay correctly' , ( ) => {
662+ const consoleSpy = spy ( console , 'log' ) ;
663+
664+ try {
665+ const factory = createLoggingFactory ( {
666+ SUPABASE_ANON_KEY : 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' ,
667+ } ) ;
668+ const logger = factory . createLogger ( 'test' ) ;
669+
670+ const ctx : TaskLogContext = {
671+ flowSlug : 'greetUser' ,
672+ stepSlug : 'sendEmail' ,
673+ msgId : '1044' ,
674+ runId : 'run-123' ,
675+ workerId : 'worker-1' ,
676+ workerName : 'greet-user-worker' ,
677+ queueName : 'orders' ,
678+ retryAttempt : 2 , // Second attempt (first retry)
679+ maxRetries : 3 ,
680+ baseDelay : 5 , // 5 second base delay
681+ } ;
682+
683+ const error = new Error ( 'Request failed' ) ;
684+ logger . taskFailed ( ctx , error ) ;
685+
686+ assertSpyCalls ( consoleSpy , 1 ) ;
687+ const output = consoleSpy . calls [ 0 ] . args [ 0 ] as string ;
688+
689+ // Should show retry 2/3 with 10s delay (5 * 2^1 = 10)
690+ assertStringIncludes ( output , 'retry 2/3' ) ;
691+ assertStringIncludes ( output , '10s' ) ;
692+ } finally {
693+ restore ( ) ;
694+ }
695+ } ) ;
696+
697+ Deno . test ( 'FancyFormatter - taskFailed does NOT show retry info when no retries remaining' , ( ) => {
698+ const consoleSpy = spy ( console , 'log' ) ;
699+
700+ try {
701+ const factory = createLoggingFactory ( {
702+ SUPABASE_ANON_KEY : 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' ,
703+ } ) ;
704+ const logger = factory . createLogger ( 'test' ) ;
705+
706+ const ctx : TaskLogContext = {
707+ flowSlug : 'greetUser' ,
708+ stepSlug : 'sendEmail' ,
709+ msgId : '1044' ,
710+ runId : 'run-123' ,
711+ workerId : 'worker-1' ,
712+ workerName : 'greet-user-worker' ,
713+ queueName : 'orders' ,
714+ retryAttempt : 4 , // 4th attempt, exceeds maxRetries
715+ maxRetries : 3 , // Only 3 retries allowed
716+ baseDelay : 5 ,
717+ } ;
718+
719+ const error = new Error ( 'Request failed' ) ;
720+ logger . taskFailed ( ctx , error ) ;
721+
722+ assertSpyCalls ( consoleSpy , 1 ) ;
723+ const output = consoleSpy . calls [ 0 ] . args [ 0 ] as string ;
724+
725+ // Should NOT contain retry icon when no retries remaining
726+ assertEquals ( output . includes ( '↻' ) , false ) ;
727+ } finally {
728+ restore ( ) ;
729+ }
730+ } ) ;
731+
732+ Deno . test ( 'SimpleFormatter - taskFailed shows retry info in key=value format' , ( ) => {
733+ const consoleSpy = spy ( console , 'log' ) ;
734+
735+ try {
736+ const factory = createLoggingFactory ( {
737+ SUPABASE_ANON_KEY : 'some-production-key' ,
738+ EDGE_WORKER_LOG_LEVEL : 'verbose' ,
739+ } ) ;
740+ const logger = factory . createLogger ( 'test' ) ;
741+
742+ const ctx : TaskLogContext = {
743+ flowSlug : 'greetUser' ,
744+ stepSlug : 'sendEmail' ,
745+ msgId : '1044' ,
746+ runId : 'run-123' ,
747+ workerId : 'worker-1' ,
748+ workerName : 'greet-user-worker' ,
749+ queueName : 'orders' ,
750+ retryAttempt : 1 ,
751+ maxRetries : 3 ,
752+ baseDelay : 5 ,
753+ } ;
754+
755+ const error = new Error ( 'Request failed' ) ;
756+ logger . taskFailed ( ctx , error ) ;
757+
758+ assertSpyCalls ( consoleSpy , 1 ) ;
759+ const output = consoleSpy . calls [ 0 ] . args [ 0 ] as string ;
760+
761+ // Should contain retry info in key=value format
762+ assertStringIncludes ( output , 'retry=1/3' ) ;
763+ assertStringIncludes ( output , 'retry_delay_s=5' ) ;
764+ } finally {
765+ restore ( ) ;
766+ }
767+ } ) ;
768+
769+ Deno . test ( 'SimpleFormatter - taskFailed does NOT show retry info when no retries remaining' , ( ) => {
770+ const consoleSpy = spy ( console , 'log' ) ;
771+
772+ try {
773+ const factory = createLoggingFactory ( {
774+ SUPABASE_ANON_KEY : 'some-production-key' ,
775+ EDGE_WORKER_LOG_LEVEL : 'verbose' ,
776+ } ) ;
777+ const logger = factory . createLogger ( 'test' ) ;
778+
779+ const ctx : TaskLogContext = {
780+ flowSlug : 'greetUser' ,
781+ stepSlug : 'sendEmail' ,
782+ msgId : '1044' ,
783+ runId : 'run-123' ,
784+ workerId : 'worker-1' ,
785+ workerName : 'greet-user-worker' ,
786+ queueName : 'orders' ,
787+ retryAttempt : 4 , // Exceeds maxRetries
788+ maxRetries : 3 ,
789+ baseDelay : 5 ,
790+ } ;
791+
792+ const error = new Error ( 'Request failed' ) ;
793+ logger . taskFailed ( ctx , error ) ;
794+
795+ assertSpyCalls ( consoleSpy , 1 ) ;
796+ const output = consoleSpy . calls [ 0 ] . args [ 0 ] as string ;
797+
798+ // Should NOT contain retry info when no retries remaining
799+ assertEquals ( output . includes ( 'retry=' ) , false ) ;
800+ assertEquals ( output . includes ( 'retry_delay_s=' ) , false ) ;
801+ } finally {
802+ restore ( ) ;
803+ }
804+ } ) ;
0 commit comments