diff --git a/packages/shared/sdk-client/src/LDClientImpl.test.ts b/packages/shared/sdk-client/src/LDClientImpl.test.ts index d0c36d84c..b2e14d315 100644 --- a/packages/shared/sdk-client/src/LDClientImpl.test.ts +++ b/packages/shared/sdk-client/src/LDClientImpl.test.ts @@ -1,18 +1,13 @@ import { LDContext } from '@launchdarkly/js-sdk-common'; -import { basicPlatform } from '@launchdarkly/private-js-mocks'; +import { basicPlatform, logger } from '@launchdarkly/private-js-mocks'; +import LDEmitter from './api/LDEmitter'; import fetchFlags from './evaluation/fetchFlags'; import * as mockResponseJson from './evaluation/mockResponse.json'; import LDClientImpl from './LDClientImpl'; -jest.mock('./evaluation/fetchFlags', () => { - const actual = jest.requireActual('./evaluation/fetchFlags'); - return { - __esModule: true, - ...actual, - default: jest.fn(), - }; -}); +jest.mock('./api/LDEmitter'); +jest.mock('./evaluation/fetchFlags'); describe('sdk-client object', () => { const testSdkKey = 'test-sdk-key'; @@ -20,15 +15,21 @@ describe('sdk-client object', () => { const mockFetchFlags = fetchFlags as jest.Mock; let ldc: LDClientImpl; + let mockEmitter: LDEmitter; - beforeEach(async () => { + beforeEach(() => { mockFetchFlags.mockResolvedValue(mockResponseJson); - ldc = new LDClientImpl(testSdkKey, context, basicPlatform, {}); - await ldc.start(); + ldc = new LDClientImpl(testSdkKey, context, basicPlatform, { logger }); + [mockEmitter] = (LDEmitter as jest.Mock).mock.instances; + }); + + afterEach(() => { + jest.resetAllMocks(); }); test('instantiate with blank options', () => { + ldc = new LDClientImpl(testSdkKey, context, basicPlatform, {}); expect(ldc.config).toMatchObject({ allAttributesPrivate: false, baseUri: 'https://sdk.launchdarkly.com', @@ -61,6 +62,7 @@ describe('sdk-client object', () => { }); test('all flags', async () => { + await ldc.start(); const all = ldc.allFlags(); expect(all).toEqual({ @@ -76,8 +78,45 @@ describe('sdk-client object', () => { }); test('variation', async () => { + await ldc.start(); const devTestFlag = ldc.variation('dev-test-flag'); expect(devTestFlag).toBe(true); }); + + test('identify success', async () => { + mockResponseJson['dev-test-flag'].value = false; + mockFetchFlags.mockResolvedValue(mockResponseJson); + const carContext: LDContext = { kind: 'car', key: 'mazda-cx7' }; + + await ldc.identify(carContext); + const c = ldc.getContext(); + const all = ldc.allFlags(); + + expect(carContext).toEqual(c); + expect(all).toMatchObject({ + 'dev-test-flag': false, + }); + }); + + test('identify error invalid context', async () => { + // @ts-ignore + const carContext: LDContext = { kind: 'car', key: undefined }; + + await expect(ldc.identify(carContext)).rejects.toThrowError(/no key/); + expect(logger.error).toBeCalledTimes(1); + expect(mockEmitter.emit).toHaveBeenNthCalledWith(1, 'error', expect.any(Error)); + expect(ldc.getContext()).toEqual(context); + }); + + test('identify error fetch error', async () => { + // @ts-ignore + mockFetchFlags.mockRejectedValue(new Error('unknown test fetch error')); + const carContext: LDContext = { kind: 'car', key: 'mazda-3' }; + + await expect(ldc.identify(carContext)).rejects.toThrowError(/fetch error/); + expect(logger.error).toBeCalledTimes(1); + expect(mockEmitter.emit).toHaveBeenNthCalledWith(1, 'error', expect.any(Error)); + expect(ldc.getContext()).toEqual(context); + }); }); diff --git a/packages/shared/sdk-client/src/LDClientImpl.ts b/packages/shared/sdk-client/src/LDClientImpl.ts index d6874e10a..e98f491a6 100644 --- a/packages/shared/sdk-client/src/LDClientImpl.ts +++ b/packages/shared/sdk-client/src/LDClientImpl.ts @@ -45,7 +45,7 @@ export default class LDClientImpl implements LDClient { */ constructor( public readonly sdkKey: string, - public readonly context: LDContext, + public context: LDContext, public readonly platform: Platform, options: LDOptions, ) { @@ -53,11 +53,6 @@ export default class LDClientImpl implements LDClient { throw new Error('You must configure the client with a client-side SDK key'); } - const checkedContext = Context.fromLDContext(context); - if (!checkedContext.valid) { - throw new Error('Context was unspecified or had no key'); - } - if (!platform.encoding) { throw new Error('Platform must implement Encoding because btoa is required.'); } @@ -78,12 +73,11 @@ export default class LDClientImpl implements LDClient { async start() { try { - this.flags = await fetchFlags(this.sdkKey, this.context, this.config, this.platform); + await this.identify(this.context); this.emitter.emit('ready'); } catch (error: any) { - this.logger.error(error); - this.emitter.emit('error', error); this.emitter.emit('failed', error); + throw error; } } @@ -113,13 +107,24 @@ export default class LDClientImpl implements LDClient { return clone(this.context); } - identify( - context: LDContext, - hash?: string, - onDone?: (err: Error | null, flags: LDFlagSet | null) => void, - ): Promise { - // TODO: - return Promise.resolve({}); + // TODO: implement secure mode + async identify(context: LDContext, hash?: string): Promise { + const checkedContext = Context.fromLDContext(context); + if (!checkedContext.valid) { + const error = new Error('Context was unspecified or had no key'); + this.logger.error(error); + this.emitter.emit('error', error); + throw error; + } + + try { + this.flags = await fetchFlags(this.sdkKey, context, this.config, this.platform); + this.context = context; + } catch (error: any) { + this.logger.error(error); + this.emitter.emit('error', error); + throw error; + } } off(eventName: EventName, listener?: Function): void { diff --git a/packages/shared/sdk-client/src/api/LDClient.ts b/packages/shared/sdk-client/src/api/LDClient.ts index 22bae74e0..5a9c3e4b7 100644 --- a/packages/shared/sdk-client/src/api/LDClient.ts +++ b/packages/shared/sdk-client/src/api/LDClient.ts @@ -89,21 +89,10 @@ export interface LDClient { * The context properties. Must contain at least the `key` property. * @param hash * The signed context key if you are using [Secure Mode](https://docs.launchdarkly.com/sdk/features/secure-mode#configuring-secure-mode-in-the-javascript-client-side-sdk). - * @param onDone - * A function which will be called as soon as the flag values for the new context are available, - * with two parameters: an error value (if any), and an {@link LDFlagSet} containing the new values - * (which can also be obtained by calling {@link variation}). If the callback is omitted, you will - * receive a Promise instead. * @returns - * If you provided a callback, then nothing. Otherwise, a Promise which resolve once the flag - * values for the new context are available, providing an {@link LDFlagSet} containing the new values - * (which can also be obtained by calling {@link variation}). + * A Promise which resolve once the flag values for the new context are available. */ - identify( - context: LDContext, - hash?: string, - onDone?: (err: Error | null, flags: LDFlagSet | null) => void, - ): Promise; + identify(context: LDContext, hash?: string): Promise; /** * Returns the client's current context.