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
227 changes: 3 additions & 224 deletions packages/shared/sdk-server/__tests__/LDClient.hooks.test.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,14 @@
import { basicPlatform } from '@launchdarkly/private-js-mocks';

import { integrations, LDClientImpl, LDEvaluationDetail, LDMigrationStage } from '../src';
import { LDClientImpl, LDMigrationStage } from '../src';
import Reasons from '../src/evaluation/Reasons';
import TestData from '../src/integrations/test_data/TestData';
import TestLogger, { LogLevel } from './Logger';
import { TestHook } from './hooks/TestHook';
import TestLogger from './Logger';
import makeCallbacks from './makeCallbacks';

const defaultUser = { kind: 'user', key: 'user-key' };

type EvalCapture = {
method: string;
hookContext: integrations.EvaluationSeriesContext;
hookData: integrations.EvaluationSeriesData;
detail?: LDEvaluationDetail;
};

class TestHook implements integrations.Hook {
captureBefore: EvalCapture[] = [];
captureAfter: EvalCapture[] = [];

getMetadataImpl: () => integrations.HookMetadata = () => ({ name: 'LaunchDarkly Test Hook' });

getMetadata(): integrations.HookMetadata {
return this.getMetadataImpl();
}

verifyBefore(
hookContext: integrations.EvaluationSeriesContext,
data: integrations.EvaluationSeriesData,
) {
expect(this.captureBefore).toHaveLength(1);
expect(this.captureBefore[0].hookContext).toEqual(hookContext);
expect(this.captureBefore[0].hookData).toEqual(data);
}

verifyAfter(
hookContext: integrations.EvaluationSeriesContext,
data: integrations.EvaluationSeriesData,
detail: LDEvaluationDetail,
) {
expect(this.captureAfter).toHaveLength(1);
expect(this.captureAfter[0].hookContext).toEqual(hookContext);
expect(this.captureAfter[0].hookData).toEqual(data);
expect(this.captureAfter[0].detail).toEqual(detail);
}

beforeEvalImpl: (
hookContext: integrations.EvaluationSeriesContext,
data: integrations.EvaluationSeriesData,
) => integrations.EvaluationSeriesData = (_hookContext, data) => data;

afterEvalImpl: (
hookContext: integrations.EvaluationSeriesContext,
data: integrations.EvaluationSeriesData,
detail: LDEvaluationDetail,
) => integrations.EvaluationSeriesData = (_hookContext, data, _detail) => data;

beforeEvaluation?(
hookContext: integrations.EvaluationSeriesContext,
data: integrations.EvaluationSeriesData,
): integrations.EvaluationSeriesData {
this.captureBefore.push({ method: 'beforeEvaluation', hookContext, hookData: data });
return this.beforeEvalImpl(hookContext, data);
}
afterEvaluation?(
hookContext: integrations.EvaluationSeriesContext,
data: integrations.EvaluationSeriesData,
detail: LDEvaluationDetail,
): integrations.EvaluationSeriesData {
this.captureAfter.push({ method: 'afterEvaluation', hookContext, hookData: data, detail });
return this.afterEvalImpl(hookContext, data, detail);
}
}

describe('given an LDClient with test data', () => {
let client: LDClientImpl;
let td: TestData;
Expand Down Expand Up @@ -409,105 +345,6 @@ describe('given an LDClient with test data', () => {
},
);
});

it('propagates data between stages', async () => {
testHook.beforeEvalImpl = (
_hookContext: integrations.EvaluationSeriesContext,
data: integrations.EvaluationSeriesData,
) => ({
...data,
added: 'added data',
});
await client.variation('flagKey', defaultUser, false);

testHook.verifyAfter(
{
flagKey: 'flagKey',
context: { ...defaultUser },
defaultValue: false,
method: 'LDClient.variation',
},
{ added: 'added data' },
{
value: false,
reason: { kind: 'ERROR', errorKind: 'FLAG_NOT_FOUND' },
variationIndex: null,
},
);
});

it('handles an exception thrown in beforeEvaluation', async () => {
testHook.beforeEvalImpl = (
_hookContext: integrations.EvaluationSeriesContext,
_data: integrations.EvaluationSeriesData,
) => {
throw new Error('bad hook');
};
await client.variation('flagKey', defaultUser, false);
logger.expectMessages([
{
level: LogLevel.Error,
matches:
/An error was encountered in "beforeEvaluation" of the "LaunchDarkly Test Hook" hook: Error: bad hook/,
},
]);
});

it('handles an exception thrown in afterEvaluation', async () => {
testHook.afterEvalImpl = () => {
throw new Error('bad hook');
};
await client.variation('flagKey', defaultUser, false);
logger.expectMessages([
{
level: LogLevel.Error,
matches:
/An error was encountered in "afterEvaluation" of the "LaunchDarkly Test Hook" hook: Error: bad hook/,
},
]);
});

it('handles exception getting the hook metadata', async () => {
testHook.getMetadataImpl = () => {
throw new Error('bad hook');
};
await client.variation('flagKey', defaultUser, false);

logger.expectMessages([
{
level: LogLevel.Error,
matches: /Exception thrown getting metadata for hook. Unable to get hook name./,
},
]);
});

it('uses unknown name when the name cannot be accessed', async () => {
testHook.beforeEvalImpl = (
_hookContext: integrations.EvaluationSeriesContext,
_data: integrations.EvaluationSeriesData,
) => {
throw new Error('bad hook');
};
testHook.getMetadataImpl = () => {
throw new Error('bad hook');
};
testHook.afterEvalImpl = () => {
throw new Error('bad hook');
};
await client.variation('flagKey', defaultUser, false);
logger.expectMessages([
{
level: LogLevel.Error,
matches:
/An error was encountered in "afterEvaluation" of the "unknown hook" hook: Error: bad hook/,
},
{
level: LogLevel.Error,
matches:
/An error was encountered in "beforeEvaluation" of the "unknown hook" hook: Error: bad hook/,
},
]);
});
});

it('can add a hook after initialization', async () => {
Expand Down Expand Up @@ -555,61 +392,3 @@ it('can add a hook after initialization', async () => {
},
);
});

it('executes hook stages in the specified order', async () => {
const beforeCalledOrder: string[] = [];
const afterCalledOrder: string[] = [];

const hookA = new TestHook();
hookA.beforeEvalImpl = (_context, data) => {
beforeCalledOrder.push('a');
return data;
};

hookA.afterEvalImpl = (_context, data, _detail) => {
afterCalledOrder.push('a');
return data;
};

const hookB = new TestHook();
hookB.beforeEvalImpl = (_context, data) => {
beforeCalledOrder.push('b');
return data;
};
hookB.afterEvalImpl = (_context, data, _detail) => {
afterCalledOrder.push('b');
return data;
};

const hookC = new TestHook();
hookC.beforeEvalImpl = (_context, data) => {
beforeCalledOrder.push('c');
return data;
};

hookC.afterEvalImpl = (_context, data, _detail) => {
afterCalledOrder.push('c');
return data;
};

const logger = new TestLogger();
const td = new TestData();
const client = new LDClientImpl(
'sdk-key',
basicPlatform,
{
updateProcessor: td.getFactory(),
sendEvents: false,
logger,
hooks: [hookA, hookB],
},
makeCallbacks(true),
);

await client.waitForInitialization();
client.addHook(hookC);
await client.variation('flagKey', defaultUser, false);

expect(beforeCalledOrder).toEqual(['a', 'b', 'c']);
expect(afterCalledOrder).toEqual(['c', 'b', 'a']);
});
Loading