Skip to content

Commit

Permalink
fix: flag dependencies; tests (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
bgiori authored Mar 14, 2023
1 parent 9f8081e commit 8ae0118
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 28 deletions.
30 changes: 12 additions & 18 deletions packages/node/src/local/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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(
Expand All @@ -73,16 +78,19 @@ export class LocalEvaluationClient {
user: ExperimentUser,
flagKeys?: string[],
): Promise<Variants> {
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,
Expand Down Expand Up @@ -114,18 +122,4 @@ export class LocalEvaluationClient {
public stop(): void {
return this.poller.stop();
}

private async getFlagConfigs(flagKeys?: string[]): Promise<FlagConfig[]> {
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;
}
}
9 changes: 9 additions & 0 deletions packages/node/src/local/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<string, FlagConfig> {
const flagConfigsArray = JSON.parse(flagConfigs);
const flagConfigsRecord: Record<string, FlagConfig> = {};
Expand Down
21 changes: 11 additions & 10 deletions packages/node/test/local/benchmark.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand All @@ -20,18 +21,18 @@ 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 () => {
await client.evaluate(user, [flag]);
});
// 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();
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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
Expand Down
45 changes: 45 additions & 0 deletions packages/node/test/local/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});

0 comments on commit 8ae0118

Please sign in to comment.