Skip to content

Commit 1ab0cc6

Browse files
fix: onContextChanged not running for named providers (#491)
Signed-off-by: Lukas Reining <[email protected]>
1 parent 25f69cf commit 1ab0cc6

File tree

2 files changed

+110
-1
lines changed

2 files changed

+110
-1
lines changed

packages/client/src/open-feature.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,17 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI<Provider> implements Ma
3737
async setContext(context: EvaluationContext): Promise<void> {
3838
const oldContext = this._context;
3939
this._context = context;
40-
await this._defaultProvider?.onContextChange?.(oldContext, context);
40+
41+
const allProviders = [this._defaultProvider, ...this._clientProviders.values()];
42+
await Promise.all(
43+
allProviders.map(async (provider) => {
44+
try {
45+
return await provider.onContextChange?.(oldContext, context);
46+
} catch (err) {
47+
this._logger?.error(`Error running context change handler of provider ${provider.metadata.name}:`, err);
48+
}
49+
})
50+
);
4151
}
4252

4353
getContext(): EvaluationContext {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { EvaluationContext, JsonValue, OpenFeature, Provider, ProviderMetadata, ResolutionDetails } from '../src';
2+
3+
class MockProvider implements Provider {
4+
readonly metadata: ProviderMetadata;
5+
6+
constructor(options?: { name?: string }) {
7+
this.metadata = { name: options?.name ?? 'mock-provider' };
8+
}
9+
10+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
11+
onContextChange(oldContext: EvaluationContext, newContext: EvaluationContext): Promise<void> {
12+
return Promise.resolve();
13+
}
14+
15+
resolveBooleanEvaluation(): ResolutionDetails<boolean> {
16+
throw new Error('Not implemented');
17+
}
18+
19+
resolveNumberEvaluation(): ResolutionDetails<number> {
20+
throw new Error('Not implemented');
21+
}
22+
23+
resolveObjectEvaluation<T extends JsonValue>(): ResolutionDetails<T> {
24+
throw new Error('Not implemented');
25+
}
26+
27+
resolveStringEvaluation(): ResolutionDetails<string> {
28+
throw new Error('Not implemented');
29+
}
30+
}
31+
32+
describe('Evaluation Context', () => {
33+
describe('Requirement 3.2.2', () => {
34+
it('the API MUST have a method for setting the global evaluation context', () => {
35+
const context: EvaluationContext = { property1: false };
36+
OpenFeature.setContext(context);
37+
expect(OpenFeature.getContext()).toEqual(context);
38+
});
39+
});
40+
41+
describe('Requirement 3.2.4', () => {
42+
describe('when the global evaluation context is set, the on context changed handler MUST run', () => {
43+
it('on all registered providers', async () => {
44+
// Set initial context
45+
const context: EvaluationContext = { property1: false };
46+
await OpenFeature.setContext(context);
47+
48+
// Set some providers
49+
const defaultProvider = new MockProvider();
50+
const provider1 = new MockProvider();
51+
const provider2 = new MockProvider();
52+
53+
OpenFeature.setProvider(defaultProvider);
54+
OpenFeature.setProvider('client1', provider1);
55+
OpenFeature.setProvider('client2', provider2);
56+
57+
// Spy on context changed handlers of all providers
58+
const contextChangedSpys = [defaultProvider, provider1, provider2].map((provider) =>
59+
jest.spyOn(provider, 'onContextChange')
60+
);
61+
62+
// Change context
63+
const newContext: EvaluationContext = { property1: true, property2: 'prop2' };
64+
await OpenFeature.setContext(newContext);
65+
66+
contextChangedSpys.forEach((spy) => expect(spy).toHaveBeenCalledWith(context, newContext));
67+
});
68+
69+
it('on all registered providers even if one fails', async () => {
70+
// Set initial context
71+
const context: EvaluationContext = { property1: false };
72+
await OpenFeature.setContext(context);
73+
74+
// Set some providers
75+
const defaultProvider = new MockProvider();
76+
const provider1 = new MockProvider();
77+
const provider2 = new MockProvider();
78+
79+
OpenFeature.setProvider(defaultProvider);
80+
OpenFeature.setProvider('client1', provider1);
81+
OpenFeature.setProvider('client2', provider2);
82+
83+
// Spy on context changed handlers of all providers
84+
const contextChangedSpys = [defaultProvider, provider1, provider2].map((provider) =>
85+
jest.spyOn(provider, 'onContextChange')
86+
);
87+
88+
// Let first handler fail
89+
contextChangedSpys[0].mockImplementation(() => Promise.reject(new Error('Error')));
90+
91+
// Change context
92+
const newContext: EvaluationContext = { property1: true, property2: 'prop2' };
93+
await OpenFeature.setContext(newContext);
94+
95+
contextChangedSpys.forEach((spy) => expect(spy).toHaveBeenCalledWith(context, newContext));
96+
});
97+
});
98+
});
99+
});

0 commit comments

Comments
 (0)