@@ -13,10 +13,13 @@ import {
13
13
validateTestAssignments ,
14
14
} from '../../test/testHelpers' ;
15
15
import { IAssignmentLogger } from '../assignment-logger' ;
16
+ import { IConfigurationWire } from '../configuration' ;
16
17
import { IConfigurationStore } from '../configuration-store/configuration-store' ;
17
18
import { MemoryOnlyConfigurationStore } from '../configuration-store/memory.store' ;
18
19
import { MAX_EVENT_QUEUE_SIZE , DEFAULT_POLL_INTERVAL_MS , POLL_JITTER_PCT } from '../constants' ;
20
+ import { decodePrecomputedFlag } from '../decoding' ;
19
21
import { Flag , ObfuscatedFlag , VariationType } from '../interfaces' ;
22
+ import { setSaltOverrideForTests } from '../obfuscation' ;
20
23
import { AttributeType } from '../types' ;
21
24
22
25
import EppoClient , { FlagConfigurationRequestParameters , checkTypeMatch } from './eppo-client' ;
@@ -177,6 +180,114 @@ describe('EppoClient E2E test', () => {
177
180
} ) ;
178
181
} ) ;
179
182
183
+ describe ( 'precomputed flags' , ( ) => {
184
+ beforeAll ( ( ) => {
185
+ storage . setEntries ( {
186
+ [ flagKey ] : mockFlag ,
187
+ disabledFlag : { ...mockFlag , enabled : false } ,
188
+ anotherFlag : {
189
+ ...mockFlag ,
190
+ allocations : [
191
+ {
192
+ key : 'allocation-b' ,
193
+ rules : [ ] ,
194
+ splits : [
195
+ {
196
+ shards : [ ] ,
197
+ variationKey : 'b' ,
198
+ } ,
199
+ ] ,
200
+ doLog : true ,
201
+ } ,
202
+ ] ,
203
+ } ,
204
+ } ) ;
205
+ } ) ;
206
+
207
+ let client : EppoClient ;
208
+ beforeEach ( ( ) => {
209
+ client = new EppoClient ( { flagConfigurationStore : storage } ) ;
210
+ } ) ;
211
+
212
+ afterEach ( ( ) => {
213
+ setSaltOverrideForTests ( null ) ;
214
+ } ) ;
215
+
216
+ it ( 'skips disabled flags' , ( ) => {
217
+ const encodedPrecomputedWire = client . getPrecomputedAssignments ( 'subject' , { } ) ;
218
+ const { precomputed } = JSON . parse ( encodedPrecomputedWire ) as IConfigurationWire ;
219
+ if ( ! precomputed ) {
220
+ fail ( 'Precomputed data not in Configuration response' ) ;
221
+ }
222
+ const precomputedResponse = JSON . parse ( precomputed . response ) ;
223
+
224
+ expect ( precomputedResponse ) . toBeTruthy ( ) ;
225
+ const precomputedFlags = precomputedResponse ?. flags ?? { } ;
226
+ expect ( Object . keys ( precomputedFlags ) ) . toContain ( 'anotherFlag' ) ;
227
+ expect ( Object . keys ( precomputedFlags ) ) . toContain ( flagKey ) ;
228
+ expect ( Object . keys ( precomputedFlags ) ) . not . toContain ( 'disabledFlag' ) ;
229
+ } ) ;
230
+
231
+ it ( 'evaluates and returns assignments' , ( ) => {
232
+ const encodedPrecomputedWire = client . getPrecomputedAssignments ( 'subject' , { } ) ;
233
+ const { precomputed } = JSON . parse ( encodedPrecomputedWire ) as IConfigurationWire ;
234
+ if ( ! precomputed ) {
235
+ fail ( 'Precomputed data not in Configuration response' ) ;
236
+ }
237
+ const precomputedResponse = JSON . parse ( precomputed . response ) ;
238
+
239
+ expect ( precomputedResponse ) . toBeTruthy ( ) ;
240
+ const precomputedFlags = precomputedResponse ?. flags ?? { } ;
241
+ const firstFlag = precomputedFlags [ flagKey ] ;
242
+ const secondFlag = precomputedFlags [ 'anotherFlag' ] ;
243
+ expect ( firstFlag . variationValue ) . toEqual ( 'variation-a' ) ;
244
+ expect ( secondFlag . variationValue ) . toEqual ( 'variation-b' ) ;
245
+ } ) ;
246
+
247
+ it ( 'obfuscates assignments' , ( ) => {
248
+ // Use a known salt to produce deterministic hashes
249
+ setSaltOverrideForTests ( {
250
+ base64String : 'BzURTg==' ,
251
+ saltString : '0735114e' ,
252
+ bytes : new Uint8Array ( [ 7 , 53 , 17 , 78 ] ) ,
253
+ } ) ;
254
+
255
+ const encodedPrecomputedWire = client . getPrecomputedAssignments ( 'subject' , { } , true ) ;
256
+ const { precomputed } = JSON . parse ( encodedPrecomputedWire ) as IConfigurationWire ;
257
+ if ( ! precomputed ) {
258
+ fail ( 'Precomputed data not in Configuration response' ) ;
259
+ }
260
+ const precomputedResponse = JSON . parse ( precomputed . response ) ;
261
+
262
+ expect ( precomputedResponse ) . toBeTruthy ( ) ;
263
+ expect ( precomputedResponse . salt ) . toEqual ( 'BzURTg==' ) ;
264
+
265
+ const precomputedFlags = precomputedResponse ?. flags ?? { } ;
266
+ expect ( Object . keys ( precomputedFlags ) ) . toContain ( 'ddc24ede545855b9bbae82cfec6a83a1' ) ; // flagKey, md5 hashed
267
+ expect ( Object . keys ( precomputedFlags ) ) . toContain ( '2b439e5a0104d62400dc44c34230f6f2' ) ; // 'anotherFlag', md5 hashed
268
+
269
+ const decodedFirstFlag = decodePrecomputedFlag (
270
+ precomputedFlags [ 'ddc24ede545855b9bbae82cfec6a83a1' ] ,
271
+ ) ;
272
+ expect ( decodedFirstFlag . flagKey ) . toEqual ( 'ddc24ede545855b9bbae82cfec6a83a1' ) ;
273
+ expect ( decodedFirstFlag . variationType ) . toEqual ( VariationType . STRING ) ;
274
+ expect ( decodedFirstFlag . variationKey ) . toEqual ( 'a' ) ;
275
+ expect ( decodedFirstFlag . variationValue ) . toEqual ( 'variation-a' ) ;
276
+ expect ( decodedFirstFlag . doLog ) . toEqual ( true ) ;
277
+ expect ( decodedFirstFlag . extraLogging ) . toEqual ( { } ) ;
278
+
279
+ const decodedSecondFlag = decodePrecomputedFlag (
280
+ precomputedFlags [ '2b439e5a0104d62400dc44c34230f6f2' ] ,
281
+ ) ;
282
+ expect ( decodedSecondFlag . flagKey ) . toEqual ( '2b439e5a0104d62400dc44c34230f6f2' ) ;
283
+ expect ( decodedSecondFlag . variationType ) . toEqual ( VariationType . STRING ) ;
284
+ expect ( decodedSecondFlag . variationKey ) . toEqual ( 'b' ) ;
285
+ expect ( decodedSecondFlag . variationValue ) . toEqual ( 'variation-b' ) ;
286
+ expect ( decodedSecondFlag . doLog ) . toEqual ( true ) ;
287
+ expect ( decodedSecondFlag . extraLogging ) . toEqual ( { } ) ;
288
+ } ) ;
289
+ } ) ;
290
+
180
291
describe ( 'UFC Shared Test Cases' , ( ) => {
181
292
const testCases = testCasesByFileName < IAssignmentTestCase > ( ASSIGNMENT_TEST_DATA_DIR ) ;
182
293
0 commit comments