Skip to content

Commit fbd9f91

Browse files
aepflitoddbaert
andauthored
test(flagd): rework e2e tests to new format (#1129)
Signed-off-by: Simon Schrottner <[email protected]> Signed-off-by: Todd Baert <[email protected]> Co-authored-by: Todd Baert <[email protected]>
1 parent 24a7e0e commit fbd9f91

29 files changed

+651
-310
lines changed

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"checkObsoleteDependencies": true,
6565
"checkVersionMismatches": true,
6666
"ignoredDependencies": ["jest-cucumber", "jest"],
67-
"ignoredFiles": ["**/test/**", "**/spec/**", "**/*.spec.ts", "**/*.spec.js", "**/*.test.ts", "**/*.test.js"]
67+
"ignoredFiles": ["**/test/**", "**/tests/*", "**/spec/**", "**/*.spec.ts", "**/*.spec.js", "**/*.test.ts", "**/*.test.js"]
6868
}
6969
]
7070
}

libs/providers/flagd-web/src/e2e/constants.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ import { getGherkinTestPath } from '@openfeature/flagd-core';
22

33
export const FLAGD_NAME = 'flagd';
44

5-
export const GHERKIN_EVALUATION_FEATURE = getGherkinTestPath('flagd.feature');
5+
export const GHERKIN_EVALUATION_FEATURE = getGherkinTestPath(
6+
'evaluation.feature',
7+
'spec/specification/assets/gherkin/',
8+
);

libs/providers/flagd-web/src/e2e/step-definitions/flag.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { StepDefinitions } from 'jest-cucumber';
2-
import type { EvaluationDetails, FlagValue, JsonObject } from '@openfeature/web-sdk';
2+
import type { EvaluationContext, EvaluationDetails, FlagValue, JsonObject } from '@openfeature/web-sdk';
33
import { OpenFeature, ProviderEvents, StandardResolutionReasons } from '@openfeature/web-sdk';
44
import { E2E_CLIENT_NAME } from '@openfeature/flagd-core';
55

@@ -8,6 +8,7 @@ export const flagStepDefinitions: StepDefinitions = ({ given, and, when, then })
88
let value: FlagValue;
99
let details: EvaluationDetails<FlagValue>;
1010
let fallback: FlagValue;
11+
let context: EvaluationContext;
1112

1213
const client = OpenFeature.getClient(E2E_CLIENT_NAME);
1314

@@ -17,7 +18,7 @@ export const flagStepDefinitions: StepDefinitions = ({ given, and, when, then })
1718
});
1819
});
1920

20-
given('a provider is registered', () => undefined);
21+
given('a stable provider', () => undefined);
2122
given('a flagd provider is set', () => undefined);
2223

2324
when(
@@ -79,6 +80,32 @@ export const flagStepDefinitions: StepDefinitions = ({ given, and, when, then })
7980
value = client.getObjectValue(key, defaultValue);
8081
});
8182

