@@ -593,9 +593,14 @@ describe("ReflagClient", () => {
593593 await client . updateUser ( user . id ) ;
594594 await client . flush ( ) ;
595595
596- expect ( logger . error ) . toHaveBeenCalledWith (
597- expect . stringMatching ( "post request to .* failed with error" ) ,
598- error ,
596+ expect ( logger . warn ) . toHaveBeenCalledWith (
597+ "flush of buffered items failed; discarding items" ,
598+ expect . objectContaining ( {
599+ count : 1 ,
600+ error : expect . objectContaining ( {
601+ message : "failed to send bulk events" ,
602+ } ) ,
603+ } ) ,
599604 ) ;
600605 } ) ;
601606
@@ -608,9 +613,15 @@ describe("ReflagClient", () => {
608613 await client . flush ( ) ;
609614
610615 expect ( logger . warn ) . toHaveBeenCalledWith (
611- expect . stringMatching ( "invalid response received from server for" ) ,
612- JSON . stringify ( response ) ,
616+ "flush of buffered items failed; discarding items" ,
617+ expect . objectContaining ( {
618+ count : 1 ,
619+ error : expect . objectContaining ( {
620+ message : "failed to send bulk events" ,
621+ } ) ,
622+ } ) ,
613623 ) ;
624+ expect ( logger . error ) . not . toHaveBeenCalled ( ) ;
614625 } ) ;
615626
616627 it ( "should throw an error if opts are not valid or the user is not set" , async ( ) => {
@@ -682,9 +693,14 @@ describe("ReflagClient", () => {
682693 await client . updateCompany ( company . id , { } ) ;
683694 await client . flush ( ) ;
684695
685- expect ( logger . error ) . toHaveBeenCalledWith (
686- expect . stringMatching ( "post request to .* failed with error" ) ,
687- error ,
696+ expect ( logger . warn ) . toHaveBeenCalledWith (
697+ "flush of buffered items failed; discarding items" ,
698+ expect . objectContaining ( {
699+ count : 1 ,
700+ error : expect . objectContaining ( {
701+ message : "failed to send bulk events" ,
702+ } ) ,
703+ } ) ,
688704 ) ;
689705 } ) ;
690706
@@ -700,9 +716,15 @@ describe("ReflagClient", () => {
700716 await client . flush ( ) ;
701717
702718 expect ( logger . warn ) . toHaveBeenCalledWith (
703- expect . stringMatching ( "invalid response received from server for" ) ,
704- JSON . stringify ( response ) ,
719+ "flush of buffered items failed; discarding items" ,
720+ expect . objectContaining ( {
721+ count : 1 ,
722+ error : expect . objectContaining ( {
723+ message : "failed to send bulk events" ,
724+ } ) ,
725+ } ) ,
705726 ) ;
727+ expect ( logger . error ) . not . toHaveBeenCalled ( ) ;
706728 } ) ;
707729
708730 it ( "should throw an error if company is not valid" , async ( ) => {
@@ -819,9 +841,14 @@ describe("ReflagClient", () => {
819841 await client . bindClient ( { user } ) . track ( event . event ) ;
820842 await client . flush ( ) ;
821843
822- expect ( logger . error ) . toHaveBeenCalledWith (
823- expect . stringMatching ( "post request to .* failed with error" ) ,
824- error ,
844+ expect ( logger . warn ) . toHaveBeenCalledWith (
845+ "flush of buffered items failed; discarding items" ,
846+ expect . objectContaining ( {
847+ count : 2 ,
848+ error : expect . objectContaining ( {
849+ message : "failed to send bulk events" ,
850+ } ) ,
851+ } ) ,
825852 ) ;
826853 } ) ;
827854
@@ -837,9 +864,15 @@ describe("ReflagClient", () => {
837864 await client . flush ( ) ;
838865
839866 expect ( logger . warn ) . toHaveBeenCalledWith (
840- expect . stringMatching ( "invalid response received from server for " ) ,
841- JSON . stringify ( response ) ,
867+ "flush of buffered items failed; discarding items" ,
868+ expect . objectContaining ( {
869+ count : 2 ,
870+ error : expect . objectContaining ( {
871+ message : "failed to send bulk events" ,
872+ } ) ,
873+ } ) ,
842874 ) ;
875+ expect ( logger . error ) . not . toHaveBeenCalled ( ) ;
843876 } ) ;
844877
845878 it ( "should log if user is not set" , async ( ) => {
@@ -957,10 +990,11 @@ describe("ReflagClient", () => {
957990 } ) ;
958991
959992 it ( "should load flag definitions from flagsFallbackProvider when live fetch fails" , async ( ) => {
993+ const savedAt = "2026-03-09T00:00:00.000Z" ;
960994 const flagsFallbackProvider : FlagsFallbackProvider = {
961995 load : vi . fn ( ) . mockResolvedValue ( {
962996 version : 1 ,
963- savedAt : "2026-03-09T00:00:00.000Z" ,
997+ savedAt,
964998 flags : flagDefinitions . features ,
965999 } ) ,
9661000 save : vi . fn ( ) ,
@@ -971,6 +1005,90 @@ describe("ReflagClient", () => {
9711005 const client = new ReflagClient ( {
9721006 ...validOptions ,
9731007 flagsFallbackProvider,
1008+ flagsFetchRetries : 0 ,
1009+ } ) ;
1010+
1011+ vi . useFakeTimers ( ) ;
1012+ vi . setSystemTime ( new Date ( "2026-03-09T00:20:00.000Z" ) ) ;
1013+ try {
1014+ await client . initialize ( ) ;
1015+
1016+ expect ( flagsFallbackProvider . load ) . toHaveBeenCalledWith (
1017+ expect . objectContaining ( {
1018+ secretKeyHash : expect . any ( String ) ,
1019+ } ) ,
1020+ ) ;
1021+ expect ( logger . warn ) . toHaveBeenCalledTimes ( 1 ) ;
1022+ expect ( logger . warn ) . toHaveBeenCalledWith (
1023+ `remote flags unavailable, using fallback flags fetched 20m ago (${ savedAt } )` ,
1024+ ) ;
1025+
1026+ expect (
1027+ client . getFlag ( { company, user, other : otherContext } , "flag1" ) ,
1028+ ) . toStrictEqual ( {
1029+ key : "flag1" ,
1030+ isEnabled : true ,
1031+ config : {
1032+ key : "config-1" ,
1033+ payload : { something : "else" } ,
1034+ } ,
1035+ track : expect . any ( Function ) ,
1036+ } ) ;
1037+ } finally {
1038+ vi . useRealTimers ( ) ;
1039+ }
1040+ } ) ;
1041+
1042+ it ( "should log remote flag fetch failures at debug level when using fallback definitions" , async ( ) => {
1043+ const error = new Error ( "fetch failed" ) ;
1044+ const savedAt = "2026-03-09T00:00:00.000Z" ;
1045+ const flagsFallbackProvider : FlagsFallbackProvider = {
1046+ load : vi . fn ( ) . mockResolvedValue ( {
1047+ version : 1 ,
1048+ savedAt,
1049+ flags : flagDefinitions . features ,
1050+ } ) ,
1051+ save : vi . fn ( ) ,
1052+ } ;
1053+
1054+ httpClient . get . mockRejectedValue ( error ) ;
1055+
1056+ const client = new ReflagClient ( {
1057+ ...validOptions ,
1058+ flagsFallbackProvider,
1059+ flagsFetchRetries : 0 ,
1060+ } ) ;
1061+
1062+ vi . useFakeTimers ( ) ;
1063+ vi . setSystemTime ( new Date ( "2026-03-09T00:20:00.000Z" ) ) ;
1064+ try {
1065+ await client . initialize ( ) ;
1066+
1067+ expect ( logger . debug ) . toHaveBeenCalledWith (
1068+ 'get request to "features" failed with error after 0 retries' ,
1069+ error ,
1070+ ) ;
1071+ expect ( logger . warn ) . toHaveBeenCalledWith (
1072+ `remote flags unavailable, using fallback flags fetched 20m ago (${ savedAt } )` ,
1073+ ) ;
1074+ expect ( logger . error ) . not . toHaveBeenCalled ( ) ;
1075+ } finally {
1076+ vi . useRealTimers ( ) ;
1077+ }
1078+ } ) ;
1079+
1080+ it ( "should warn when live fetch fails and flagsFallbackProvider has no saved snapshot" , async ( ) => {
1081+ const flagsFallbackProvider : FlagsFallbackProvider = {
1082+ load : vi . fn ( ) . mockResolvedValue ( undefined ) ,
1083+ save : vi . fn ( ) ,
1084+ } ;
1085+
1086+ httpClient . get . mockResolvedValue ( { success : false } ) ;
1087+
1088+ const client = new ReflagClient ( {
1089+ ...validOptions ,
1090+ flagsFallbackProvider,
1091+ flagsFetchRetries : 0 ,
9741092 } ) ;
9751093
9761094 await client . initialize ( ) ;
@@ -980,18 +1098,9 @@ describe("ReflagClient", () => {
9801098 secretKeyHash : expect . any ( String ) ,
9811099 } ) ,
9821100 ) ;
983-
984- expect (
985- client . getFlag ( { company, user, other : otherContext } , "flag1" ) ,
986- ) . toStrictEqual ( {
987- key : "flag1" ,
988- isEnabled : true ,
989- config : {
990- key : "config-1" ,
991- payload : { something : "else" } ,
992- } ,
993- track : expect . any ( Function ) ,
994- } ) ;
1101+ expect ( logger . warn ) . toHaveBeenCalledWith (
1102+ "remote flags unavailable, no fallback flags found in flagsFallbackProvider" ,
1103+ ) ;
9951104 } ) ;
9961105
9971106 it ( "should save fetched flag definitions to flagsFallbackProvider" , async ( ) => {
@@ -1856,9 +1965,14 @@ describe("ReflagClient", () => {
18561965
18571966 await client . flush ( ) ;
18581967
1859- expect ( logger . error ) . toHaveBeenCalledWith (
1860- expect . stringMatching ( "post request .* failed with error" ) ,
1861- expect . any ( Error ) ,
1968+ expect ( logger . warn ) . toHaveBeenCalledWith (
1969+ "flush of buffered items failed; discarding items" ,
1970+ expect . objectContaining ( {
1971+ count : 2 ,
1972+ error : expect . objectContaining ( {
1973+ message : "failed to send bulk events" ,
1974+ } ) ,
1975+ } ) ,
18621976 ) ;
18631977 } ) ;
18641978
0 commit comments