From 8ae01186c0ada351f008e3d17e827cc9de787570 Mon Sep 17 00:00:00 2001 From: Brian Giori Date: Mon, 13 Mar 2023 18:47:53 -0700 Subject: [PATCH] fix: flag dependencies; tests (#14) --- packages/node/src/local/client.ts | 30 ++++++--------- packages/node/src/local/fetcher.ts | 9 +++++ packages/node/test/local/benchmark.test.ts | 21 +++++----- packages/node/test/local/client.test.ts | 45 ++++++++++++++++++++++ 4 files changed, 77 insertions(+), 28 deletions(-) diff --git a/packages/node/src/local/client.ts b/packages/node/src/local/client.ts index e829b11..318f469 100644 --- a/packages/node/src/local/client.ts +++ b/packages/node/src/local/client.ts @@ -24,6 +24,7 @@ export class LocalEvaluationClient { private readonly logger: Logger; private readonly config: LocalEvaluationConfig; private readonly poller: FlagConfigPoller; + private flags: FlagConfig[]; /** * Directly access the client's flag config cache. @@ -47,6 +48,10 @@ export class LocalEvaluationClient { this.config.serverUrl, this.config.debug, ); + // We no longer use the flag config cache for accessing variants. + fetcher.setRawReceiver((flags: string) => { + this.flags = JSON.parse(flags); + }); this.cache = flagConfigCache; this.logger = new ConsoleLogger(this.config.debug); this.poller = new FlagConfigPoller( @@ -73,16 +78,19 @@ export class LocalEvaluationClient { user: ExperimentUser, flagKeys?: string[], ): Promise { - const flagConfigs = await this.getFlagConfigs(flagKeys); this.logger.debug( '[Experiment] evaluate - user:', user, - 'flagConfigs:', - flagConfigs, + 'flags:', + this.flags, ); - const results: Results = evaluation.evaluate(flagConfigs, user); + const results: Results = evaluation.evaluate(this.flags, user); const variants: Variants = {}; + const filter = flagKeys && flagKeys.length > 0; for (const flagKey in results) { + if (filter && !flagKeys.includes(flagKey)) { + continue; + } const flagResult = results[flagKey]; variants[flagKey] = { value: flagResult.value, @@ -114,18 +122,4 @@ export class LocalEvaluationClient { public stop(): void { return this.poller.stop(); } - - private async getFlagConfigs(flagKeys?: string[]): Promise { - if (!flagKeys) { - return Object.values(await this.cache.getAll()); - } - const result: FlagConfig[] = []; - for (const key of flagKeys) { - const flagConfig = await this.cache.get(key); - if (flagConfig) { - result.push(flagConfig); - } - } - return result; - } } diff --git a/packages/node/src/local/fetcher.ts b/packages/node/src/local/fetcher.ts index 9269f2c..e5a8d4f 100644 --- a/packages/node/src/local/fetcher.ts +++ b/packages/node/src/local/fetcher.ts @@ -14,6 +14,8 @@ export class FlagConfigFetcher { private readonly serverUrl: string; private readonly httpClient: HttpClient; + private receiver: (string) => void; + public constructor( apiKey: string, httpClient: HttpClient, @@ -56,9 +58,16 @@ export class FlagConfigFetcher { ); } this.logger.debug(`[Experiment] Got flag configs: ${response.body}`); + if (this.receiver) { + this.receiver(response.body); + } return this.parse(response.body); } + public setRawReceiver(rawReceiver: (flags: string) => void): void { + this.receiver = rawReceiver; + } + private parse(flagConfigs: string): Record { const flagConfigsArray = JSON.parse(flagConfigs); const flagConfigsRecord: Record = {}; diff --git a/packages/node/test/local/benchmark.test.ts b/packages/node/test/local/benchmark.test.ts index 7f1a7cf..78dfcb5 100644 --- a/packages/node/test/local/benchmark.test.ts +++ b/packages/node/test/local/benchmark.test.ts @@ -3,12 +3,13 @@ import { ExperimentUser } from 'src/types/user'; import { measure } from './util/performance'; -const apiKey = 'server-5G5HQL3jUIPXWaJBTgAvDFHy277srxSg'; +const apiKey = 'server-Ed2doNl5YOblB5lRavQ9toj02arvHpMj'; -const client = Experiment.initializeLocal(apiKey); +const client = Experiment.initializeLocal(apiKey, { debug: false }); beforeAll(async () => { await client.start(); + await client.evaluate({ user_id: 'u', device_id: 'd' }); }); afterAll(() => { @@ -20,7 +21,7 @@ afterAll(() => { * with respect to CPU */ -test('ExperimentClient.evaluate benchmark, 1 flag < 10ms', async () => { +test('ExperimentClient.evaluate benchmark, 1 flag < 20ms', async () => { const user = randomExperimentUser(); const flag = randomBenchmarkFlag(); const duration = await measure(async () => { @@ -28,10 +29,10 @@ test('ExperimentClient.evaluate benchmark, 1 flag < 10ms', async () => { }); // eslint-disable-next-line no-console console.log('1 flag: ', duration, 'ms'); - expect(duration).toBeLessThan(10); + expect(duration).toBeLessThan(20); }); -test('ExperimentClient.evaluate benchmark, 10 flags < 10ms', async () => { +test('ExperimentClient.evaluate benchmark, 10 flags < 20ms', async () => { let total = 0; for (let i = 0; i < 10; i++) { const user = randomExperimentUser(); @@ -43,10 +44,10 @@ test('ExperimentClient.evaluate benchmark, 10 flags < 10ms', async () => { } // eslint-disable-next-line no-console console.log('10 flag: ', total, 'ms'); - expect(total).toBeLessThan(10); + expect(total).toBeLessThan(20); }); -test('ExperimentClient.evaluate benchmark, 100 flags < 100ms', async () => { +test('ExperimentClient.evaluate benchmark, 100 flags < 200ms', async () => { let total = 0; for (let i = 0; i < 100; i++) { const user = randomExperimentUser(); @@ -58,10 +59,10 @@ test('ExperimentClient.evaluate benchmark, 100 flags < 100ms', async () => { } // eslint-disable-next-line no-console console.log('100 flag: ', total, 'ms'); - expect(total).toBeLessThan(100); + expect(total).toBeLessThan(200); }); -test('ExperimentClient.evaluate benchmark, 1000 flags < 1000ms', async () => { +test('ExperimentClient.evaluate benchmark, 1000 flags < 2000ms', async () => { let total = 0; for (let i = 0; i < 1000; i++) { const user = randomExperimentUser(); @@ -73,7 +74,7 @@ test('ExperimentClient.evaluate benchmark, 1000 flags < 1000ms', async () => { } // eslint-disable-next-line no-console console.log('1000 flag: ', total, 'ms'); - expect(total).toBeLessThan(1000); + expect(total).toBeLessThan(2000); }); // Utilities diff --git a/packages/node/test/local/client.test.ts b/packages/node/test/local/client.test.ts index f63d601..6be8a7f 100644 --- a/packages/node/test/local/client.test.ts +++ b/packages/node/test/local/client.test.ts @@ -28,3 +28,48 @@ test('ExperimentClient.evaluate one flag, success', async () => { const variant = variants['sdk-local-evaluation-ci-test']; expect(variant).toEqual({ value: 'on', payload: 'payload' }); }); + +test('ExperimentClient.evaluate with dependencies, no flag keys, success', async () => { + const variants = await client.evaluate({ + user_id: 'user_id', + device_id: 'device_id', + }); + const variant = variants['sdk-ci-local-dependencies-test']; + expect(variant).toEqual({ value: 'control', payload: null }); +}); + +test('ExperimentClient.evaluate with dependencies, with flag keys, success', async () => { + const variants = await client.evaluate( + { + user_id: 'user_id', + device_id: 'device_id', + }, + ['sdk-ci-local-dependencies-test'], + ); + const variant = variants['sdk-ci-local-dependencies-test']; + expect(variant).toEqual({ value: 'control', payload: null }); +}); + +test('ExperimentClient.evaluate with dependencies, with unknown flag keys, no variant', async () => { + const variants = await client.evaluate( + { + user_id: 'user_id', + device_id: 'device_id', + }, + ['does-not-exist'], + ); + const variant = variants['sdk-ci-local-dependencies-test']; + expect(variant).toBeUndefined(); +}); + +test('ExperimentClient.evaluate with dependencies, variant held out', async () => { + const variants = await client.evaluate({ + user_id: 'user_id', + device_id: 'device_id', + }); + const variant = variants['sdk-ci-local-dependencies-test-holdout']; + expect(variant).toBeUndefined(); + expect( + client.cache.get('sdk-ci-local-dependencies-test-holdout'), + ).toBeDefined(); +});