83+
and(/^a flag with key "(.*)" is evaluated with default value "(.*)"$/, async (key, defaultValue) => {
84+
await OpenFeature.setContext(context);
85+
flagKey = key;
86+
fallback = defaultValue;
87+
value = client.getStringValue(flagKey, fallback as string);
88+
});
89+
90+
when(
91+
/^context contains keys "(.*)", "(.*)", "(.*)", "(.*)" with values "(.*)", "(.*)", (\d+), "(.*)"$/,
92+
(key0, key1, key2, key3, stringVal1, stringVal2, intVal, boolVal) => {
93+
context = {
94+
[key0]: stringVal1,
95+
[key1]: stringVal2,
96+
[key2]: Number.parseInt(intVal),
97+
[key3]: boolVal === true,
98+
};
99+
},
100+
);
101+
102+
and(/^the resolved flag value is "(.*)" when the context is empty$/, async (expectedValue) => {
103+
context = {};
104+
await OpenFeature.setContext(context);
105+
value = client.getStringValue(flagKey, fallback as string);
106+
expect(value).toEqual(expectedValue);
107+
});
108+
82109
then(
83110
/^the resolved object value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/,
84111
(field1: string, field2: string, field3: string, boolValue: string, stringValue: string, intValue: string) => {

libs/providers/flagd/src/e2e/constants.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ export const FLAGD_NAME = 'flagd';
44
export const UNSTABLE_CLIENT_NAME = 'unstable';
55
export const UNAVAILABLE_CLIENT_NAME = 'unavailable';
66

7-
export const GHERKIN_FLAGD_FEATURE = getGherkinTestPath('flagd.feature');
8-
export const GHERKIN_FLAGD_JSON_EVALUATOR_FEATURE = getGherkinTestPath('flagd-json-evaluator.feature');
9-
export const GHERKIN_FLAGD_RECONNECT_FEATURE = getGherkinTestPath('flagd-reconnect.feature');
7+
export const GHERKIN_FLAGD = getGherkinTestPath('*.feature');
8+
export const CONNECTION_FEATURE = getGherkinTestPath('connection.feature');
9+
export const CONTEXT_ENRICHMENT_FEATURE = getGherkinTestPath('contextEnrichment.feature');
10+
export const EVALUATION_FEATURE = getGherkinTestPath('evaluation.feature');
11+
export const EVENTS_FEATURE = getGherkinTestPath('events.feature');
12+
export const METADATA_FEATURE = getGherkinTestPath('metadata.feature');
13+
export const RPC_CACHING_FEATURE = getGherkinTestPath('rpc-caching.feature');
14+
export const SELECTOR_FEATURE = getGherkinTestPath('selector.feature');
15+
export const TARGETING_FEATURE = getGherkinTestPath('targeting.feature');
1016
export const GHERKIN_EVALUATION_FEATURE = getGherkinTestPath(
1117
'evaluation.feature',
1218
'spec/specification/assets/gherkin/',
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export * from './constants';
2-
export * from './step-definitions';
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { StepsDefinitionCallbackOptions } from 'jest-cucumber/dist/src/feature-definition-creation';
2+
import type { State, Steps } from './state';
3+
import { CacheOption, getConfig, ResolverType } from '../../lib/configuration';
4+
import { mapValueToType } from './utils';
5+
6+
export const configSteps: Steps = (state: State) => {
7+
function mapName(name: string): string {
8+
switch (name) {
9+
case 'resolver':
10+
return 'resolverType';
11+
default:
12+
return name;
13+
}
14+
}
15+
16+
return ({ given, when, then }: StepsDefinitionCallbackOptions) => {
17+
beforeEach(() => {
18+
state.options = {};
19+
});
20+
given(/^an option "(.*)" of type "(.*)" with value "(.*)"$/, (name: string, type: string, value: string) => {
21+
state.options[mapName(name)] = mapValueToType(value, type);
22+
});
23+
given(/^an environment variable "(.*)" with value "(.*)"$/, (name, value) => {
24+
process.env[name] = value;
25+
});
26+
when('a config was initialized', () => {
27+
state.config = getConfig(state.options);
28+
});
29+
30+
then(
31+
/^the option "(.*)" of type "(.*)" should have the value "(.*)"$/,
32+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
33+
(_name: string, _type: string, _value: string) => {
34+
// TODO: implement with configuration unification, see: https://github.com/open-feature/js-sdk-contrib/issues/1096
35+
// const expected = mapValueToType(value, type);
36+
// const propertyName = mapName(name);
37+
// // eslint-disable-next-line @typescript-eslint/ban-ts-comment
38+
// // @ts-ignore
39+
// const configElement = state.config[propertyName];
40+
// expect(configElement).toBe(expected);
41+
},
42+
);
43+
44+
// eslint-disable-next-line @typescript-eslint/no-empty-function
45+
then('we should have an error', () => {});
46+
};
47+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { JsonObject } from '@openfeature/server-sdk';
2+
import type { State, Steps } from './state';
3+
import { mapValueToType } from './utils';
4+
5+
export const contextSteps: Steps =
6+
(state: State) =>
7+
({ given, when, then }) => {
8+
beforeEach(() => (state.context = undefined));
9+
given(
10+
/^a context containing a key "(.*)", with type "(.*)" and with value "(.*)"$/,
11+
(key: string, type: string, value: string) => {
12+
if (state.context == undefined) {
13+
state.context = {};
14+
}
15+
state.context[key] = mapValueToType(value, type);
16+
},
17+
);
18+
given(
19+
/^a context containing a nested property with outer key "(.*)" and inner key "(.*)", with value "(.*)"$/,
20+
(outer: string, inner: string, value) => {
21+
if (state.context == undefined) {
22+
state.context = {};
23+
}
24+
state.context[outer] = { ...(state.context[outer] as JsonObject), [inner]: value };
25+
},
26+
);
27+
given(/^a context containing a targeting key with value "(.*)"$/, (key) => {
28+
if (state.context == undefined) {
29+
state.context = {};
30+
}
31+
state.context.targetingKey = key;
32+
});
33+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { ServerProviderEvents } from '@openfeature/server-sdk';
2+
import type { State, Steps } from './state';
3+
import { waitFor } from './utils';
4+
5+
export const eventSteps: Steps =
6+
(state: State) =>
7+
({ given, when, then }) => {
8+
function map(eventType: string): ServerProviderEvents {
9+
switch (eventType) {
10+
case 'error':
11+
return ServerProviderEvents.Error;
12+
case 'ready':
13+
return ServerProviderEvents.Ready;
14+
case 'stale':
15+
return ServerProviderEvents.Stale;
16+
case 'change':
17+
return ServerProviderEvents.ConfigurationChanged;
18+
19+
default:
20+
throw new Error('unknown eventtype');
21+
}
22+
}
23+
24+
given(/a (.*) event handler/, async (type: string) => {
25+
state.client?.addHandler(map(type), (details) => {
26+
state.events.push({ type, details });
27+
});
28+
});
29+
30+
then(/^the (.*) event handler should have been executed$/, async (type: string) => {
31+
await waitFor(() => expect(state.events.find((value) => value.type == type)).toBeDefined(), { timeout: 20000 });
32+
expect(state.events.find((value) => value.type == type)).toBeDefined();
33+
});
34+
35+
then(/^the (.*) event handler should have been executed within (\d+)ms$/, async (type: string, ms: number) => {
36+
await waitFor(() => expect(state.events.find((value) => value.type == type)).toBeDefined(), { timeout: ms });
37+
const actual = state.events.find((value) => value.type == type);
38+
expect(actual).toBeDefined();
39+
});
40+
41+
when(/^a (.*) event was fired$/, async (type: string) => {
42+
await waitFor(() => expect(state.events.find((value) => value.type == type)), { timeout: 2000 });
43+
expect(state.events.find((value) => value.type == type)).toBeDefined();
44+
});
45+
46+
then('the flag should be part of the event payload', async () => {
47+
await waitFor(() => expect(state.events.find((value) => value.type == 'change')), { timeout: 2000 });
48+
});
49+
};

0 commit comments

Comments
 (0)