Skip to content

Commit 5e5eac9

Browse files
committed
fixup: add gherkin for flagd-web adn minor improvements
Signed-off-by: Simon Schrottner <[email protected]>
1 parent 094cb19 commit 5e5eac9

File tree

8 files changed

+383
-20
lines changed

8 files changed

+383
-20
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export const FLAGD_NAME = 'flagd-web';
2+
export const E2E_CLIENT_NAME = 'e2e';
3+
4+
export const IMAGE_VERSION = 'v0.5.13';
5+
6+
export function getGherkinTestPath(file: string, modulePath = 'test-harness/gherkin/'): string {
7+
// TODO: find a way to resolve this in a generic manner - currently this works, because of the file structure
8+
return `<rootdir>/../../../../../shared/flagd-core/${modulePath}${file}`;
9+
}
10+
11+
export const GHERKIN_EVALUATION_FEATURE = getGherkinTestPath(
12+
'flagd.feature'
13+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './constants';
2+
export * from './step-definitions';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
import { StepDefinitions } from 'jest-cucumber';
2+
import {
3+
EvaluationContext,
4+
EvaluationDetails,
5+
FlagValue,
6+
JsonObject,
7+
OpenFeature,
8+
ProviderEvents,
9+
StandardResolutionReasons,
10+
} from '@openfeature/web-sdk';
11+
import { E2E_CLIENT_NAME } from '../constants';
12+
13+
export const flagStepDefinitions: StepDefinitions = ({ given, and, when, then }) => {
14+
let flagKey: string;
15+
let value: FlagValue;
16+
let context: EvaluationContext = {};
17+
let details: EvaluationDetails<FlagValue>;
18+
let fallback: FlagValue;
19+
let flagsChanged: string[];
20+
21+
const client = OpenFeature.getClient(E2E_CLIENT_NAME);
22+
23+
beforeAll((done) => {
24+
client.addHandler(ProviderEvents.Ready, () => {
25+
done();
26+
});
27+
});
28+
29+
beforeEach(() => {
30+
context = {};
31+
});
32+
33+
given('a provider is registered', () => undefined);
34+
given('a flagd provider is set', () => undefined);
35+
36+
when(
37+
/^a boolean flag with key "(.*)" is evaluated with default value "(.*)"$/,
38+
async (key: string, defaultValue: string) => {
39+
flagKey = key;
40+
fallback = defaultValue;
41+
value = await client.getBooleanValue(key, defaultValue === 'true');
42+
},
43+
);
44+
45+
then(/^the resolved boolean value should be "(.*)"$/, (expectedValue: string) => {
46+
expect(value).toEqual(expectedValue === 'true');
47+
});
48+
49+
when(
50+
/^a string flag with key "(.*)" is evaluated with default value "(.*)"$/,
51+
async (key: string, defaultValue: string) => {
52+
flagKey = key;
53+
fallback = defaultValue;
54+
value = await client.getStringValue(key, defaultValue);
55+
},
56+
);
57+
58+
then(/^the resolved string value should be "(.*)"$/, (expectedValue: string) => {
59+
expect(value).toEqual(expectedValue);
60+
});
61+
62+
when(
63+
/^an integer flag with key "(.*)" is evaluated with default value (\d+)$/,
64+
async (key: string, defaultValue: string) => {
65+
flagKey = key;
66+
fallback = Number(defaultValue);
67+
value = await client.getNumberValue(key, Number.parseInt(defaultValue));
68+
},
69+
);
70+
71+
then(/^the resolved integer value should be (\d+)$/, (expectedValue: string) => {
72+
expect(value).toEqual(Number.parseInt(expectedValue));
73+
});
74+
75+
when(
76+
/^a float flag with key "(.*)" is evaluated with default value (\d+\.?\d*)$/,
77+
async (key: string, defaultValue: string) => {
78+
flagKey = key;
79+
fallback = Number(defaultValue);
80+
value = await client.getNumberValue(key, Number.parseFloat(defaultValue));
81+
},
82+
);
83+
84+
then(/^the resolved float value should be (\d+\.?\d*)$/, (expectedValue: string) => {
85+
expect(value).toEqual(Number.parseFloat(expectedValue));
86+
});
87+
88+
when(/^an object flag with key "(.*)" is evaluated with a null default value$/, async (key: string) => {
89+
const defaultValue = {};
90+
flagKey = key;
91+
fallback = '';
92+
value = await client.getObjectValue(key, defaultValue);
93+
});
94+
95+
then(
96+
/^the resolved object value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/,
97+
(field1: string, field2: string, field3: string, boolValue: string, stringValue: string, intValue: string) => {
98+
const jsonObject = value as JsonObject;
99+
expect(jsonObject[field1]).toEqual(boolValue === 'true');
100+
expect(jsonObject[field2]).toEqual(stringValue);
101+
expect(jsonObject[field3]).toEqual(Number.parseInt(intValue));
102+
},
103+
);
104+
105+
when(
106+
/^a boolean flag with key "(.*)" is evaluated with details and default value "(.*)"$/,
107+
async (key: string, defaultValue: string) => {
108+
flagKey = key;
109+
fallback = defaultValue;
110+
details = await client.getBooleanDetails(key, defaultValue === 'true');
111+
},
112+
);
113+
114+
then(
115+
/^the resolved boolean details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/,
116+
(expectedValue: string, expectedVariant: string, expectedReason: string) => {
117+
expect(details).toBeDefined();
118+
expect(details.value).toEqual(expectedValue === 'true');
119+
expect(details.variant).toEqual(expectedVariant);
120+
expect(details.reason).toEqual(expectedReason);
121+
},
122+
);
123+
124+
when(
125+
/^a string flag with key "(.*)" is evaluated with details and default value "(.*)"$/,
126+
async (key: string, defaultValue: string) => {
127+
flagKey = key;
128+
fallback = defaultValue;
129+
details = await client.getStringDetails(key, defaultValue);
130+
},
131+
);
132+
133+
then(
134+
/^the resolved string details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/,
135+
(expectedValue: string, expectedVariant: string, expectedReason: string) => {
136+
expect(details).toBeDefined();
137+
expect(details.value).toEqual(expectedValue);
138+
expect(details.variant).toEqual(expectedVariant);
139+
expect(details.reason).toEqual(expectedReason);
140+
},
141+
);
142+
143+
when(
144+
/^an integer flag with key "(.*)" is evaluated with details and default value (\d+)$/,
145+
async (key: string, defaultValue: string) => {
146+
flagKey = key;
147+
fallback = defaultValue;
148+
details = await client.getNumberDetails(key, Number.parseInt(defaultValue));
149+
},
150+
);
151+
152+
then(
153+
/^the resolved integer details value should be (\d+), the variant should be "(.*)", and the reason should be "(.*)"$/,
154+
(expectedValue: string, expectedVariant: string, expectedReason: string) => {
155+
expect(details).toBeDefined();
156+
expect(details.value).toEqual(Number.parseInt(expectedValue));
157+
expect(details.variant).toEqual(expectedVariant);
158+
expect(details.reason).toEqual(expectedReason);
159+
},
160+
);
161+
162+
when(
163+
/^a float flag with key "(.*)" is evaluated with details and default value (\d+\.?\d*)$/,
164+
async (key: string, defaultValue: string) => {
165+
flagKey = key;
166+
fallback = defaultValue;
167+
details = await client.getNumberDetails(key, Number.parseFloat(defaultValue));
168+
},
169+
);
170+
171+
then(
172+
/^the resolved float details value should be (\d+\.?\d*), the variant should be "(.*)", and the reason should be "(.*)"$/,
173+
(expectedValue: string, expectedVariant: string, expectedReason: string) => {
174+
expect(details).toBeDefined();
175+
expect(details.value).toEqual(Number.parseFloat(expectedValue));
176+
expect(details.variant).toEqual(expectedVariant);
177+
expect(details.reason).toEqual(expectedReason);
178+
},
179+
);
180+
181+
when(/^an object flag with key "(.*)" is evaluated with details and a null default value$/, async (key: string) => {
182+
flagKey = key;
183+
fallback = {};
184+
details = await client.getObjectDetails(key, {});
185+
});
186+
187+
then(
188+
/^the resolved object details value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/,
189+
(field1: string, field2: string, field3: string, boolValue: string, stringValue: string, intValue: string) => {
190+
expect(details).toBeDefined();
191+
const jsonObject = details.value as JsonObject;
192+
193+
expect(jsonObject[field1]).toEqual(boolValue === 'true');
194+
expect(jsonObject[field2]).toEqual(stringValue);
195+
expect(jsonObject[field3]).toEqual(Number.parseInt(intValue));
196+
},
197+
);
198+
199+
and(
200+
/^the variant should be "(.*)", and the reason should be "(.*)"$/,
201+
(expectedVariant: string, expectedReason: string) => {
202+
expect(details).toBeDefined();
203+
expect(details.variant).toEqual(expectedVariant);
204+
expect(details.reason).toEqual(expectedReason);
205+
},
206+
);
207+
208+
then(/^the resolved string response should be "(.*)"$/, (expectedValue: string) => {
209+
expect(value).toEqual(expectedValue);
210+
});
211+
212+
when(
213+
/^a non-existent string flag with key "(.*)" is evaluated with details and a default value "(.*)"$/,
214+
async (key: string, defaultValue: string) => {
215+
flagKey = key;
216+
fallback = defaultValue;
217+
details = await client.getStringDetails(flagKey, defaultValue);
218+
},
219+
);
220+
221+
then(/^the default string value should be returned$/, () => {
222+
expect(details).toBeDefined();
223+
expect(details.value).toEqual(fallback);
224+
});
225+
226+
and(
227+
/^the reason should indicate an error and the error code should indicate a missing flag with "(.*)"$/,
228+
(errorCode: string) => {
229+
expect(details).toBeDefined();
230+
expect(details.reason).toEqual(StandardResolutionReasons.ERROR);
231+
expect(details.errorCode).toEqual(errorCode);
232+
},
233+
);
234+
235+
when(
236+
/^a string flag with key "(.*)" is evaluated as an integer, with details and a default value (\d+)$/,
237+
async (key: string, defaultValue: string) => {
238+
flagKey = key;
239+
fallback = Number.parseInt(defaultValue);
240+
details = await client.getNumberDetails(flagKey, Number.parseInt(defaultValue));
241+
},
242+
);
243+
244+
then(/^the default integer value should be returned$/, () => {
245+
expect(details).toBeDefined();
246+
expect(details.value).toEqual(fallback);
247+
});
248+
249+
and(
250+
/^the reason should indicate an error and the error code should indicate a type mismatch with "(.*)"$/,
251+
(errorCode: string) => {
252+
expect(details).toBeDefined();
253+
expect(details.reason).toEqual(StandardResolutionReasons.ERROR);
254+
expect(details.errorCode).toEqual(errorCode);
255+
},
256+
);
257+
258+
let ran: Promise<boolean>;
259+
when('a PROVIDER_READY handler is added', () => {
260+
ran = new Promise<boolean>((resolve) => {
261+
client.addHandler(ProviderEvents.Ready, async () => {
262+
resolve(true);
263+
});
264+
});
265+
});
266+
then('the PROVIDER_READY handler must run', () => {
267+
expect(ran).toBeTruthy();
268+
});
269+
270+
when('a PROVIDER_CONFIGURATION_CHANGED handler is added', () => {
271+
ran = new Promise<boolean>((resolve) => {
272+
client.addHandler(ProviderEvents.ConfigurationChanged, async (details) => {
273+
// file writes are not atomic, so we get a few events in quick succession from the testbed
274+
// some will not contain changes, this tolerates that; at least 1 should have our change
275+
if (details?.flagsChanged?.length) {
276+
flagsChanged = details?.flagsChanged;
277+
278+
resolve(true);
279+
}
280+
});
281+
});
282+
});
283+
284+
and(/^a flag with key "(.*)" is modified$/, async () => {
285+
// this happens every 1s in the associated container, so wait 3s
286+
await new Promise((resolve) => setTimeout(resolve, 3000));
287+
});
288+
289+
then('the PROVIDER_CONFIGURATION_CHANGED handler must run', () => {
290+
expect(ran).toBeTruthy();
291+
});
292+
293+
and(/^the event details must indicate "(.*)" was altered$/, (flagName) => {
294+
expect(flagsChanged).toContain(flagName);
295+
});
296+
297+
when(
298+
/^a zero-value boolean flag with key "(.*)" is evaluated with default value "(.*)"$/,
299+
(key, defaultVal: string) => {
300+
flagKey = key;
301+
fallback = defaultVal === 'true';
302+
},
303+
);
304+
305+
then(/^the resolved boolean zero-value should be "(.*)"$/, async (expectedVal: string) => {
306+
const expectedValue = expectedVal === 'true';
307+
const value = await client.getBooleanValue(flagKey, fallback as boolean);
308+
expect(value).toEqual(expectedValue);
309+
});
310+
311+
when(/^a zero-value string flag with key "(.*)" is evaluated with default value "(.*)"$/, (key, defaultVal) => {
312+
flagKey = key;
313+
fallback = defaultVal;
314+
});
315+
316+
then('the resolved string zero-value should be ""', async () => {
317+
const value = await client.getStringValue(flagKey, fallback as string);
318+
expect(value).toEqual('');
319+
});
320+
321+
when(/^a zero-value integer flag with key "(.*)" is evaluated with default value (\d+)$/, (key, defaultVal) => {
322+
flagKey = key;
323+
fallback = defaultVal;
324+
});
325+
326+
then(/^the resolved integer zero-value should be (\d+)$/, async (expectedValueString) => {
327+
const expectedValue = Number.parseInt(expectedValueString);
328+
const value = await client.getNumberValue(flagKey, fallback as number);
329+
expect(value).toEqual(expectedValue);
330+
});
331+
332+
when(
333+
/^a zero-value float flag with key "(.*)" is evaluated with default value (\d+\.\d+)$/,
334+
(key, defaultValueString) => {
335+
flagKey = key;
336+
fallback = Number.parseFloat(defaultValueString);
337+
},
338+
);
339+
340+
then(/^the resolved float zero-value should be (\d+\.\d+)$/, async (expectedValueString) => {
341+
const expectedValue = Number.parseFloat(expectedValueString);
342+
const value = await client.getNumberValue(flagKey, fallback as number);
343+
expect(value).toEqual(expectedValue);
344+
});
345+
346+
then(/^the returned reason should be "(.*)"$/, (expectedReason) => {
347+
expect(details.reason).toEqual(expectedReason);
348+
});
349+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './flag';

0 commit comments

Comments
 (0)