Skip to content

Commit df7c1dc

Browse files
committed
Add test coverage for overrides with the precomputed client
1 parent 59e0dd7 commit df7c1dc

File tree

1 file changed

+248
-1
lines changed

1 file changed

+248
-1
lines changed

src/client/eppo-precomputed-client.spec.ts

Lines changed: 248 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ import {
1212
ensureNonContextualSubjectAttributes,
1313
} from '../attributes';
1414
import { IPrecomputedConfigurationResponse } from '../configuration';
15-
import { IConfigurationStore } from '../configuration-store/configuration-store';
15+
import { IConfigurationStore, ISyncStore } from '../configuration-store/configuration-store';
1616
import { MemoryOnlyConfigurationStore } from '../configuration-store/memory.store';
1717
import { DEFAULT_POLL_INTERVAL_MS, MAX_EVENT_QUEUE_SIZE, POLL_JITTER_PCT } from '../constants';
1818
import FetchHttpClient from '../http-client';
1919
import {
2020
FormatEnum,
2121
IObfuscatedPrecomputedBandit,
2222
PrecomputedFlag,
23+
Variation,
2324
VariationType,
2425
} from '../interfaces';
2526
import { decodeBase64, encodeBase64, getMD5Hash } from '../obfuscation';
@@ -1027,3 +1028,249 @@ describe('Precomputed Bandit Store', () => {
10271028
loggerWarnSpy.mockRestore();
10281029
});
10291030
});
1031+
1032+
describe('flag overrides', () => {
1033+
let client: EppoPrecomputedClient;
1034+
let mockLogger: IAssignmentLogger;
1035+
let overridesStore: ISyncStore<Variation>;
1036+
let flagStorage: IConfigurationStore<PrecomputedFlag>;
1037+
let subject: Subject;
1038+
1039+
const precomputedFlagKey = 'mock-flag';
1040+
const hashedPrecomputedFlagKey = getMD5Hash(precomputedFlagKey);
1041+
1042+
const mockPrecomputedFlag: PrecomputedFlag = {
1043+
flagKey: hashedPrecomputedFlagKey,
1044+
variationKey: encodeBase64('a'),
1045+
variationValue: encodeBase64('variation-a'),
1046+
allocationKey: encodeBase64('allocation-a'),
1047+
doLog: true,
1048+
variationType: VariationType.STRING,
1049+
extraLogging: {},
1050+
};
1051+
1052+
beforeEach(() => {
1053+
flagStorage = new MemoryOnlyConfigurationStore();
1054+
flagStorage.setEntries({ [hashedPrecomputedFlagKey]: mockPrecomputedFlag });
1055+
mockLogger = td.object<IAssignmentLogger>();
1056+
overridesStore = new MemoryOnlyConfigurationStore<Variation>();
1057+
subject = {
1058+
subjectKey: 'test-subject',
1059+
subjectAttributes: { attr1: 'value1' },
1060+
};
1061+
1062+
client = new EppoPrecomputedClient({
1063+
precomputedFlagStore: flagStorage,
1064+
subject,
1065+
overridesStore,
1066+
});
1067+
client.setAssignmentLogger(mockLogger);
1068+
});
1069+
1070+
it('returns override values for all supported types', () => {
1071+
overridesStore.setEntries({
1072+
'string-flag': {
1073+
key: 'override-variation',
1074+
value: 'override-string',
1075+
},
1076+
'boolean-flag': {
1077+
key: 'override-variation',
1078+
value: true,
1079+
},
1080+
'numeric-flag': {
1081+
key: 'override-variation',
1082+
value: 42.5,
1083+
},
1084+
'json-flag': {
1085+
key: 'override-variation',
1086+
value: '{"foo": "bar"}',
1087+
},
1088+
});
1089+
1090+
expect(client.getStringAssignment('string-flag', 'default')).toBe('override-string');
1091+
expect(client.getBooleanAssignment('boolean-flag', false)).toBe(true);
1092+
expect(client.getNumericAssignment('numeric-flag', 0)).toBe(42.5);
1093+
expect(client.getJSONAssignment('json-flag', {})).toEqual({ foo: 'bar' });
1094+
});
1095+
1096+
it('does not log assignments when override is applied', () => {
1097+
overridesStore.setEntries({
1098+
[precomputedFlagKey]: {
1099+
key: 'override-variation',
1100+
value: 'override-value',
1101+
},
1102+
});
1103+
1104+
client.getStringAssignment(precomputedFlagKey, 'default');
1105+
1106+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(0);
1107+
});
1108+
1109+
it('uses normal assignment when no override exists for flag', () => {
1110+
// Set override for a different flag
1111+
overridesStore.setEntries({
1112+
'other-flag': {
1113+
key: 'override-variation',
1114+
value: 'override-value',
1115+
},
1116+
});
1117+
1118+
const result = client.getStringAssignment(precomputedFlagKey, 'default');
1119+
1120+
// Should get the normal assignment value from mockPrecomputedFlag
1121+
expect(result).toBe('variation-a');
1122+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(1);
1123+
});
1124+
1125+
it('uses normal assignment when no overrides store is configured', () => {
1126+
// Create client without overrides store
1127+
const clientWithoutOverrides = new EppoPrecomputedClient({
1128+
precomputedFlagStore: flagStorage,
1129+
subject,
1130+
});
1131+
clientWithoutOverrides.setAssignmentLogger(mockLogger);
1132+
1133+
const result = clientWithoutOverrides.getStringAssignment(precomputedFlagKey, 'default');
1134+
1135+
// Should get the normal assignment value from mockPrecomputedFlag
1136+
expect(result).toBe('variation-a');
1137+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(1);
1138+
});
1139+
1140+
it('respects override after initial assignment without override', () => {
1141+
// First call without override
1142+
const initialAssignment = client.getStringAssignment(precomputedFlagKey, 'default');
1143+
expect(initialAssignment).toBe('variation-a');
1144+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(1);
1145+
1146+
// Set override and make second call
1147+
overridesStore.setEntries({
1148+
[precomputedFlagKey]: {
1149+
key: 'override-variation',
1150+
value: 'override-value',
1151+
},
1152+
});
1153+
1154+
const overriddenAssignment = client.getStringAssignment(precomputedFlagKey, 'default');
1155+
expect(overriddenAssignment).toBe('override-value');
1156+
// No additional logging should occur when using override
1157+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(1);
1158+
});
1159+
1160+
it('reverts to normal assignment after removing override', () => {
1161+
// Set initial override
1162+
overridesStore.setEntries({
1163+
[precomputedFlagKey]: {
1164+
key: 'override-variation',
1165+
value: 'override-value',
1166+
},
1167+
});
1168+
1169+
const overriddenAssignment = client.getStringAssignment(precomputedFlagKey, 'default');
1170+
expect(overriddenAssignment).toBe('override-value');
1171+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(0);
1172+
1173+
// Remove override and make second call
1174+
overridesStore.setEntries({});
1175+
1176+
const normalAssignment = client.getStringAssignment(precomputedFlagKey, 'default');
1177+
expect(normalAssignment).toBe('variation-a');
1178+
// Should log the normal assignment
1179+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(1);
1180+
});
1181+
1182+
describe('setOverridesStore', () => {
1183+
it('applies overrides after setting store', () => {
1184+
// Create client without overrides store
1185+
const clientWithoutOverrides = new EppoPrecomputedClient({
1186+
precomputedFlagStore: flagStorage,
1187+
subject,
1188+
});
1189+
clientWithoutOverrides.setAssignmentLogger(mockLogger);
1190+
1191+
// Initial call without override store
1192+
const initialAssignment = clientWithoutOverrides.getStringAssignment(
1193+
precomputedFlagKey,
1194+
'default',
1195+
);
1196+
expect(initialAssignment).toBe('variation-a');
1197+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(1);
1198+
1199+
// Set overrides store with override
1200+
overridesStore.setEntries({
1201+
[precomputedFlagKey]: {
1202+
key: 'override-variation',
1203+
value: 'override-value',
1204+
},
1205+
});
1206+
clientWithoutOverrides.setOverridesStore(overridesStore);
1207+
1208+
// Call after setting override store
1209+
const overriddenAssignment = clientWithoutOverrides.getStringAssignment(
1210+
precomputedFlagKey,
1211+
'default',
1212+
);
1213+
expect(overriddenAssignment).toBe('override-value');
1214+
// No additional logging should occur when using override
1215+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(1);
1216+
});
1217+
1218+
it('reverts to normal assignment after unsetting store', () => {
1219+
// Set initial override
1220+
overridesStore.setEntries({
1221+
[precomputedFlagKey]: {
1222+
key: 'override-variation',
1223+
value: 'override-value',
1224+
},
1225+
});
1226+
1227+
client.getStringAssignment(precomputedFlagKey, 'default');
1228+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(0);
1229+
1230+
// Unset overrides store
1231+
client.unsetOverridesStore();
1232+
1233+
const normalAssignment = client.getStringAssignment(precomputedFlagKey, 'default');
1234+
expect(normalAssignment).toBe('variation-a');
1235+
// Should log the normal assignment
1236+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(1);
1237+
});
1238+
1239+
it('switches between different override stores', () => {
1240+
// Create a second override store
1241+
const secondOverridesStore = new MemoryOnlyConfigurationStore<Variation>();
1242+
1243+
// Set up different overrides in each store
1244+
overridesStore.setEntries({
1245+
[precomputedFlagKey]: {
1246+
key: 'override-1',
1247+
value: 'value-1',
1248+
},
1249+
});
1250+
1251+
secondOverridesStore.setEntries({
1252+
[precomputedFlagKey]: {
1253+
key: 'override-2',
1254+
value: 'value-2',
1255+
},
1256+
});
1257+
1258+
// Start with first override store
1259+
const firstOverride = client.getStringAssignment(precomputedFlagKey, 'default');
1260+
expect(firstOverride).toBe('value-1');
1261+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(0);
1262+
1263+
// Switch to second override store
1264+
client.setOverridesStore(secondOverridesStore);
1265+
const secondOverride = client.getStringAssignment(precomputedFlagKey, 'default');
1266+
expect(secondOverride).toBe('value-2');
1267+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(0);
1268+
1269+
// Switch back to first override store
1270+
client.setOverridesStore(overridesStore);
1271+
const backToFirst = client.getStringAssignment(precomputedFlagKey, 'default');
1272+
expect(backToFirst).toBe('value-1');
1273+
expect(td.explain(mockLogger.logAssignment).callCount).toBe(0);
1274+
});
1275+
});
1276+
});

0 commit comments

Comments
 (0)