Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions packages/server/src/client/internal/open-feature-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ export class OpenFeatureClient implements Client {
mergedContext: EvaluationContext,
options: FlagEvaluationOptions,
) {
let accumulatedContext = mergedContext;
const accumulatedContext = mergedContext;

for (const [index, hook] of hooks.entries()) {
const hookContextIndex = hooks.length - 1 - index; // reverse index for before hooks
Expand All @@ -343,10 +343,7 @@ export class OpenFeatureClient implements Client {

const hookResult = await hook?.before?.(hookContext, Object.freeze(options.hookHints));
if (hookResult) {
accumulatedContext = {
...accumulatedContext,
...hookResult,
};
Object.assign(accumulatedContext, hookResult);

for (let i = 0; i < hooks.length; i++) {
Object.assign(hookContexts[hookContextIndex].context, accumulatedContext);
Expand Down
34 changes: 34 additions & 0 deletions packages/server/test/client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,40 @@ describe('OpenFeatureClient', () => {
client.setContext({ [KEY]: VAL });
expect(client.getContext()[KEY]).toEqual(VAL);
});

it('context object is reference stable between hook and evaluation calls', async () => {
let hookContextRef;
const contextMap = new WeakMap<EvaluationContext, HookContext>();
const contextStabilityProvider = {
metadata: {
name: 'evaluation-context',
},
hooks: [
{
before: jest.fn((hookContext: HookContext) => {
contextMap.set(hookContext.context, hookContext);
return hookContext.context;
})
}
],
resolveBooleanEvaluation: jest.fn((_flagKey, _defaultValue, context): Promise<ResolutionDetails<boolean>> => {
// We expect that the context object reference is the same as that captured in the hook
hookContextRef = contextMap.get(context);
return Promise.resolve({
value: true,
});
}),
} as unknown as Provider;

await OpenFeature.setProviderAndWait(contextStabilityProvider);
const client = OpenFeature.getClient();

const context = { data: 1, value: '2' };
await client.getBooleanValue('some-other-flag', false, context);
expect(contextStabilityProvider.resolveBooleanEvaluation).toHaveBeenCalled();
expect(contextStabilityProvider.hooks?.[0].before).toHaveBeenCalled();
expect(hookContextRef).toBeDefined();
});
});
});

Expand Down