Skip to content

Commit 2f18c13

Browse files
committed
Merge branch 'main' into feat--eppo-assignment-logger
2 parents 01ee04f + 9b2a216 commit 2f18c13

23 files changed

+1921
-178
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eppo/js-client-sdk-common",
3-
"version": "4.9.0",
3+
"version": "4.12.1-alpha.0",
44
"description": "Common library for Eppo JavaScript SDKs (web, react native, and node)",
55
"main": "dist/index.js",
66
"files": [
@@ -53,7 +53,7 @@
5353
"eslint-config-prettier": "^10.0.1",
5454
"eslint-import-resolver-typescript": "^3.7.0",
5555
"eslint-plugin-import": "^2.25.4",
56-
"eslint-plugin-prettier": "5.0.0",
56+
"eslint-plugin-prettier": "5.2.3",
5757
"eslint-plugin-promise": "^7.2.1",
5858
"eslint-plugin-unused-imports": "^4.1.4",
5959
"jest": "^29.7.0",

src/api-endpoints.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
UFC_ENDPOINT,
44
BANDIT_ENDPOINT,
55
PRECOMPUTED_FLAGS_ENDPOINT,
6+
FLAG_OVERRIDES_KEY_VALIDATION_URL,
67
} from './constants';
78
import { IQueryParams, IQueryParamsWithSubject } from './http-client';
89

@@ -36,4 +37,8 @@ export default class ApiEndpoints {
3637
precomputedFlagsEndpoint(): URL {
3738
return this.endpoint(PRECOMPUTED_FLAGS_ENDPOINT);
3839
}
40+
41+
flagOverridesKeyValidationEndpoint(): URL {
42+
return this.endpoint(FLAG_OVERRIDES_KEY_VALIDATION_URL);
43+
}
3944
}

