Skip to content

Commit 50fb1ff

Browse files
committed
Test format in response
1 parent f1972ba commit 50fb1ff

File tree

2 files changed

+170
-2
lines changed

2 files changed

+170
-2
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import ApiEndpoints from '../api-endpoints';
2+
import { logger } from '../application-logger';
3+
import { IAssignmentEvent, IAssignmentLogger } from '../assignment-logger';
4+
import { BanditEvaluator } from '../bandit-evaluator';
5+
import { IBanditEvent, IBanditLogger } from '../bandit-logger';
6+
import { AssignmentCache } from '../cache/abstract-assignment-cache';
7+
import ConfigurationRequestor from '../configuration-requestor';
8+
import { IConfigurationStore } from '../configuration-store/configuration-store';
9+
import {
10+
DEFAULT_INITIAL_CONFIG_REQUEST_RETRIES,
11+
DEFAULT_POLL_CONFIG_REQUEST_RETRIES,
12+
DEFAULT_REQUEST_TIMEOUT_MS,
13+
DEFAULT_POLL_INTERVAL_MS,
14+
} from '../constants';
15+
import { Evaluator } from '../evaluator';
16+
import { IFlagEvaluationDetails } from '../flag-evaluation-details-builder';
17+
import FetchHttpClient from '../http-client';
18+
import { BanditParameters, BanditVariation, Flag, ObfuscatedFlag, Variation } from '../interfaces';
19+
import initPoller, { IPoller } from '../poller';
20+
21+
export interface IAssignmentDetails<T extends Variation['value'] | object> {
22+
variation: T;
23+
action: string | null;
24+
evaluationDetails: IFlagEvaluationDetails;
25+
}
26+
27+
export type FlagConfigurationRequestParameters = {
28+
apiKey: string;
29+
sdkVersion: string;
30+
sdkName: string;
31+
baseUrl?: string;
32+
requestTimeoutMs?: number;
33+
pollingIntervalMs?: number;
34+
numInitialRequestRetries?: number;
35+
numPollRequestRetries?: number;
36+
pollAfterSuccessfulInitialization?: boolean;
37+
pollAfterFailedInitialization?: boolean;
38+
throwOnFailedInitialization?: boolean;
39+
skipInitialPoll?: boolean;
40+
};
41+
42+
export interface IContainerExperiment<T> {
43+
flagKey: string;
44+
controlVariationEntry: T;
45+
treatmentVariationEntries: Array<T>;
46+
}
47+
48+
export default class EppoClient {
49+
private readonly queuedAssignmentEvents: IAssignmentEvent[] = [];
50+
private assignmentLogger?: IAssignmentLogger;
51+
private readonly queuedBanditEvents: IBanditEvent[] = [];
52+
private banditLogger?: IBanditLogger;
53+
private isGracefulFailureMode = true;
54+
private assignmentCache?: AssignmentCache;
55+
private banditAssignmentCache?: AssignmentCache;
56+
private requestPoller?: IPoller;
57+
private readonly evaluator = new Evaluator();
58+
private readonly banditEvaluator = new BanditEvaluator();
59+
60+
constructor(
61+
private flagConfigurationStore: IConfigurationStore<Flag | ObfuscatedFlag>,
62+
private banditVariationConfigurationStore?: IConfigurationStore<BanditVariation[]>,
63+
private banditModelConfigurationStore?: IConfigurationStore<BanditParameters>,
64+
private configurationRequestParameters?: FlagConfigurationRequestParameters,
65+
) {}
66+
67+
public setConfigurationRequestParameters(
68+
configurationRequestParameters: FlagConfigurationRequestParameters,
69+
) {
70+
this.configurationRequestParameters = configurationRequestParameters;
71+
}
72+
73+
public setFlagConfigurationStore(
74+
flagConfigurationStore: IConfigurationStore<Flag | ObfuscatedFlag>,
75+
) {
76+
this.flagConfigurationStore = flagConfigurationStore;
77+
}
78+
79+
public setBanditVariationConfigurationStore(
80+
banditVariationConfigurationStore: IConfigurationStore<BanditVariation[]>,
81+
) {
82+
this.banditVariationConfigurationStore = banditVariationConfigurationStore;
83+
}
84+
85+
public setBanditModelConfigurationStore(
86+
banditModelConfigurationStore: IConfigurationStore<BanditParameters>,
87+
) {
88+
this.banditModelConfigurationStore = banditModelConfigurationStore;
89+
}
90+
91+
public setIsObfuscated(isObfuscated: boolean) {
92+
this.isObfuscated = isObfuscated;
93+
}
94+
95+
public async fetchFlagConfigurations() {
96+
if (!this.configurationRequestParameters) {
97+
throw new Error(
98+
'Eppo SDK unable to fetch flag configurations without configuration request parameters',
99+
);
100+
}
101+
// if fetchFlagConfigurations() was previously called, stop any polling process from that call
102+
this.requestPoller?.stop();
103+
104+
const {
105+
apiKey,
106+
sdkName,
107+
sdkVersion,
108+
baseUrl, // Default is set in ApiEndpoints constructor if undefined
109+
requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS,
110+
numInitialRequestRetries = DEFAULT_INITIAL_CONFIG_REQUEST_RETRIES,
111+
numPollRequestRetries = DEFAULT_POLL_CONFIG_REQUEST_RETRIES,
112+
pollAfterSuccessfulInitialization = false,
113+
pollAfterFailedInitialization = false,
114+
throwOnFailedInitialization = false,
115+
skipInitialPoll = false,
116+
} = this.configurationRequestParameters;
117+
118+
let { pollingIntervalMs = DEFAULT_POLL_INTERVAL_MS } = this.configurationRequestParameters;
119+
if (pollingIntervalMs <= 0) {
120+
logger.error('pollingIntervalMs must be greater than 0. Using default');
121+
pollingIntervalMs = DEFAULT_POLL_INTERVAL_MS;
122+
}
123+
124+
// todo: Inject the chain of dependencies below
125+
const apiEndpoints = new ApiEndpoints({
126+
baseUrl,
127+
queryParams: { apiKey, sdkName, sdkVersion },
128+
});
129+
const httpClient = new FetchHttpClient(apiEndpoints, requestTimeoutMs);
130+
const configurationRequestor = new ConfigurationRequestor(
131+
httpClient,
132+
this.flagConfigurationStore,
133+
this.banditVariationConfigurationStore ?? null,
134+
this.banditModelConfigurationStore ?? null,
135+
);
136+
137+
const pollingCallback = async () => {
138+
if (await this.flagConfigurationStore.isExpired()) {
139+
return configurationRequestor.fetchAndStoreConfigurations();
140+
}
141+
};
142+
143+
this.requestPoller = initPoller(pollingIntervalMs, pollingCallback, {
144+
maxStartRetries: numInitialRequestRetries,
145+
maxPollRetries: numPollRequestRetries,
146+
pollAfterSuccessfulStart: pollAfterSuccessfulInitialization,
147+
pollAfterFailedStart: pollAfterFailedInitialization,
148+
errorOnFailedStart: throwOnFailedInitialization,
149+
skipInitialPoll: skipInitialPoll,
150+
});
151+
152+
await this.requestPoller.start();
153+
}
154+
155+
public stopPolling() {
156+
if (this.requestPoller) {
157+
this.requestPoller.stop();
158+
}
159+
}
160+
}

src/precomputed-requestor.spec.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ const MOCK_PRECOMPUTED_RESPONSE = {
2424
doLog: true,
2525
},
2626
},
27-
environment: 'production',
27+
environment: {
28+
name: 'production',
29+
},
30+
format: 'PRECOMPUTED',
2831
createdAt: '2024-03-20T00:00:00Z',
2932
};
3033

@@ -89,7 +92,12 @@ describe('PrecomputedRequestor', () => {
8992
expect(flag2?.extraLogging).toEqual({});
9093
expect(flag2?.doLog).toBe(true);
9194

92-
expect(precomputedFlagStore.getEnvironment()).toBe('production');
95+
// TODO: create a method get format from the response
96+
expect(fetchSpy).toHaveBeenCalledTimes(1);
97+
const response = await fetchSpy.mock.results[0].value.json();
98+
expect(response.format).toBe('PRECOMPUTED');
99+
100+
expect(precomputedFlagStore.getEnvironment()).toBe({ name: 'production' });
93101
expect(precomputedFlagStore.getConfigPublishedAt()).toBe('2024-03-20T00:00:00Z');
94102
});
95103

0 commit comments

Comments
 (0)