11"use strict" ;
22
3+ const util = require ( "util" ) ;
4+
35const VError = require ( "verror" ) ;
46const yaml = require ( "yaml" ) ;
57const featureTogglesModule = require ( "../src/featureToggles" ) ;
@@ -25,6 +27,9 @@ jest.mock("../src/shared/env", () => require("./__mocks__/env"));
2527const redisWrapperMock = require ( "../src/redisWrapper" ) ;
2628jest . mock ( "../src/redisWrapper" , ( ) => require ( "./__mocks__/redisWrapper" ) ) ;
2729
30+ const outputFromErrorLogger = ( calls ) =>
31+ calls . map ( ( args ) => util . format ( "%s\n%O" , args [ 0 ] , VError . info ( args [ 0 ] ) ) ) . join ( "\n" ) ;
32+
2833const configToFallbackValues = ( config ) =>
2934 Object . fromEntries ( Object . entries ( config ) . map ( ( [ key , { fallbackValue } ] ) => [ key , fallbackValue ] ) ) ;
3035const configToActiveKeys = ( config ) =>
@@ -131,16 +136,12 @@ describe("feature toggles test", () => {
131136 `[]`
132137 ) ;
133138 expect ( loggerSpy . error ) . toHaveBeenCalledTimes ( 1 ) ;
134- expect ( loggerSpy . error . mock . calls [ 0 ] ) . toMatchInlineSnapshot ( `
135- [
136- [FeatureTogglesError: scope exceeds allowed number of keys],
137- ]
138- ` ) ;
139- expect ( VError . info ( loggerSpy . error . mock . calls [ 0 ] [ 0 ] ) ) . toMatchInlineSnapshot ( `
139+ expect ( outputFromErrorLogger ( loggerSpy . error . mock . calls ) ) . toMatchInlineSnapshot ( `
140+ "FeatureTogglesError: scope exceeds allowed number of keys
140141 {
141- "maxKeys": 4 ,
142- "scopeMap": "{"tenantId":"t1","label":"l1","bla":"b1","naa":"n1","xxx":"x1"}",
143- }
142+ scopeMap: '{"tenantId":"t1","label":"l1","bla":"b1","naa":"n1","xxx":"x1"}' ,
143+ maxKeys: 4
144+ }"
144145 ` ) ;
145146 } ) ;
146147
@@ -288,11 +289,12 @@ describe("feature toggles test", () => {
288289
289290 expect ( loggerSpy . warning ) . toHaveBeenCalledTimes ( 1 ) ;
290291 expect ( loggerSpy . warning ) . toHaveBeenCalledWith ( expect . any ( VError ) ) ;
291- const [ loggedErr ] = loggerSpy . warning . mock . calls [ 0 ] ;
292- expect ( loggedErr ) . toMatchInlineSnapshot (
293- `[FeatureTogglesError: found invalid fallback values during initialization]`
294- ) ;
295- expect ( VError . info ( loggedErr ) ) . toMatchSnapshot ( ) ;
292+ expect ( outputFromErrorLogger ( loggerSpy . warning . mock . calls ) ) . toMatchInlineSnapshot ( `
293+ "FeatureTogglesError: found invalid fallback values during initialization
294+ {
295+ validationErrors: '[{"featureKey":"test/feature_b","errorMessage":"value null is not allowed"},{"featureKey":"test/feature_c","errorMessage":"value \\\\"{0}\\\\" has invalid type {1}, must be {2}","errorMessageValues":["1","string","number"]}]'
296+ }"
297+ ` ) ;
296298 expect ( loggerSpy . error ) . not . toHaveBeenCalled ( ) ;
297299 } ) ;
298300
@@ -1151,4 +1153,74 @@ describe("feature toggles test", () => {
11511153 expect ( loggerSpy . error ) . not . toHaveBeenCalled ( ) ;
11521154 } ) ;
11531155 } ) ;
1156+
1157+ describe ( "message handling" , ( ) => {
1158+ beforeEach ( async ( ) => {
1159+ await featureToggles . initializeFeatures ( { config : mockConfig } ) ;
1160+ } ) ;
1161+
1162+ it ( "empty array should be handled fine" , async ( ) => {
1163+ await redisWrapperMock . publishMessage ( featureToggles . __redisChannel , "[]" ) ;
1164+ expect ( loggerSpy . warning ) . toHaveBeenCalledTimes ( 0 ) ;
1165+ expect ( loggerSpy . error ) . toHaveBeenCalledTimes ( 0 ) ;
1166+ } ) ;
1167+
1168+ it ( "error in case message is not a serialized array" , async ( ) => {
1169+ await redisWrapperMock . publishMessage ( featureToggles . __redisChannel , "hello world!" ) ;
1170+ expect ( loggerSpy . warning ) . toHaveBeenCalledTimes ( 0 ) ;
1171+ expect ( loggerSpy . error ) . toHaveBeenCalledTimes ( 1 ) ;
1172+ expect ( outputFromErrorLogger ( loggerSpy . error . mock . calls ) ) . toMatchInlineSnapshot ( `
1173+ "FeatureTogglesError: error during message deserialization
1174+ { channel: 'feature-channel', message: 'hello world!' }"
1175+ ` ) ;
1176+ } ) ;
1177+
1178+ it ( "invalid change entries are ignored but logged" , async ( ) => {
1179+ const changeEntries = [ { featureKey : FEATURE . C , newValue : 10 } ] ;
1180+
1181+ expect ( featureToggles . getFeatureValue ( FEATURE . C ) ) . toBe ( mockFallbackValues [ FEATURE . C ] ) ;
1182+
1183+ await redisWrapperMock . publishMessage ( featureToggles . __redisChannel , JSON . stringify ( changeEntries ) ) ;
1184+
1185+ expect ( loggerSpy . warning ) . toHaveBeenCalledTimes ( 1 ) ;
1186+ expect ( outputFromErrorLogger ( loggerSpy . warning . mock . calls ) ) . toMatchInlineSnapshot ( `
1187+ "FeatureTogglesError: received and ignored invalid value from message
1188+ {
1189+ validationErrors: '[{"featureKey":"test/feature_c","scopeKey":"//","errorMessage":"value \\\\"{0}\\\\" has invalid type {1}, must be {2}","errorMessageValues":[10,"number","string"]}]'
1190+ }"
1191+ ` ) ;
1192+
1193+ expect ( featureToggles . getFeatureValue ( FEATURE . C ) ) . toBe ( mockFallbackValues [ FEATURE . C ] ) ;
1194+ } ) ;
1195+
1196+ it ( "all change entries are processed even if one fails" , async ( ) => {
1197+ const scopeMap = { tenant : "testing" } ;
1198+ const changeEntries = [
1199+ { featureKey : FEATURE . C , newValue : "modified" } ,
1200+ { } ,
1201+ null ,
1202+ "bla" ,
1203+ { featureKey : FEATURE . E , newValue : 9 , scopeMap } ,
1204+ ] ;
1205+
1206+ expect ( featureToggles . getFeatureValue ( FEATURE . C ) ) . toBe ( mockFallbackValues [ FEATURE . C ] ) ;
1207+ expect ( featureToggles . getFeatureValue ( FEATURE . E , scopeMap ) ) . toBe ( mockFallbackValues [ FEATURE . E ] ) ;
1208+
1209+ await redisWrapperMock . publishMessage ( featureToggles . __redisChannel , JSON . stringify ( changeEntries ) ) ;
1210+
1211+ expect ( loggerSpy . warning ) . toHaveBeenCalledTimes ( 3 ) ;
1212+ expect ( outputFromErrorLogger ( loggerSpy . warning . mock . calls ) ) . toMatchInlineSnapshot ( `
1213+ "FeatureTogglesError: received and ignored change entry
1214+ { changeEntry: '{}' }
1215+ FeatureTogglesError: received and ignored change entry
1216+ { changeEntry: 'null' }
1217+ FeatureTogglesError: received and ignored change entry
1218+ { changeEntry: '"bla"' }"
1219+ ` ) ;
1220+ expect ( loggerSpy . error ) . toHaveBeenCalledTimes ( 0 ) ;
1221+
1222+ expect ( featureToggles . getFeatureValue ( FEATURE . C ) ) . toMatchInlineSnapshot ( `"modified"` ) ;
1223+ expect ( featureToggles . getFeatureValue ( FEATURE . E , scopeMap ) ) . toMatchInlineSnapshot ( `9` ) ;
1224+ } ) ;
1225+ } ) ;
11541226} ) ;
0 commit comments