src/client/eppo-client-with-bandits.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ import ApiEndpoints from '../api-endpoints';
1212
import { IAssignmentEvent, IAssignmentLogger } from '../assignment-logger';
1313
import { BanditEvaluation, BanditEvaluator } from '../bandit-evaluator';
1414
import { IBanditEvent, IBanditLogger } from '../bandit-logger';
15+
import ConfigurationRequestor from '../configuration-requestor';
16+
import { MemoryOnlyConfigurationStore } from '../configuration-store/memory.store';
1517
import {
1618
IConfigurationWire,
1719
IPrecomputedConfiguration,
1820
IObfuscatedPrecomputedConfigurationResponse,
19-
} from '../configuration';
20-
import ConfigurationRequestor from '../configuration-requestor';
21-
import { MemoryOnlyConfigurationStore } from '../configuration-store/memory.store';
21+
} from '../configuration-wire-types';
2222
import { Evaluator, FlagEvaluation } from '../evaluator';
2323
import {
2424
AllocationEvaluationCode,
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { MemoryOnlyConfigurationStore } from '../configuration-store/memory.store';
2+
import { Flag, FormatEnum, ObfuscatedFlag, VariationType } from '../interfaces';
3+
import * as overrideValidatorModule from '../override-validator';
4+
5+
import EppoClient from './eppo-client';
6+
7+
describe('EppoClient', () => {
8+
const storage = new MemoryOnlyConfigurationStore<Flag | ObfuscatedFlag>();
9+
10+
function setUnobfuscatedFlagEntries(
11+
entries: Record<string, Flag | ObfuscatedFlag>,
12+
): Promise<boolean> {
13+
storage.setFormat(FormatEnum.SERVER);
14+
return storage.setEntries(entries);
15+
}
16+
17+
const flagKey = 'mock-flag';
18+
19+
const variationA = {
20+
key: 'a',
21+
value: 'variation-a',
22+
};
23+
24+
const variationB = {
25+
key: 'b',
26+
value: 'variation-b',
27+
};
28+
29+
const mockFlag: Flag = {
30+
key: flagKey,
31+
enabled: true,
32+
variationType: VariationType.STRING,
33+
variations: { a: variationA, b: variationB },
34+
allocations: [
35+
{
36+
key: 'allocation-a',
37+
rules: [],
38+
splits: [
39+
{
40+
shards: [],
41+
variationKey: 'a',
42+
},
43+
],
44+
doLog: true,
45+
},
46+
],
47+
totalShards: 10000,
48+
};
49+
50+
let client: EppoClient;
51+
let subjectKey: string;
52+
53+
beforeEach(async () => {
54+
await setUnobfuscatedFlagEntries({ [flagKey]: mockFlag });
55+
subjectKey = 'subject-10';
56+
client = new EppoClient({ flagConfigurationStore: storage });
57+
});
58+
59+
describe('parseOverrides', () => {
60+
it('should parse a valid payload', async () => {
61+
jest.spyOn(overrideValidatorModule, 'sendValidationRequest').mockResolvedValue(undefined);
62+
const result = await client.parseOverrides(
63+
JSON.stringify({
64+
browserExtensionKey: 'my-key',
65+
overrides: { [flagKey]: variationB },
66+
}),
67+
);
68+
expect(result).toEqual({ [flagKey]: variationB });
69+
});
70+
71+
it('should throw an error if the key is missing', async () => {
72+
jest.spyOn(overrideValidatorModule, 'sendValidationRequest').mockResolvedValue(undefined);
73+
expect(() =>
74+
client.parseOverrides(
75+
JSON.stringify({
76+
overrides: { [flagKey]: variationB },
77+
}),
78+
),
79+
).rejects.toThrow();
80+
});
81+
82+
it('should throw an error if the key is not a string', async () => {
83+
jest.spyOn(overrideValidatorModule, 'sendValidationRequest').mockResolvedValue(undefined);
84+
expect(() =>
85+
client.parseOverrides(
86+
JSON.stringify({
87+
browserExtensionKey: 123,
88+
overrides: { [flagKey]: variationB },
89+
}),
90+
),
91+
).rejects.toThrow();
92+
});
93+
94+
it('should throw an error if the overrides are not parseable', async () => {
95+
jest.spyOn(overrideValidatorModule, 'sendValidationRequest').mockResolvedValue(undefined);
96+
expect(() =>
97+
client.parseOverrides(`{
98+
browserExtensionKey: 'my-key',
99+
overrides: { [flagKey]: ,
100+
}`),
101+
).rejects.toThrow();
102+
});
103+
104+
it('should throw an error if overrides is not an object', async () => {
105+
jest.spyOn(overrideValidatorModule, 'sendValidationRequest').mockResolvedValue(undefined);
106+
expect(() =>
107+
client.parseOverrides(
108+
JSON.stringify({
109+
browserExtensionKey: 'my-key',
110+
overrides: false,
111+
}),
112+
),
113+
).rejects.toThrow();
114+
});
115+
116+
it('should throw an error if an invalid key is supplied', async () => {
117+
jest.spyOn(overrideValidatorModule, 'sendValidationRequest').mockImplementation(async () => {
118+
throw new Error(`Unable to authorize key`);
119+
});
120+
expect(() =>
121+
client.parseOverrides(
122+
JSON.stringify({
123+
browserExtensionKey: 'my-key',
124+
overrides: { [flagKey]: variationB },
125+
}),
126+
),
127+
).rejects.toThrow();
128+
});
129+
});
130+
131+
describe('withOverrides', () => {
132+
it('should create a new instance of EppoClient with specified overrides without affecting the original instance', () => {
133+
const clientWithOverrides = client.withOverrides({ [flagKey]: variationB });
134+
135+
expect(client.getStringAssignment(flagKey, subjectKey, {}, 'default')).toBe('variation-a');
136+
expect(clientWithOverrides.getStringAssignment(flagKey, subjectKey, {}, 'default')).toBe(
137+
'variation-b',
138+
);
139+
});
140+
});
141+
});

0 commit comments

Comments
 (0)