diff --git a/.github/workflows/mocks.yml b/.github/workflows/mocks.yml deleted file mode 100644 index 6a5b5d6b7..000000000 --- a/.github/workflows/mocks.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: shared/mocks - -on: - push: - branches: [main, 'feat/**'] - paths-ignore: - - '**.md' #Do not need to run CI for markdown changes. - pull_request: - branches: [main, 'feat/**'] - paths-ignore: - - '**.md' - -jobs: - build-test-mocks: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - - id: shared - name: Shared CI Steps - uses: ./actions/ci - with: - workspace_name: '@launchdarkly/private-js-mocks' - workspace_path: packages/shared/mocks - should_build_docs: false diff --git a/.github/workflows/react-native-detox.yml b/.github/workflows/react-native-detox.yml index d42886a67..1b491d0de 100644 --- a/.github/workflows/react-native-detox.yml +++ b/.github/workflows/react-native-detox.yml @@ -16,7 +16,6 @@ on: - 'packages/shared/common/**' - 'packages/shared/sdk-client/**' - 'packages/sdk/react-native/**' - - 'packages/shared/mocks/**' jobs: detox-android: diff --git a/package.json b/package.json index dd94fbcb1..2778c9e9d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "@launchdarkly/js-core", "workspaces": [ "packages/shared/common", - "packages/shared/mocks", "packages/shared/sdk-client", "packages/shared/sdk-server", "packages/shared/sdk-server-edge", diff --git a/packages/sdk/browser/package.json b/packages/sdk/browser/package.json index 96ff6bcf4..6cc036846 100644 --- a/packages/sdk/browser/package.json +++ b/packages/sdk/browser/package.json @@ -40,7 +40,6 @@ }, "devDependencies": { "@jest/globals": "^29.7.0", - "@launchdarkly/private-js-mocks": "0.0.1", "@rollup/plugin-commonjs": "^25.0.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.0.2", diff --git a/packages/sdk/cloudflare/jsr.json b/packages/sdk/cloudflare/jsr.json index 39c3d457d..6ebe70663 100644 --- a/packages/sdk/cloudflare/jsr.json +++ b/packages/sdk/cloudflare/jsr.json @@ -3,15 +3,7 @@ "version": "2.5.14", "exports": "./src/index.ts", "publish": { - "include": [ - "LICENSE", - "README.md", - "package.json", - "jsr.json", - "src/**/*.ts" - ], - "exclude": [ - "src/**/*.test.ts" - ] + "include": ["LICENSE", "README.md", "package.json", "jsr.json", "src/**/*.ts"], + "exclude": ["src/**/*.test.ts"] } } diff --git a/packages/sdk/react-native/__tests__/fromExternal/react-native-sse/EventSource.test.ts b/packages/sdk/react-native/__tests__/fromExternal/react-native-sse/EventSource.test.ts index f6c4173ad..a7d323fa9 100644 --- a/packages/sdk/react-native/__tests__/fromExternal/react-native-sse/EventSource.test.ts +++ b/packages/sdk/react-native/__tests__/fromExternal/react-native-sse/EventSource.test.ts @@ -1,15 +1,19 @@ -import { type EventName } from '@launchdarkly/js-client-sdk-common'; -import { createLogger } from '@launchdarkly/private-js-mocks'; +import { type EventName, LDLogger } from '@launchdarkly/js-client-sdk-common'; import EventSource, { backoff, jitter, } from '../../../src/fromExternal/react-native-sse/EventSource'; -let logger: ReturnType; +let logger: LDLogger; beforeEach(() => { - logger = createLogger(); + logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; }); describe('EventSource', () => { diff --git a/packages/sdk/react-native/package.json b/packages/sdk/react-native/package.json index 2a045f012..6419af347 100644 --- a/packages/sdk/react-native/package.json +++ b/packages/sdk/react-native/package.json @@ -46,7 +46,6 @@ "base64-js": "^1.5.1" }, "devDependencies": { - "@launchdarkly/private-js-mocks": "0.0.1", "@testing-library/react": "^14.1.2", "@trivago/prettier-plugin-sort-imports": "^4.1.1", "@types/jest": "^29.5.11", diff --git a/packages/sdk/server-node/__tests__/LDClientNode.listeners.test.ts b/packages/sdk/server-node/__tests__/LDClientNode.listeners.test.ts index 182488b15..2a8fd8cce 100644 --- a/packages/sdk/server-node/__tests__/LDClientNode.listeners.test.ts +++ b/packages/sdk/server-node/__tests__/LDClientNode.listeners.test.ts @@ -1,15 +1,18 @@ -import { integrations } from '@launchdarkly/js-server-sdk-common'; -import { createLogger } from '@launchdarkly/private-js-mocks'; +import { integrations, LDLogger } from '@launchdarkly/js-server-sdk-common'; import { Context, LDClient } from '../src'; import LDClientNode from '../src/LDClientNode'; -let logger: ReturnType; +let logger: LDLogger; beforeEach(() => { - logger = createLogger(); + logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; }); - describe('given an LDClient with test data', () => { let client: LDClient; let td: integrations.TestData; diff --git a/packages/sdk/server-node/__tests__/LDClientNode.test.ts b/packages/sdk/server-node/__tests__/LDClientNode.test.ts index 28d8cb1d6..8de453297 100644 --- a/packages/sdk/server-node/__tests__/LDClientNode.test.ts +++ b/packages/sdk/server-node/__tests__/LDClientNode.test.ts @@ -1,12 +1,16 @@ -import { LDContext } from '@launchdarkly/js-server-sdk-common'; -import { createLogger } from '@launchdarkly/private-js-mocks'; +import { LDContext, LDLogger } from '@launchdarkly/js-server-sdk-common'; import { init } from '../src'; -let logger: ReturnType; +let logger: LDLogger; beforeEach(() => { - logger = createLogger(); + logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; }); it('fires ready event in offline mode', (done) => { diff --git a/packages/sdk/server-node/__tests__/LDClientNode.tls.test.ts b/packages/sdk/server-node/__tests__/LDClientNode.tls.test.ts index 2034b49f4..11454bb43 100644 --- a/packages/sdk/server-node/__tests__/LDClientNode.tls.test.ts +++ b/packages/sdk/server-node/__tests__/LDClientNode.tls.test.ts @@ -6,15 +6,18 @@ import { TestHttpServer, } from 'launchdarkly-js-test-helpers'; -import { createLogger } from '@launchdarkly/private-js-mocks'; - -import { LDClient } from '../src'; +import { LDClient, LDLogger } from '../src'; import LDClientNode from '../src/LDClientNode'; -let logger: ReturnType; +let logger: LDLogger; beforeEach(() => { - logger = createLogger(); + logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; }); describe('When using a TLS connection', () => { diff --git a/packages/sdk/server-node/package.json b/packages/sdk/server-node/package.json index a8d2958bd..eaceeedd9 100644 --- a/packages/sdk/server-node/package.json +++ b/packages/sdk/server-node/package.json @@ -50,7 +50,6 @@ "launchdarkly-eventsource": "2.0.3" }, "devDependencies": { - "@launchdarkly/private-js-mocks": "0.0.1", "@trivago/prettier-plugin-sort-imports": "^4.1.1", "@types/jest": "^29.4.0", "@typescript-eslint/eslint-plugin": "^6.20.0", diff --git a/packages/shared/mocks/src/contextDeduplicator.ts b/packages/shared/common/__tests__/contextDeduplicator.ts similarity index 82% rename from packages/shared/mocks/src/contextDeduplicator.ts rename to packages/shared/common/__tests__/contextDeduplicator.ts index b04d0d277..0c4ff2b8f 100644 --- a/packages/shared/mocks/src/contextDeduplicator.ts +++ b/packages/shared/common/__tests__/contextDeduplicator.ts @@ -1,4 +1,5 @@ -import type { Context, subsystem } from '@common'; +import { subsystem } from '../src/api'; +import Context from '../src/Context'; export default class ContextDeduplicator implements subsystem.LDContextDeduplicator { flushInterval?: number | undefined = 0.1; diff --git a/packages/shared/mocks/src/platform.ts b/packages/shared/common/__tests__/createBasicPlatform.ts similarity index 92% rename from packages/shared/mocks/src/platform.ts rename to packages/shared/common/__tests__/createBasicPlatform.ts index e3b0b9ecc..6b64d40dd 100644 --- a/packages/shared/mocks/src/platform.ts +++ b/packages/shared/common/__tests__/createBasicPlatform.ts @@ -1,6 +1,5 @@ -import type { PlatformData, SdkData } from '@common'; - -import { setupCrypto } from './crypto'; +import { PlatformData, SdkData } from '../src/api'; +import { setupCrypto } from './setupCrypto'; const setupInfo = () => ({ platformData: jest.fn( diff --git a/packages/shared/common/src/internal/diagnostics/DiagnosticsManager.test.ts b/packages/shared/common/__tests__/internal/diagnostics/DiagnosticsManager.test.ts similarity index 95% rename from packages/shared/common/src/internal/diagnostics/DiagnosticsManager.test.ts rename to packages/shared/common/__tests__/internal/diagnostics/DiagnosticsManager.test.ts index c73d02007..6cfa7b6a8 100644 --- a/packages/shared/common/src/internal/diagnostics/DiagnosticsManager.test.ts +++ b/packages/shared/common/__tests__/internal/diagnostics/DiagnosticsManager.test.ts @@ -1,6 +1,5 @@ -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; - -import DiagnosticsManager from './DiagnosticsManager'; +import DiagnosticsManager from '../../../src/internal/diagnostics/DiagnosticsManager'; +import { createBasicPlatform } from '../../createBasicPlatform'; describe('given a diagnostics manager', () => { const dateNowString = '2023-08-10'; diff --git a/packages/shared/common/__tests__/internal/events/EventProcessor.test.ts b/packages/shared/common/__tests__/internal/events/EventProcessor.test.ts index e972fa46e..82de97679 100644 --- a/packages/shared/common/__tests__/internal/events/EventProcessor.test.ts +++ b/packages/shared/common/__tests__/internal/events/EventProcessor.test.ts @@ -1,10 +1,5 @@ -import { - ContextDeduplicator, - createBasicPlatform, - createLogger, -} from '@launchdarkly/private-js-mocks'; - import { LDContextCommon, LDMultiKindContext } from '../../../src/api/context'; +import { LDLogger } from '../../../src/api/logging/LDLogger'; import { LDContextDeduplicator, LDDeliveryStatus, LDEventType } from '../../../src/api/subsystem'; import Context from '../../../src/Context'; import { EventProcessor, InputIdentifyEvent } from '../../../src/internal'; @@ -13,13 +8,20 @@ import shouldSample from '../../../src/internal/events/sampling'; import BasicLogger from '../../../src/logging/BasicLogger'; import format from '../../../src/logging/format'; import ClientContext from '../../../src/options/ClientContext'; +import ContextDeduplicator from '../../contextDeduplicator'; +import { createBasicPlatform } from '../../createBasicPlatform'; let mockPlatform: ReturnType; let clientContext: ClientContext; -let logger: ReturnType; +let logger: LDLogger; beforeEach(() => { - logger = createLogger(); + logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; mockPlatform = createBasicPlatform(); clientContext = { basicConfiguration: { diff --git a/packages/shared/common/src/internal/events/EventSender.test.ts b/packages/shared/common/__tests__/internal/events/EventSender.test.ts similarity index 84% rename from packages/shared/common/src/internal/events/EventSender.test.ts rename to packages/shared/common/__tests__/internal/events/EventSender.test.ts index 3bd27d77c..2e4c3f3dd 100644 --- a/packages/shared/common/src/internal/events/EventSender.test.ts +++ b/packages/shared/common/__tests__/internal/events/EventSender.test.ts @@ -1,21 +1,20 @@ -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; - -import { Info, PlatformData, SdkData } from '../../api'; -import { LDDeliveryStatus, LDEventSenderResult, LDEventType } from '../../api/subsystem'; -import { ApplicationTags, ClientContext } from '../../options'; -import EventSender from './EventSender'; +import { Info, PlatformData, SdkData } from '../../../src/api'; +import { LDDeliveryStatus, LDEventSenderResult, LDEventType } from '../../../src/api/subsystem'; +import EventSender from '../../../src/internal/events/EventSender'; +import { ApplicationTags, ClientContext } from '../../../src/options'; +import { createBasicPlatform } from '../../createBasicPlatform'; let mockPlatform: ReturnType; +function runWithTimers(fn: () => Promise) { + const promise = fn(); + return jest.runAllTimersAsync().then(() => promise); +} + beforeEach(() => { mockPlatform = createBasicPlatform(); }); -jest.mock('../../utils', () => { - const actual = jest.requireActual('../../utils'); - return { ...actual, sleep: jest.fn() }; -}); - const basicConfig = { tags: new ApplicationTags({ application: { id: 'testApplication1', version: '1.0.0' } }), serviceEndpoints: { @@ -120,9 +119,8 @@ describe('given an event sender', () => { }, ); - eventSenderResult = await eventSender.sendEventData( - LDEventType.AnalyticsEvents, - testEventData1, + eventSenderResult = await runWithTimers(() => + eventSender.sendEventData(LDEventType.AnalyticsEvents, testEventData1), ); }); @@ -142,9 +140,8 @@ describe('given an event sender', () => { it('includes the payload', async () => { const { status: status1 } = eventSenderResult; - const { status: status2 } = await eventSender.sendEventData( - LDEventType.DiagnosticEvent, - testEventData2, + const { status: status2 } = await runWithTimers(() => + eventSender.sendEventData(LDEventType.DiagnosticEvent, testEventData2), ); expect(status1).toEqual(LDDeliveryStatus.Succeeded); @@ -168,7 +165,9 @@ describe('given an event sender', () => { it('sends a unique payload for analytics events', async () => { // send the same request again to assert unique uuids - await eventSender.sendEventData(LDEventType.AnalyticsEvents, testEventData1); + await runWithTimers(() => + eventSender.sendEventData(LDEventType.AnalyticsEvents, testEventData1), + ); expect(mockFetch).toHaveBeenCalledTimes(2); expect(mockFetch).toHaveBeenNthCalledWith( @@ -190,9 +189,8 @@ describe('given an event sender', () => { describe.each([400, 408, 429, 503])('given recoverable errors', (responseStatusCode) => { beforeEach(async () => { setupMockFetch(responseStatusCode); - eventSenderResult = await eventSender.sendEventData( - LDEventType.AnalyticsEvents, - testEventData1, + eventSenderResult = await runWithTimers(() => + eventSender.sendEventData(LDEventType.AnalyticsEvents, testEventData1), ); }); @@ -210,9 +208,8 @@ describe('given an event sender', () => { it('given a result for too large of a payload', async () => { setupMockFetch(413); - eventSenderResult = await eventSender.sendEventData( - LDEventType.AnalyticsEvents, - testEventData1, + eventSenderResult = await runWithTimers(() => + eventSender.sendEventData(LDEventType.AnalyticsEvents, testEventData1), ); const errorMessage = `Received error 413 for event posting - giving up permanently`; @@ -228,9 +225,8 @@ describe('given an event sender', () => { describe.each([401, 403])('given unrecoverable errors', (responseStatusCode) => { beforeEach(async () => { setupMockFetch(responseStatusCode); - eventSenderResult = await eventSender.sendEventData( - LDEventType.AnalyticsEvents, - testEventData1, + eventSenderResult = await runWithTimers(() => + eventSender.sendEventData(LDEventType.AnalyticsEvents, testEventData1), ); }); diff --git a/packages/shared/common/src/internal/stream/StreamingProcessor.test.ts b/packages/shared/common/__tests__/internal/stream/StreamingProcessor.test.ts similarity index 92% rename from packages/shared/common/src/internal/stream/StreamingProcessor.test.ts rename to packages/shared/common/__tests__/internal/stream/StreamingProcessor.test.ts index 56f738da5..293e35124 100644 --- a/packages/shared/common/src/internal/stream/StreamingProcessor.test.ts +++ b/packages/shared/common/__tests__/internal/stream/StreamingProcessor.test.ts @@ -1,14 +1,13 @@ -import { createBasicPlatform, createLogger } from '@launchdarkly/private-js-mocks'; +import { EventName, Info, LDLogger, ProcessStreamResponse } from '../../../src/api'; +import { LDStreamProcessor } from '../../../src/api/subsystem'; +import { DataSourceErrorKind } from '../../../src/datasource/DataSourceErrorKinds'; +import { LDStreamingError } from '../../../src/datasource/errors'; +import { DiagnosticsManager } from '../../../src/internal/diagnostics'; +import StreamingProcessor from '../../../src/internal/stream/StreamingProcessor'; +import { defaultHeaders } from '../../../src/utils'; +import { createBasicPlatform } from '../../createBasicPlatform'; -import { EventName, Info, LDLogger, ProcessStreamResponse } from '../../api'; -import { LDStreamProcessor } from '../../api/subsystem'; -import { DataSourceErrorKind } from '../../datasource/DataSourceErrorKinds'; -import { LDStreamingError } from '../../datasource/errors'; -import { defaultHeaders } from '../../utils'; -import { DiagnosticsManager } from '../diagnostics'; -import StreamingProcessor from './StreamingProcessor'; - -let logger: ReturnType; +let logger: LDLogger; const serviceEndpoints = { events: '', @@ -44,7 +43,12 @@ let basicPlatform: any; beforeEach(() => { basicPlatform = createBasicPlatform(); - logger = createLogger(); + logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; }); const createMockEventSource = (streamUri: string = '', options: any = {}) => ({ diff --git a/packages/shared/common/__tests__/options/ApplicationTags.test.ts b/packages/shared/common/__tests__/options/ApplicationTags.test.ts index 51ae58449..ffc51b3d9 100644 --- a/packages/shared/common/__tests__/options/ApplicationTags.test.ts +++ b/packages/shared/common/__tests__/options/ApplicationTags.test.ts @@ -1,10 +1,16 @@ -import { createLogger } from '@launchdarkly/private-js-mocks'; - import ApplicationTags from '../../src/options/ApplicationTags'; describe.each([ [ - { application: { id: 'is-valid', version: 'also-valid' }, logger: createLogger() }, + { + application: { id: 'is-valid', version: 'also-valid' }, + logger: { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }, + }, 'application-id/is-valid application-version/also-valid', [], ], @@ -16,36 +22,105 @@ describe.each([ name: 'test-app-1', versionName: 'test-version-1', }, - logger: createLogger(), + logger: { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }, }, 'application-id/is-valid application-name/test-app-1 application-version/also-valid application-version-name/test-version-1', [], ], - [{ application: { id: 'is-valid' }, logger: createLogger() }, 'application-id/is-valid', []], [ - { application: { version: 'also-valid' }, logger: createLogger() }, + { + application: { id: 'is-valid' }, + logger: { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }, + }, + 'application-id/is-valid', + [], + ], + [ + { + application: { version: 'also-valid' }, + logger: { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }, + }, 'application-version/also-valid', [], ], - [{ application: {}, logger: createLogger() }, undefined, []], - [{ logger: createLogger() }, undefined, []], + [ + { + application: {}, + logger: { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }, + }, + undefined, + [], + ], + [ + { + logger: { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }, + }, + undefined, + [], + ], [undefined, undefined, []], // Above ones are 'valid' cases. Below are invalid. [ - { application: { id: 'bad tag' }, logger: createLogger() }, + { + application: { id: 'bad tag' }, + logger: { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }, + }, undefined, [/Config option "application.id" must/], ], [ - { application: { id: 'bad tag', version: 'good-tag' }, logger: createLogger() }, + { + application: { id: 'bad tag', version: 'good-tag' }, + logger: { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }, + }, 'application-version/good-tag', [/Config option "application.id" must/], ], [ { application: { id: 'bad tag', version: 'good-tag', name: '', versionName: 'test-version-1' }, - logger: createLogger(), + logger: { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }, }, 'application-version/good-tag application-version-name/test-version-1', [/Config option "application.id" must/, /Config option "application.name" must/], @@ -58,7 +133,12 @@ describe.each([ name: 'invalid name', versionName: 'invalid version name', }, - logger: createLogger(), + logger: { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }, }, undefined, [ diff --git a/packages/shared/mocks/src/crypto.ts b/packages/shared/common/__tests__/setupCrypto.ts similarity index 91% rename from packages/shared/mocks/src/crypto.ts rename to packages/shared/common/__tests__/setupCrypto.ts index 8a7d9a625..72b2d13ef 100644 --- a/packages/shared/mocks/src/crypto.ts +++ b/packages/shared/common/__tests__/setupCrypto.ts @@ -1,4 +1,4 @@ -import type { Hasher } from '@common'; +import { Hasher } from '../src/api'; export const setupCrypto = () => { let counter = 0; diff --git a/packages/shared/common/package.json b/packages/shared/common/package.json index 19de8318f..8c2f2e666 100644 --- a/packages/shared/common/package.json +++ b/packages/shared/common/package.json @@ -30,7 +30,6 @@ }, "license": "Apache-2.0", "devDependencies": { - "@launchdarkly/private-js-mocks": "0.0.1", "@trivago/prettier-plugin-sort-imports": "^4.1.1", "@types/jest": "^29.4.0", "@typescript-eslint/eslint-plugin": "^6.20.0", diff --git a/packages/shared/common/rollup.config.js b/packages/shared/common/rollup.config.js new file mode 100644 index 000000000..d8fb93ed9 --- /dev/null +++ b/packages/shared/common/rollup.config.js @@ -0,0 +1,43 @@ +import common from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; +import resolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; + +const getSharedConfig = (format, file) => ({ + input: 'src/index.ts', + output: [ + { + format: format, + sourcemap: true, + file: file, + }, + ], + onwarn: (warning) => { + if (warning.code !== 'CIRCULAR_DEPENDENCY') { + console.error(`(!) ${warning.message}`); + } + }, +}); + +export default [ + { + ...getSharedConfig('es', 'dist/index.es.js'), + plugins: [ + typescript({ + module: 'esnext', + }), + common({ + transformMixedEsModules: true, + esmExternals: true, + }), + resolve(), + terser(), + json(), + ], + }, + { + ...getSharedConfig('cjs', 'dist/index.cjs.js'), + plugins: [typescript(), common(), resolve(), terser(), json()], + }, +]; diff --git a/packages/shared/mocks/CHANGELOG.md b/packages/shared/mocks/CHANGELOG.md deleted file mode 100644 index cc1a5afb9..000000000 --- a/packages/shared/mocks/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -# Changelog - -All notable changes to `@launchdarkly/private-js-mocks` will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). diff --git a/packages/shared/mocks/LICENSE b/packages/shared/mocks/LICENSE deleted file mode 100644 index ab8bd335b..000000000 --- a/packages/shared/mocks/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2023 Catamorphic, Co. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/packages/shared/mocks/README.md b/packages/shared/mocks/README.md deleted file mode 100644 index 2d178b398..000000000 --- a/packages/shared/mocks/README.md +++ /dev/null @@ -1,131 +0,0 @@ -# LaunchDarkly SDK JavaScript Mocks - -[![Actions Status][mocks-ci-badge]][mocks-ci] - -> [!CAUTION] -> Internal use only. -> This project contains JavaScript mocks that are consumed in unit tests in client-side and server-side JavaScript SDKs. - -## Installation - -This package is not published publicly. To use it internally, add the following line to your project's package.json -devDependencies. yarn workspace has been setup to recognize this package so this dependency should automatically work: - -```bash - "devDependencies": { - "@launchdarkly/private-js-mocks": "0.0.1", - ... -``` - -Then in your jest config add `@launchdarkly/private-js-mocks/setup` to setupFilesAfterEnv: - -```js -// jest.config.js or jest.config.json -module.exports = { - setupFilesAfterEnv: ['@launchdarkly/private-js-mocks/setup'], - ... -} -``` - -## Usage - -> [!IMPORTANT] -> basicPlatform and clientContext must be used inside a test because it's setup before each test. - -- `basicPlatform`: a concrete but basic implementation of [Platform](https://github.com/launchdarkly/js-core/blob/main/packages/shared/common/src/api/platform/Platform.ts). This is setup beforeEach so it must be used inside a test. - -- `clientContext`: ClientContext object including `basicPlatform` above. This is setup beforeEach so it must be used inside a test as well. - -- `hasher`: a Hasher object returned by `Crypto.createHash`. All functions in this object are jest mocks. This is exported - separately as a top level export because `Crypto` does not expose this publicly and we want to respect that. - -## Example - -```tsx -import { basicPlatform, clientContext, hasher } from '@launchdarkly/private-js-mocks'; - -// DOES NOT WORK: crypto is undefined because basicPlatform must be inside a test -// because it's setup by the package in beforeEach. -const { crypto } = basicPlatform; // DON'T DO THIS HERE - -// DOES NOT WORK: clientContext must be used inside a test. Otherwise all properties -// of it will be undefined. -const { - basicConfiguration: { serviceEndpoints, tags }, - platform: { info }, -} = clientContext; // DON'T DO THIS HERE - -describe('button', () => { - // DOES NOT WORK: again must be inside an actual test. At the test suite, - // level, beforeEach has not been run. - const { crypto } = basicPlatform; // DON'T DO THIS HERE - - // DO THIS - let crypto: Crypto; - let info: Info; - let serviceEndpoints: ServiceEndpoints; - let tags: ApplicationTags; - - beforeEach(() => { - // WORKS: basicPlatform and clientContext have been setup by the package. - ({ crypto, info } = basicPlatform); - - // WORKS - ({ - basicConfiguration: { serviceEndpoints, tags }, - platform: { info }, - } = clientContext); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('hashes the correct string', () => { - // arrange - const bucketer = new Bucketer(crypto); - - // act - const [bucket, hadContext] = bucketer.bucket(); - - // assert - // WORKS - expect(crypto.createHash).toHaveBeenCalled(); - - // WORKS: alternatively you can just use the full path to access the properties - // of basicPlatform - expect(basicPlatform.crypto.createHash).toHaveBeenCalled(); - - // GOTCHA: hasher is a separte import from crypto to respect - // the public Crypto interface. - expect(hasher.update).toHaveBeenCalledWith(expected); - expect(hasher.digest).toHaveBeenCalledWith('hex'); - }); -}); -``` - -## Developing this package - -If you make changes to this package, you'll need to run `yarn build` in the `mocks` directory for changes to take effect. - -## Contributing - -See [Contributing](../shared/CONTRIBUTING.md). - -## About LaunchDarkly - -- LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can: - - Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases. - - Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?). - - Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file. - - Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan). - - Disable parts of your application to facilitate maintenance, without taking everything offline. -- LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Check out [our documentation](https://docs.launchdarkly.com/sdk) for a complete list. -- Explore LaunchDarkly - - [launchdarkly.com](https://www.launchdarkly.com/ 'LaunchDarkly Main Website') for more information - - [docs.launchdarkly.com](https://docs.launchdarkly.com/ 'LaunchDarkly Documentation') for our documentation and SDK reference guides - - [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ 'LaunchDarkly API Documentation') for our API documentation - - [blog.launchdarkly.com](https://blog.launchdarkly.com/ 'LaunchDarkly Blog Documentation') for the latest product updates - -[mocks-ci-badge]: https://github.com/launchdarkly/js-core/actions/workflows/mocks.yml/badge.svg -[mocks-ci]: https://github.com/launchdarkly/js-core/actions/workflows/mocks.yml diff --git a/packages/shared/mocks/jest.config.js b/packages/shared/mocks/jest.config.js deleted file mode 100644 index 6753062cc..000000000 --- a/packages/shared/mocks/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - transform: { '^.+\\.ts?$': 'ts-jest' }, - testMatch: ['**/*.test.ts?(x)'], - testEnvironment: 'node', - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], - collectCoverageFrom: ['src/**/*.ts'], -}; diff --git a/packages/shared/mocks/package.json b/packages/shared/mocks/package.json deleted file mode 100644 index 4ffdc0ba9..000000000 --- a/packages/shared/mocks/package.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "name": "@launchdarkly/private-js-mocks", - "private": true, - "version": "0.0.1", - "type": "commonjs", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": "./dist/index.js" - }, - "homepage": "https://github.com/launchdarkly/js-core/tree/main/packages/shared/common", - "repository": { - "type": "git", - "url": "https://github.com/launchdarkly/js-core.git" - }, - "description": "LaunchDarkly SDK for JavaScript - mocks", - "files": [ - "dist" - ], - "keywords": [ - "mocks", - "unit", - "tests", - "launchdarkly", - "js", - "client" - ], - "scripts": { - "test": "", - "build-types": "yarn workspace @launchdarkly/js-sdk-common build-types", - "build": "yarn build-types && npx tsc", - "clean": "npx tsc --build --clean", - "lint": "npx eslint --ext .ts", - "lint:fix": "yarn run lint -- --fix" - }, - "license": "Apache-2.0", - "devDependencies": { - "@trivago/prettier-plugin-sort-imports": "^4.2.0", - "@types/jest": "^29.5.5", - "@typescript-eslint/eslint-plugin": "^6.20.0", - "@typescript-eslint/parser": "^6.20.0", - "eslint": "^8.50.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-airbnb-typescript": "^17.1.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-import": "^2.28.1", - "eslint-plugin-jest": "^27.6.3", - "eslint-plugin-prettier": "^5.0.0", - "jest": "^29.7.0", - "launchdarkly-js-test-helpers": "^2.2.0", - "prettier": "^3.0.3", - "ts-jest": "^29.0.5", - "typescript": "^5.2.2" - } -} diff --git a/packages/shared/mocks/src/index.ts b/packages/shared/mocks/src/index.ts deleted file mode 100644 index 0fffd6117..000000000 --- a/packages/shared/mocks/src/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import ContextDeduplicator from './contextDeduplicator'; -import { MockEventProcessor, setupMockEventProcessor } from './eventProcessor'; -import { createLogger } from './logger'; -import { createBasicPlatform } from './platform'; -import { MockStreamingProcessor, setupMockStreamingProcessor } from './streamingProcessor'; - -export { - createLogger, - ContextDeduplicator, - MockEventProcessor, - setupMockEventProcessor, - MockStreamingProcessor, - setupMockStreamingProcessor, - createBasicPlatform, -}; diff --git a/packages/shared/mocks/src/logger.ts b/packages/shared/mocks/src/logger.ts deleted file mode 100644 index 80cb3e728..000000000 --- a/packages/shared/mocks/src/logger.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const createLogger = () => ({ - error: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - debug: jest.fn(), -}); diff --git a/packages/shared/mocks/tsconfig.eslint.json b/packages/shared/mocks/tsconfig.eslint.json deleted file mode 100644 index 56c9b3830..000000000 --- a/packages/shared/mocks/tsconfig.eslint.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["/**/*.ts"], - "exclude": ["node_modules"] -} diff --git a/packages/shared/mocks/tsconfig.json b/packages/shared/mocks/tsconfig.json deleted file mode 100644 index 93b5f38e5..000000000 --- a/packages/shared/mocks/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "rootDir": "src", - "outDir": "dist", - "target": "ES2017", - "lib": ["es6"], - "module": "commonjs", - "strict": true, - "noImplicitOverride": true, - // Needed for CommonJS modules: markdown-it, fs-extra - "allowSyntheticDefaultImports": true, - "sourceMap": true, - "declaration": true, - "declarationMap": true, // enables importers to jump to source - "stripInternal": true, - "paths": { - "@common": ["../common"] - } - }, - "exclude": ["**/*.test.ts", "dist", "node_modules", "__tests__"], - "references": [ - { - "path": "../common" - } - ] -} diff --git a/packages/shared/mocks/tsconfig.ref.json b/packages/shared/mocks/tsconfig.ref.json deleted file mode 100644 index 0c86b2c55..000000000 --- a/packages/shared/mocks/tsconfig.ref.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["src/**/*"], - "compilerOptions": { - "composite": true - } -} diff --git a/packages/shared/sdk-client/__tests__/LDClientImpl.events.test.ts b/packages/shared/sdk-client/__tests__/LDClientImpl.events.test.ts index 3b6c6a084..ca7b006e0 100644 --- a/packages/shared/sdk-client/__tests__/LDClientImpl.events.test.ts +++ b/packages/shared/sdk-client/__tests__/LDClientImpl.events.test.ts @@ -4,17 +4,15 @@ import { clone, internal, LDContext, + LDLogger, subsystem, } from '@launchdarkly/js-sdk-common'; -import { - createBasicPlatform, - createLogger, - MockEventProcessor, -} from '@launchdarkly/private-js-mocks'; import LDClientImpl from '../src/LDClientImpl'; import { Flags } from '../src/types'; +import { createBasicPlatform } from './createBasicPlatform'; import * as mockResponseJson from './evaluation/mockResponse.json'; +import { MockEventProcessor } from './eventProcessor'; import { MockEventSource } from './streaming/LDClientImpl.mocks'; import { makeTestDataManagerFactory } from './TestDataManager'; @@ -22,16 +20,21 @@ type InputCustomEvent = internal.InputCustomEvent; type InputIdentifyEvent = internal.InputIdentifyEvent; let mockPlatform: ReturnType; -let logger: ReturnType; +let logger: LDLogger; beforeEach(() => { mockPlatform = createBasicPlatform(); - logger = createLogger(); + logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; }); jest.mock('@launchdarkly/js-sdk-common', () => { const actual = jest.requireActual('@launchdarkly/js-sdk-common'); - const m = jest.requireActual('@launchdarkly/private-js-mocks'); + const m = jest.requireActual('./eventProcessor'); return { ...actual, ...{ diff --git a/packages/shared/sdk-client/__tests__/LDClientImpl.storage.test.ts b/packages/shared/sdk-client/__tests__/LDClientImpl.storage.test.ts index 863dcb5c0..4fe6fe50f 100644 --- a/packages/shared/sdk-client/__tests__/LDClientImpl.storage.test.ts +++ b/packages/shared/sdk-client/__tests__/LDClientImpl.storage.test.ts @@ -1,20 +1,25 @@ -import { AutoEnvAttributes, clone, type LDContext } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform, createLogger } from '@launchdarkly/private-js-mocks'; +import { AutoEnvAttributes, clone, type LDContext, LDLogger } from '@launchdarkly/js-sdk-common'; import { toMulti } from '../src/context/addAutoEnv'; import LDClientImpl from '../src/LDClientImpl'; import LDEmitter from '../src/LDEmitter'; import { Flags, PatchFlag } from '../src/types'; +import { createBasicPlatform } from './createBasicPlatform'; import * as mockResponseJson from './evaluation/mockResponse.json'; import { MockEventSource } from './streaming/LDClientImpl.mocks'; import { makeTestDataManagerFactory } from './TestDataManager'; let mockPlatform: ReturnType; -let logger: ReturnType; +let logger: LDLogger; beforeEach(() => { mockPlatform = createBasicPlatform(); - logger = createLogger(); + logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; }); const testSdkKey = 'test-sdk-key'; diff --git a/packages/shared/sdk-client/__tests__/LDClientImpl.test.ts b/packages/shared/sdk-client/__tests__/LDClientImpl.test.ts index 44b169e36..f6308e9cb 100644 --- a/packages/shared/sdk-client/__tests__/LDClientImpl.test.ts +++ b/packages/shared/sdk-client/__tests__/LDClientImpl.test.ts @@ -1,9 +1,9 @@ -import { AutoEnvAttributes, clone, Hasher, LDContext } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform, createLogger } from '@launchdarkly/private-js-mocks'; +import { AutoEnvAttributes, clone, Hasher, LDContext, LDLogger } from '@launchdarkly/js-sdk-common'; import { DataSourceState } from '../src/datasource/DataSourceStatus'; import LDClientImpl from '../src/LDClientImpl'; import { Flags } from '../src/types'; +import { createBasicPlatform } from './createBasicPlatform'; import * as mockResponseJson from './evaluation/mockResponse.json'; import { MockEventSource } from './streaming/LDClientImpl.mocks'; import { makeTestDataManagerFactory } from './TestDataManager'; @@ -32,7 +32,7 @@ describe('sdk-client object', () => { let simulatedEvents: { data?: any }[] = []; let defaultPutResponse: Flags; let mockPlatform: ReturnType; - let logger: ReturnType; + let logger: LDLogger; function onDataSourceChangePromise(numToAwait: number) { let countdown = numToAwait; @@ -49,7 +49,12 @@ describe('sdk-client object', () => { beforeEach(() => { mockPlatform = createBasicPlatform(); - logger = createLogger(); + logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; defaultPutResponse = clone(mockResponseJson); mockPlatform.crypto.randomUUID.mockReturnValue('random1'); const hasher = { diff --git a/packages/shared/sdk-client/__tests__/LDClientImpl.timeout.test.ts b/packages/shared/sdk-client/__tests__/LDClientImpl.timeout.test.ts index fd2bc60f5..51a1503e7 100644 --- a/packages/shared/sdk-client/__tests__/LDClientImpl.timeout.test.ts +++ b/packages/shared/sdk-client/__tests__/LDClientImpl.timeout.test.ts @@ -1,19 +1,24 @@ -import { AutoEnvAttributes, clone, LDContext } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform, createLogger } from '@launchdarkly/private-js-mocks'; +import { AutoEnvAttributes, clone, LDContext, LDLogger } from '@launchdarkly/js-sdk-common'; import { toMulti } from '../src/context/addAutoEnv'; import LDClientImpl from '../src/LDClientImpl'; import { Flags } from '../src/types'; +import { createBasicPlatform } from './createBasicPlatform'; import * as mockResponseJson from './evaluation/mockResponse.json'; import { MockEventSource } from './streaming/LDClientImpl.mocks'; import { makeTestDataManagerFactory } from './TestDataManager'; let mockPlatform: ReturnType; -let logger: ReturnType; +let logger: LDLogger; beforeEach(() => { mockPlatform = createBasicPlatform(); - logger = createLogger(); + logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; }); const testSdkKey = 'test-sdk-key'; diff --git a/packages/shared/sdk-client/__tests__/LDClientImpl.variation.test.ts b/packages/shared/sdk-client/__tests__/LDClientImpl.variation.test.ts index c360e33bd..257d1a10a 100644 --- a/packages/shared/sdk-client/__tests__/LDClientImpl.variation.test.ts +++ b/packages/shared/sdk-client/__tests__/LDClientImpl.variation.test.ts @@ -1,18 +1,29 @@ -import { AutoEnvAttributes, clone, Context, LDContext } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform, createLogger } from '@launchdarkly/private-js-mocks'; +import { + AutoEnvAttributes, + clone, + Context, + LDContext, + LDLogger, +} from '@launchdarkly/js-sdk-common'; import LDClientImpl from '../src/LDClientImpl'; import { Flags } from '../src/types'; +import { createBasicPlatform } from './createBasicPlatform'; import * as mockResponseJson from './evaluation/mockResponse.json'; import { MockEventSource } from './streaming/LDClientImpl.mocks'; import { makeTestDataManagerFactory } from './TestDataManager'; let mockPlatform: ReturnType; -let logger: ReturnType; +let logger: LDLogger; beforeEach(() => { mockPlatform = createBasicPlatform(); - logger = createLogger(); + logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; }); const testSdkKey = 'test-sdk-key'; diff --git a/packages/shared/sdk-client/__tests__/context/addAutoEnv.test.ts b/packages/shared/sdk-client/__tests__/context/addAutoEnv.test.ts index b686a3c1b..cc0759d86 100644 --- a/packages/shared/sdk-client/__tests__/context/addAutoEnv.test.ts +++ b/packages/shared/sdk-client/__tests__/context/addAutoEnv.test.ts @@ -2,10 +2,10 @@ import { Crypto, Info, type LDContext, + LDLogger, LDMultiKindContext, LDUser, } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform, createLogger } from '@launchdarkly/private-js-mocks'; import { Configuration, ConfigurationImpl } from '../../src/configuration'; import { @@ -14,13 +14,19 @@ import { addDeviceInfo, toMulti, } from '../../src/context/addAutoEnv'; +import { createBasicPlatform } from '../createBasicPlatform'; let mockPlatform: ReturnType; -let logger: ReturnType; +let logger: LDLogger; beforeEach(() => { mockPlatform = createBasicPlatform(); - logger = createLogger(); + logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; }); describe('automatic environment attributes', () => { diff --git a/packages/shared/sdk-client/__tests__/context/ensureKey.test.ts b/packages/shared/sdk-client/__tests__/context/ensureKey.test.ts index 2faf60177..60efb0e0a 100644 --- a/packages/shared/sdk-client/__tests__/context/ensureKey.test.ts +++ b/packages/shared/sdk-client/__tests__/context/ensureKey.test.ts @@ -5,9 +5,9 @@ import type { LDMultiKindContext, LDUser, } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import { ensureKey } from '../../src/context/ensureKey'; +import { createBasicPlatform } from '../createBasicPlatform'; let mockPlatform: ReturnType; diff --git a/packages/shared/sdk-client/__tests__/createBasicPlatform.ts b/packages/shared/sdk-client/__tests__/createBasicPlatform.ts new file mode 100644 index 000000000..17178f061 --- /dev/null +++ b/packages/shared/sdk-client/__tests__/createBasicPlatform.ts @@ -0,0 +1,59 @@ +import { PlatformData, SdkData } from '@launchdarkly/js-sdk-common'; + +import { setupCrypto } from './setupCrypto'; + +const setupInfo = () => ({ + platformData: jest.fn( + (): PlatformData => ({ + os: { + name: 'An OS', + version: '1.0.1', + arch: 'An Arch', + }, + name: 'The SDK Name', + additional: { + nodeVersion: '42', + }, + ld_application: { + key: '', + envAttributesVersion: '1.0', + id: 'com.testapp.ld', + name: 'LDApplication.TestApp', + version: '1.1.1', + }, + ld_device: { + key: '', + envAttributesVersion: '1.0', + os: { name: 'Another OS', version: '99', family: 'orange' }, + manufacturer: 'coconut', + }, + }), + ), + sdkData: jest.fn( + (): SdkData => ({ + name: 'An SDK', + version: '2.0.2', + userAgentBase: 'TestUserAgent', + wrapperName: 'Rapper', + wrapperVersion: '1.2.3', + }), + ), +}); + +export const createBasicPlatform = () => ({ + encoding: { + btoa: (s: string) => Buffer.from(s).toString('base64'), + }, + info: setupInfo(), + crypto: setupCrypto(), + requests: { + fetch: jest.fn(), + createEventSource: jest.fn(), + getEventSourceCapabilities: jest.fn(), + }, + storage: { + get: jest.fn(), + set: jest.fn(), + clear: jest.fn(), + }, +}); diff --git a/packages/shared/mocks/src/eventProcessor.ts b/packages/shared/sdk-client/__tests__/eventProcessor.ts similarity index 85% rename from packages/shared/mocks/src/eventProcessor.ts rename to packages/shared/sdk-client/__tests__/eventProcessor.ts index ffa577e16..30fa70802 100644 --- a/packages/shared/mocks/src/eventProcessor.ts +++ b/packages/shared/sdk-client/__tests__/eventProcessor.ts @@ -1,4 +1,4 @@ -import type { ClientContext, internal, subsystem } from '@common'; +import { ClientContext, internal, subsystem } from '@launchdarkly/js-sdk-common'; export const MockEventProcessor = jest.fn(); diff --git a/packages/shared/sdk-client/__tests__/setupCrypto.ts b/packages/shared/sdk-client/__tests__/setupCrypto.ts new file mode 100644 index 000000000..fc8d0b460 --- /dev/null +++ b/packages/shared/sdk-client/__tests__/setupCrypto.ts @@ -0,0 +1,20 @@ +import { Hasher } from '@launchdarkly/js-sdk-common'; + +export const setupCrypto = () => { + let counter = 0; + const hasher = { + update: jest.fn((): Hasher => hasher), + digest: jest.fn(() => '1234567890123456'), + }; + + return { + createHash: jest.fn(() => hasher), + createHmac: jest.fn(), + randomUUID: jest.fn(() => { + counter += 1; + // Will provide a unique value for tests. + // Very much not a UUID of course. + return `${counter}`; + }), + }; +}; diff --git a/packages/shared/sdk-client/__tests__/storage/getOrGenerateKey.test.ts b/packages/shared/sdk-client/__tests__/storage/getOrGenerateKey.test.ts index 8065871ff..589f4e585 100644 --- a/packages/shared/sdk-client/__tests__/storage/getOrGenerateKey.test.ts +++ b/packages/shared/sdk-client/__tests__/storage/getOrGenerateKey.test.ts @@ -1,7 +1,7 @@ import { Crypto, Storage } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import { getOrGenerateKey } from '../../src/storage/getOrGenerateKey'; +import { createBasicPlatform } from '../createBasicPlatform'; let mockPlatform: ReturnType; diff --git a/packages/shared/sdk-client/__tests__/streaming/StreamingProcessor.test.ts b/packages/shared/sdk-client/__tests__/streaming/StreamingProcessor.test.ts index 3ace5342b..f1d4eda76 100644 --- a/packages/shared/sdk-client/__tests__/streaming/StreamingProcessor.test.ts +++ b/packages/shared/sdk-client/__tests__/streaming/StreamingProcessor.test.ts @@ -5,15 +5,16 @@ import { EventName, Info, internal, + LDLogger, LDStreamingError, Platform, ProcessStreamResponse, } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform, createLogger } from '@launchdarkly/private-js-mocks'; import { StreamingDataSourceConfig, StreamingProcessor } from '../../src/streaming'; +import { createBasicPlatform } from '../createBasicPlatform'; -let logger: ReturnType; +let logger: LDLogger; const serviceEndpoints = { events: '', @@ -69,7 +70,12 @@ function getStreamingDataSourceConfig( beforeEach(() => { basicPlatform = createBasicPlatform(); - logger = createLogger(); + logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; }); const createMockEventSource = (streamUri: string = '', options: any = {}) => ({ diff --git a/packages/shared/sdk-client/package.json b/packages/shared/sdk-client/package.json index 9d79dc224..c651eda60 100644 --- a/packages/shared/sdk-client/package.json +++ b/packages/shared/sdk-client/package.json @@ -33,7 +33,6 @@ "@launchdarkly/js-sdk-common": "2.9.0" }, "devDependencies": { - "@launchdarkly/private-js-mocks": "0.0.1", "@testing-library/dom": "^9.3.1", "@testing-library/jest-dom": "^5.16.5", "@types/jest": "^29.5.3", diff --git a/packages/shared/sdk-client/rollup.config.js b/packages/shared/sdk-client/rollup.config.js new file mode 100644 index 000000000..d8fb93ed9 --- /dev/null +++ b/packages/shared/sdk-client/rollup.config.js @@ -0,0 +1,43 @@ +import common from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; +import resolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; + +const getSharedConfig = (format, file) => ({ + input: 'src/index.ts', + output: [ + { + format: format, + sourcemap: true, + file: file, + }, + ], + onwarn: (warning) => { + if (warning.code !== 'CIRCULAR_DEPENDENCY') { + console.error(`(!) ${warning.message}`); + } + }, +}); + +export default [ + { + ...getSharedConfig('es', 'dist/index.es.js'), + plugins: [ + typescript({ + module: 'esnext', + }), + common({ + transformMixedEsModules: true, + esmExternals: true, + }), + resolve(), + terser(), + json(), + ], + }, + { + ...getSharedConfig('cjs', 'dist/index.cjs.js'), + plugins: [typescript(), common(), resolve(), terser(), json()], + }, +]; diff --git a/packages/shared/sdk-server-edge/__tests__/api/LDClient.test.ts b/packages/shared/sdk-server-edge/__tests__/api/LDClient.test.ts index 617375778..26bad83fa 100644 --- a/packages/shared/sdk-server-edge/__tests__/api/LDClient.test.ts +++ b/packages/shared/sdk-server-edge/__tests__/api/LDClient.test.ts @@ -1,7 +1,7 @@ import { internal } from '@launchdarkly/js-server-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import LDClient from '../../src/api/LDClient'; +import { createBasicPlatform } from '../createBasicPlatform'; jest.mock('@launchdarkly/js-sdk-common', () => { const actual = jest.requireActual('@launchdarkly/js-sdk-common'); diff --git a/packages/shared/sdk-server-edge/__tests__/api/createCallbacks.test.ts b/packages/shared/sdk-server-edge/__tests__/api/createCallbacks.test.ts index 957542a6c..665f3f19b 100644 --- a/packages/shared/sdk-server-edge/__tests__/api/createCallbacks.test.ts +++ b/packages/shared/sdk-server-edge/__tests__/api/createCallbacks.test.ts @@ -1,16 +1,19 @@ import { EventEmitter } from 'node:events'; -import { noop } from '@launchdarkly/js-server-sdk-common'; -import { createLogger } from '@launchdarkly/private-js-mocks'; +import { LDLogger, noop } from '@launchdarkly/js-server-sdk-common'; import createCallbacks from '../../src/api/createCallbacks'; -let logger: ReturnType; +let logger: LDLogger; beforeEach(() => { - logger = createLogger(); + logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; }); - describe('createCallbacks', () => { let emitter: EventEmitter; const err = new Error('test error'); diff --git a/packages/shared/sdk-server-edge/__tests__/createBasicPlatform.ts b/packages/shared/sdk-server-edge/__tests__/createBasicPlatform.ts new file mode 100644 index 000000000..e5139ccec --- /dev/null +++ b/packages/shared/sdk-server-edge/__tests__/createBasicPlatform.ts @@ -0,0 +1,59 @@ +import { PlatformData, SdkData } from '@launchdarkly/js-server-sdk-common'; + +import { setupCrypto } from './setupCrypto'; + +const setupInfo = () => ({ + platformData: jest.fn( + (): PlatformData => ({ + os: { + name: 'An OS', + version: '1.0.1', + arch: 'An Arch', + }, + name: 'The SDK Name', + additional: { + nodeVersion: '42', + }, + ld_application: { + key: '', + envAttributesVersion: '1.0', + id: 'com.testapp.ld', + name: 'LDApplication.TestApp', + version: '1.1.1', + }, + ld_device: { + key: '', + envAttributesVersion: '1.0', + os: { name: 'Another OS', version: '99', family: 'orange' }, + manufacturer: 'coconut', + }, + }), + ), + sdkData: jest.fn( + (): SdkData => ({ + name: 'An SDK', + version: '2.0.2', + userAgentBase: 'TestUserAgent', + wrapperName: 'Rapper', + wrapperVersion: '1.2.3', + }), + ), +}); + +export const createBasicPlatform = () => ({ + encoding: { + btoa: (s: string) => Buffer.from(s).toString('base64'), + }, + info: setupInfo(), + crypto: setupCrypto(), + requests: { + fetch: jest.fn(), + createEventSource: jest.fn(), + getEventSourceCapabilities: jest.fn(), + }, + storage: { + get: jest.fn(), + set: jest.fn(), + clear: jest.fn(), + }, +}); diff --git a/packages/shared/sdk-server-edge/__tests__/setupCrypto.ts b/packages/shared/sdk-server-edge/__tests__/setupCrypto.ts new file mode 100644 index 000000000..bdf62024f --- /dev/null +++ b/packages/shared/sdk-server-edge/__tests__/setupCrypto.ts @@ -0,0 +1,20 @@ +import { Hasher } from '@launchdarkly/js-server-sdk-common'; + +export const setupCrypto = () => { + let counter = 0; + const hasher = { + update: jest.fn((): Hasher => hasher), + digest: jest.fn(() => '1234567890123456'), + }; + + return { + createHash: jest.fn(() => hasher), + createHmac: jest.fn(), + randomUUID: jest.fn(() => { + counter += 1; + // Will provide a unique value for tests. + // Very much not a UUID of course. + return `${counter}`; + }), + }; +}; diff --git a/packages/shared/sdk-server-edge/package.json b/packages/shared/sdk-server-edge/package.json index 25fcb5538..5c4044f9c 100644 --- a/packages/shared/sdk-server-edge/package.json +++ b/packages/shared/sdk-server-edge/package.json @@ -40,7 +40,6 @@ "crypto-js": "^4.1.1" }, "devDependencies": { - "@launchdarkly/private-js-mocks": "0.0.1", "@trivago/prettier-plugin-sort-imports": "^4.1.1", "@types/crypto-js": "^4.1.1", "@types/jest": "^29.5.0", diff --git a/packages/shared/sdk-server/__tests__/LDClient.allFlags.test.ts b/packages/shared/sdk-server/__tests__/LDClient.allFlags.test.ts index 9903eb51d..585c6a9b5 100644 --- a/packages/shared/sdk-server/__tests__/LDClient.allFlags.test.ts +++ b/packages/shared/sdk-server/__tests__/LDClient.allFlags.test.ts @@ -1,7 +1,6 @@ -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; - import { LDClientImpl } from '../src'; import TestData from '../src/integrations/test_data/TestData'; +import { createBasicPlatform } from './createBasicPlatform'; import TestLogger, { LogLevel } from './Logger'; import makeCallbacks from './makeCallbacks'; @@ -16,7 +15,7 @@ describe('given an LDClient with test data', () => { logger = new TestLogger(); td = new TestData(); client = new LDClientImpl( - 'sdk-key', + 'sdk-key-all-flags-test-data', createBasicPlatform(), { updateProcessor: td.getFactory(), @@ -280,7 +279,7 @@ describe('given an offline client', () => { logger = new TestLogger(); td = new TestData(); client = new LDClientImpl( - 'sdk-key', + 'sdk-key-all-flags-offline', createBasicPlatform(), { offline: true, diff --git a/packages/shared/sdk-server/__tests__/LDClient.evaluation.test.ts b/packages/shared/sdk-server/__tests__/LDClient.evaluation.test.ts index 2ef0d660d..a57dc8599 100644 --- a/packages/shared/sdk-server/__tests__/LDClient.evaluation.test.ts +++ b/packages/shared/sdk-server/__tests__/LDClient.evaluation.test.ts @@ -1,31 +1,14 @@ import { subsystem } from '@launchdarkly/js-sdk-common'; -import { - createBasicPlatform, - MockStreamingProcessor, - setupMockStreamingProcessor, -} from '@launchdarkly/private-js-mocks'; import { LDClientImpl, LDFeatureStore } from '../src'; import TestData from '../src/integrations/test_data/TestData'; import AsyncStoreFacade from '../src/store/AsyncStoreFacade'; import InMemoryFeatureStore from '../src/store/InMemoryFeatureStore'; import VersionedDataKinds from '../src/store/VersionedDataKinds'; +import { createBasicPlatform } from './createBasicPlatform'; import TestLogger, { LogLevel } from './Logger'; import makeCallbacks from './makeCallbacks'; -jest.mock('@launchdarkly/js-sdk-common', () => { - const actual = jest.requireActual('@launchdarkly/js-sdk-common'); - return { - ...actual, - ...{ - internal: { - ...actual.internal, - StreamingProcessor: MockStreamingProcessor, - }, - }, - }; -}); - const defaultUser = { key: 'user' }; describe('given an LDClient with test data', () => { @@ -35,7 +18,7 @@ describe('given an LDClient with test data', () => { beforeEach(async () => { td = new TestData(); client = new LDClientImpl( - 'sdk-key', + 'sdk-key-evaluation-test-data', createBasicPlatform(), { updateProcessor: td.getFactory(), @@ -281,7 +264,7 @@ describe('given an offline client', () => { logger = new TestLogger(); td = new TestData(); client = new LDClientImpl( - 'sdk-key', + 'sdk-key-evaluation-offline', createBasicPlatform(), { offline: true, @@ -345,7 +328,7 @@ describe('given a client and store that are uninitialized', () => { }); client = new LDClientImpl( - 'sdk-key', + 'sdk-key-evaluation-uninitialized-store', createBasicPlatform(), { updateProcessor: new InertUpdateProcessor(), @@ -392,14 +375,18 @@ describe('given a client that is un-initialized and store that is initialized', }, segments: {}, }); - setupMockStreamingProcessor(true); client = new LDClientImpl( - 'sdk-key', + 'sdk-key-initialized-store', createBasicPlatform(), { sendEvents: false, featureStore: store, + updateProcessor: () => ({ + start: jest.fn(), + stop: jest.fn(), + close: jest.fn(), + }), }, makeCallbacks(true), ); diff --git a/packages/shared/sdk-server/__tests__/LDClient.events.test.ts b/packages/shared/sdk-server/__tests__/LDClient.events.test.ts index 075fb5802..5d1cdd84e 100644 --- a/packages/shared/sdk-server/__tests__/LDClient.events.test.ts +++ b/packages/shared/sdk-server/__tests__/LDClient.events.test.ts @@ -1,10 +1,10 @@ import { AsyncQueue } from 'launchdarkly-js-test-helpers'; import { Context, internal } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import { LDClientImpl } from '../src'; import TestData from '../src/integrations/test_data/TestData'; +import { createBasicPlatform } from './createBasicPlatform'; import TestLogger, { LogLevel } from './Logger'; import makeCallbacks from './makeCallbacks'; @@ -29,7 +29,7 @@ describe('given a client with mock event processor', () => { td = new TestData(); client = new LDClientImpl( - 'sdk-key', + 'sdk-key-events', createBasicPlatform(), { updateProcessor: td.getFactory(), diff --git a/packages/shared/sdk-server/__tests__/LDClient.hooks.test.ts b/packages/shared/sdk-server/__tests__/LDClient.hooks.test.ts index b108a71d2..929d8f67a 100644 --- a/packages/shared/sdk-server/__tests__/LDClient.hooks.test.ts +++ b/packages/shared/sdk-server/__tests__/LDClient.hooks.test.ts @@ -1,8 +1,7 @@ -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; - import { LDClientImpl, LDMigrationStage } from '../src'; import Reasons from '../src/evaluation/Reasons'; import TestData from '../src/integrations/test_data/TestData'; +import { createBasicPlatform } from './createBasicPlatform'; import { TestHook } from './hooks/TestHook'; import TestLogger from './Logger'; import makeCallbacks from './makeCallbacks'; @@ -20,7 +19,7 @@ describe('given an LDClient with test data', () => { testHook = new TestHook(); td = new TestData(); client = new LDClientImpl( - 'sdk-key', + 'sdk-key-hooks-test-data', createBasicPlatform(), { updateProcessor: td.getFactory(), @@ -351,7 +350,7 @@ it('can add a hook after initialization', async () => { const logger = new TestLogger(); const td = new TestData(); const client = new LDClientImpl( - 'sdk-key', + 'sdk-key-hook-after-init', createBasicPlatform(), { updateProcessor: td.getFactory(), diff --git a/packages/shared/sdk-server/__tests__/LDClient.migrations.test.ts b/packages/shared/sdk-server/__tests__/LDClient.migrations.test.ts index d494245a7..3a5df82a4 100644 --- a/packages/shared/sdk-server/__tests__/LDClient.migrations.test.ts +++ b/packages/shared/sdk-server/__tests__/LDClient.migrations.test.ts @@ -1,8 +1,7 @@ -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; - import { LDClientImpl, LDMigrationStage } from '../src'; import TestData from '../src/integrations/test_data/TestData'; import { LDClientCallbacks } from '../src/LDClientImpl'; +import { createBasicPlatform } from './createBasicPlatform'; /** * Basic callback handler that records errors for tests. @@ -33,7 +32,7 @@ describe('given an LDClient with test data', () => { td = new TestData(); [errors, callbacks] = makeCallbacks(); client = new LDClientImpl( - 'sdk-key', + 'sdk-key-migration', createBasicPlatform(), { updateProcessor: td.getFactory(), diff --git a/packages/shared/sdk-server/__tests__/LDClientImpl.bigSegments.test.ts b/packages/shared/sdk-server/__tests__/LDClientImpl.bigSegments.test.ts index 471121256..9015cc338 100644 --- a/packages/shared/sdk-server/__tests__/LDClientImpl.bigSegments.test.ts +++ b/packages/shared/sdk-server/__tests__/LDClientImpl.bigSegments.test.ts @@ -1,11 +1,11 @@ import { Crypto, Hasher, Hmac } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import { LDBigSegmentsOptions } from '../src'; import { BigSegmentStore } from '../src/api/interfaces'; import makeBigSegmentRef from '../src/evaluation/makeBigSegmentRef'; import TestData from '../src/integrations/test_data/TestData'; import LDClientImpl from '../src/LDClientImpl'; +import { createBasicPlatform } from './createBasicPlatform'; import { makeSegmentMatchClause } from './evaluation/flags'; import makeCallbacks from './makeCallbacks'; @@ -75,7 +75,7 @@ describe('given test data with big segments', () => { }; client = new LDClientImpl( - 'sdk-key', + 'sdk-key-big-segments-test-data', { ...createBasicPlatform(), crypto }, { updateProcessor: td.getFactory(), @@ -114,7 +114,7 @@ describe('given test data with big segments', () => { }; client = new LDClientImpl( - 'sdk-key', + 'sdk-key-big-segments-with-user', { ...createBasicPlatform(), crypto }, { updateProcessor: td.getFactory(), @@ -153,7 +153,7 @@ describe('given test data with big segments', () => { }; client = new LDClientImpl( - 'sdk-key', + 'sdk-key-big-segments-store-error', { ...createBasicPlatform(), crypto }, { updateProcessor: td.getFactory(), @@ -180,7 +180,7 @@ describe('given test data with big segments', () => { describe('given a client without big segment support.', () => { beforeEach(async () => { client = new LDClientImpl( - 'sdk-key', + 'sdk-key-big-segments-no-store', { ...createBasicPlatform(), crypto }, { updateProcessor: td.getFactory(), diff --git a/packages/shared/sdk-server/__tests__/LDClientImpl.listeners.test.ts b/packages/shared/sdk-server/__tests__/LDClientImpl.listeners.test.ts index a97b7c083..d32ef6d24 100644 --- a/packages/shared/sdk-server/__tests__/LDClientImpl.listeners.test.ts +++ b/packages/shared/sdk-server/__tests__/LDClientImpl.listeners.test.ts @@ -1,10 +1,9 @@ import { AsyncQueue } from 'launchdarkly-js-test-helpers'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; - import { AttributeReference, LDClientImpl } from '../src'; import { Op } from '../src/evaluation/data/Clause'; import TestData from '../src/integrations/test_data/TestData'; +import { createBasicPlatform } from './createBasicPlatform'; import { makeFlagWithSegmentMatch } from './evaluation/flags'; import TestLogger from './Logger'; import makeCallbacks from './makeCallbacks'; @@ -18,7 +17,7 @@ describe('given an LDClient with test data', () => { queue = new AsyncQueue(); td = new TestData(); client = new LDClientImpl( - 'sdk-key', + 'sdk-key-listeners', createBasicPlatform(), { updateProcessor: td.getFactory(), diff --git a/packages/shared/sdk-server/__tests__/LDClientImpl.test.ts b/packages/shared/sdk-server/__tests__/LDClientImpl.test.ts index 4995ebc6b..34d97823d 100644 --- a/packages/shared/sdk-server/__tests__/LDClientImpl.test.ts +++ b/packages/shared/sdk-server/__tests__/LDClientImpl.test.ts @@ -1,24 +1,46 @@ -import { - createBasicPlatform, - MockStreamingProcessor, - setupMockStreamingProcessor, -} from '@launchdarkly/private-js-mocks'; +import { LDClientContext, LDStreamingError } from '@launchdarkly/js-sdk-common'; -import { LDClientImpl, LDOptions } from '../src'; +import { LDOptions } from '../src/api/options/LDOptions'; +import { LDFeatureStore } from '../src/api/subsystems/LDFeatureStore'; +import LDClientImpl from '../src/LDClientImpl'; +import { createBasicPlatform } from './createBasicPlatform'; import TestLogger, { LogLevel } from './Logger'; -jest.mock('@launchdarkly/js-sdk-common', () => { - const actual = jest.requireActual('@launchdarkly/js-sdk-common'); - return { - ...actual, - ...{ - internal: { - ...actual.internal, - StreamingProcessor: MockStreamingProcessor, - }, - }, - }; -}); +function getUpdateProcessorFactory(shouldError: boolean = false, initTimeoutMs: number = 0) { + let initTimeoutHandle: any; + let patchTimeoutHandle: any; + let deleteTimeoutHandle: any; + + return ( + _clientContext: LDClientContext, + featureStore: LDFeatureStore, + initSuccessHandler: Function, + errorHandler?: (e: Error) => void, + ) => ({ + start: jest.fn(async () => { + if (shouldError) { + initTimeoutHandle = setTimeout(() => { + const unauthorized = new Error('test-error') as LDStreamingError; + // @ts-ignore + unauthorized.code = 401; + errorHandler?.(unauthorized); + }, 0); + } else { + // execute put which will resolve the identify promise + initTimeoutHandle = setTimeout(() => { + featureStore.init({}, () => {}); + initSuccessHandler(); + }, initTimeoutMs); + } + }), + close: jest.fn(() => { + clearTimeout(initTimeoutHandle); + clearTimeout(patchTimeoutHandle); + clearTimeout(deleteTimeoutHandle); + }), + eventSource: {}, + }); +} describe('LDClientImpl', () => { let client: LDClientImpl; @@ -30,11 +52,7 @@ describe('LDClientImpl', () => { hasEventListeners: jest.fn().mockName('hasEventListeners'), }; const createClient = (options: LDOptions = {}) => - new LDClientImpl('sdk-key', createBasicPlatform(), options, callbacks); - - beforeEach(() => { - setupMockStreamingProcessor(); - }); + new LDClientImpl('sdk-key-ldclientimpl.test', createBasicPlatform(), options, callbacks); afterEach(() => { client.close(); @@ -42,7 +60,7 @@ describe('LDClientImpl', () => { }); it('fires ready event in online mode', async () => { - client = createClient(); + client = createClient({ updateProcessor: getUpdateProcessorFactory() }); const initializedClient = await client.waitForInitialization({ timeout: 10 }); expect(initializedClient).toEqual(client); @@ -53,8 +71,7 @@ describe('LDClientImpl', () => { }); it('wait for initialization completes even if initialization completes before it is called', (done) => { - setupMockStreamingProcessor(); - client = createClient(); + client = createClient({ updateProcessor: getUpdateProcessorFactory() }); setTimeout(async () => { const initializedClient = await client.waitForInitialization({ timeout: 10 }); @@ -64,7 +81,7 @@ describe('LDClientImpl', () => { }); it('waiting for initialization the second time produces the same result', async () => { - client = createClient(); + client = createClient({ updateProcessor: getUpdateProcessorFactory() }); await client.waitForInitialization({ timeout: 10 }); const initializedClient = await client.waitForInitialization({ timeout: 10 }); @@ -83,9 +100,7 @@ describe('LDClientImpl', () => { }); it('initialization fails: failed event fires and initialization promise rejects', async () => { - setupMockStreamingProcessor(true); - client = createClient(); - + client = createClient({ updateProcessor: getUpdateProcessorFactory(true) }); await expect(client.waitForInitialization({ timeout: 10 })).rejects.toThrow('failed'); expect(client.initialized()).toBeFalsy(); @@ -95,8 +110,7 @@ describe('LDClientImpl', () => { }); it('initialization promise is rejected even if the failure happens before wait is called', (done) => { - setupMockStreamingProcessor(true); - client = createClient(); + client = createClient({ updateProcessor: getUpdateProcessorFactory(true) }); setTimeout(async () => { await expect(client.waitForInitialization({ timeout: 10 })).rejects.toThrow('failed'); @@ -110,8 +124,7 @@ describe('LDClientImpl', () => { }); it('waiting a second time results in the same rejection', async () => { - setupMockStreamingProcessor(true); - client = createClient(); + client = createClient({ updateProcessor: getUpdateProcessorFactory(true) }); await expect(client.waitForInitialization({ timeout: 10 })).rejects.toThrow('failed'); await expect(client.waitForInitialization({ timeout: 10 })).rejects.toThrow('failed'); @@ -128,13 +141,13 @@ describe('LDClientImpl', () => { }); it('resolves immediately if the client is already ready', async () => { - client = createClient(); + client = createClient({ updateProcessor: getUpdateProcessorFactory() }); await client.waitForInitialization({ timeout: 10 }); await client.waitForInitialization({ timeout: 10 }); }); it('creates only one Promise when waiting for initialization - when not using a timeout', async () => { - client = createClient(); + client = createClient({ updateProcessor: getUpdateProcessorFactory() }); const p1 = client.waitForInitialization(); const p2 = client.waitForInitialization(); @@ -142,17 +155,20 @@ describe('LDClientImpl', () => { }); it('rejects the returned promise when initialization does not complete within the timeout', async () => { - setupMockStreamingProcessor(undefined, undefined, undefined, undefined, undefined, 10000); - client = createClient(); + client = createClient({ + updateProcessor: getUpdateProcessorFactory(false, 10000), + }); await expect(async () => client.waitForInitialization({ timeout: 1 })).rejects.toThrow( 'waitForInitialization timed out after 1 seconds.', ); }); it('logs an error when the initialization does not complete within the timeout', async () => { - setupMockStreamingProcessor(undefined, undefined, undefined, undefined, undefined, 10000); const logger = new TestLogger(); - client = createClient({ logger }); + client = createClient({ + logger, + updateProcessor: getUpdateProcessorFactory(false, 10000), + }); try { await client.waitForInitialization({ timeout: 1 }); } catch { @@ -167,14 +183,18 @@ describe('LDClientImpl', () => { }); it('does not reject the returned promise when initialization completes within the timeout', async () => { - setupMockStreamingProcessor(undefined, undefined, undefined, undefined, undefined, 1000); - client = createClient(); - await expect(async () => client.waitForInitialization({ timeout: 5 })).not.toThrow(); + client = createClient({ + updateProcessor: getUpdateProcessorFactory(false, 100), + }); + await expect(client.waitForInitialization({ timeout: 3 })).resolves.not.toThrow(); }); it('logs when no timeout is set', async () => { const logger = new TestLogger(); - client = createClient({ logger }); + client = createClient({ + logger, + updateProcessor: getUpdateProcessorFactory(), + }); await client.waitForInitialization(); logger.expectMessages([ { @@ -187,7 +207,10 @@ describe('LDClientImpl', () => { it('logs when the timeout is too high', async () => { const logger = new TestLogger(); - client = createClient({ logger }); + client = createClient({ + logger, + updateProcessor: getUpdateProcessorFactory(), + }); await client.waitForInitialization({ timeout: Number.MAX_SAFE_INTEGER }); logger.expectMessages([ @@ -203,7 +226,7 @@ describe('LDClientImpl', () => { 'does not log when timeout is under high timeout threshold', async (timeout) => { const logger = new TestLogger(); - client = createClient({ logger }); + client = createClient({ logger, updateProcessor: getUpdateProcessorFactory() }); await client.waitForInitialization({ timeout }); expect(logger.getCount(LogLevel.Warn)).toBe(0); }, diff --git a/packages/shared/sdk-server/__tests__/Migration.test.ts b/packages/shared/sdk-server/__tests__/Migration.test.ts index 6434ec5f5..06db9a688 100644 --- a/packages/shared/sdk-server/__tests__/Migration.test.ts +++ b/packages/shared/sdk-server/__tests__/Migration.test.ts @@ -1,5 +1,3 @@ -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; - import { LDClientImpl, LDConcurrentExecution, @@ -10,6 +8,7 @@ import { import { TestData } from '../src/integrations'; import { LDClientCallbacks } from '../src/LDClientImpl'; import { createMigration, LDMigrationError, LDMigrationSuccess } from '../src/Migration'; +import { createBasicPlatform } from './createBasicPlatform'; import makeCallbacks from './makeCallbacks'; describe('given an LDClient with test data', () => { @@ -21,7 +20,7 @@ describe('given an LDClient with test data', () => { td = new TestData(); callbacks = makeCallbacks(false); client = new LDClientImpl( - 'sdk-key', + 'sdk-key-migration', createBasicPlatform(), { updateProcessor: td.getFactory(), diff --git a/packages/shared/sdk-server/__tests__/MigrationOpEvent.test.ts b/packages/shared/sdk-server/__tests__/MigrationOpEvent.test.ts index f3b60b406..435c45a70 100644 --- a/packages/shared/sdk-server/__tests__/MigrationOpEvent.test.ts +++ b/packages/shared/sdk-server/__tests__/MigrationOpEvent.test.ts @@ -1,7 +1,6 @@ import { AsyncQueue } from 'launchdarkly-js-test-helpers'; import { internal } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import { LDClientImpl, @@ -16,6 +15,7 @@ import { TestData } from '../src/integrations'; import { LDClientCallbacks } from '../src/LDClientImpl'; import { createMigration, LDMigrationError, LDMigrationSuccess } from '../src/Migration'; import MigrationOpEventConversion from '../src/MigrationOpEventConversion'; +import { createBasicPlatform } from './createBasicPlatform'; import makeCallbacks from './makeCallbacks'; jest.mock('@launchdarkly/js-sdk-common', () => ({ @@ -43,7 +43,7 @@ describe('given an LDClient with test data', () => { td = new TestData(); callbacks = makeCallbacks(false); client = new LDClientImpl( - 'sdk-key', + 'sdk-key-migration-op', createBasicPlatform(), { updateProcessor: td.getFactory(), diff --git a/packages/shared/sdk-server/__tests__/createBasicPlatform.ts b/packages/shared/sdk-server/__tests__/createBasicPlatform.ts new file mode 100644 index 000000000..17178f061 --- /dev/null +++ b/packages/shared/sdk-server/__tests__/createBasicPlatform.ts @@ -0,0 +1,59 @@ +import { PlatformData, SdkData } from '@launchdarkly/js-sdk-common'; + +import { setupCrypto } from './setupCrypto'; + +const setupInfo = () => ({ + platformData: jest.fn( + (): PlatformData => ({ + os: { + name: 'An OS', + version: '1.0.1', + arch: 'An Arch', + }, + name: 'The SDK Name', + additional: { + nodeVersion: '42', + }, + ld_application: { + key: '', + envAttributesVersion: '1.0', + id: 'com.testapp.ld', + name: 'LDApplication.TestApp', + version: '1.1.1', + }, + ld_device: { + key: '', + envAttributesVersion: '1.0', + os: { name: 'Another OS', version: '99', family: 'orange' }, + manufacturer: 'coconut', + }, + }), + ), + sdkData: jest.fn( + (): SdkData => ({ + name: 'An SDK', + version: '2.0.2', + userAgentBase: 'TestUserAgent', + wrapperName: 'Rapper', + wrapperVersion: '1.2.3', + }), + ), +}); + +export const createBasicPlatform = () => ({ + encoding: { + btoa: (s: string) => Buffer.from(s).toString('base64'), + }, + info: setupInfo(), + crypto: setupCrypto(), + requests: { + fetch: jest.fn(), + createEventSource: jest.fn(), + getEventSourceCapabilities: jest.fn(), + }, + storage: { + get: jest.fn(), + set: jest.fn(), + clear: jest.fn(), + }, +}); diff --git a/packages/shared/sdk-server/__tests__/data_sources/FileDataSource.test.ts b/packages/shared/sdk-server/__tests__/data_sources/FileDataSource.test.ts index 000314563..df0a2c920 100644 --- a/packages/shared/sdk-server/__tests__/data_sources/FileDataSource.test.ts +++ b/packages/shared/sdk-server/__tests__/data_sources/FileDataSource.test.ts @@ -1,5 +1,4 @@ import { ClientContext, Context, Filesystem, WatchHandle } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import { Flag } from '../../src/evaluation/data/Flag'; import { Segment } from '../../src/evaluation/data/Segment'; @@ -9,6 +8,7 @@ import Configuration from '../../src/options/Configuration'; import AsyncStoreFacade from '../../src/store/AsyncStoreFacade'; import InMemoryFeatureStore from '../../src/store/InMemoryFeatureStore'; import VersionedDataKinds from '../../src/store/VersionedDataKinds'; +import { createBasicPlatform } from '../createBasicPlatform'; import TestLogger from '../Logger'; const flag1Key = 'flag1'; diff --git a/packages/shared/sdk-server/__tests__/data_sources/PollingProcessor.test.ts b/packages/shared/sdk-server/__tests__/data_sources/PollingProcessor.test.ts index 19e2ac8a0..05ae9ff28 100644 --- a/packages/shared/sdk-server/__tests__/data_sources/PollingProcessor.test.ts +++ b/packages/shared/sdk-server/__tests__/data_sources/PollingProcessor.test.ts @@ -1,5 +1,4 @@ import { ClientContext } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import { LDFeatureStore } from '../../src'; import PollingProcessor from '../../src/data_sources/PollingProcessor'; @@ -8,6 +7,7 @@ import Configuration from '../../src/options/Configuration'; import AsyncStoreFacade from '../../src/store/AsyncStoreFacade'; import InMemoryFeatureStore from '../../src/store/InMemoryFeatureStore'; import VersionedDataKinds from '../../src/store/VersionedDataKinds'; +import { createBasicPlatform } from '../createBasicPlatform'; import TestLogger, { LogLevel } from '../Logger'; describe('given an event processor', () => { diff --git a/packages/shared/sdk-server/src/diagnostics/createDiagnosticsInitConfig.test.ts b/packages/shared/sdk-server/__tests__/diagnostics/createDiagnosticsInitConfig.test.ts similarity index 92% rename from packages/shared/sdk-server/src/diagnostics/createDiagnosticsInitConfig.test.ts rename to packages/shared/sdk-server/__tests__/diagnostics/createDiagnosticsInitConfig.test.ts index 1605d277c..f0e3d21ad 100644 --- a/packages/shared/sdk-server/src/diagnostics/createDiagnosticsInitConfig.test.ts +++ b/packages/shared/sdk-server/__tests__/diagnostics/createDiagnosticsInitConfig.test.ts @@ -1,8 +1,7 @@ -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; - -import { LDOptions } from '../api'; -import Configuration from '../options/Configuration'; -import createDiagnosticsInitConfig from './createDiagnosticsInitConfig'; +import { LDOptions } from '../../src/api'; +import createDiagnosticsInitConfig from '../../src/diagnostics/createDiagnosticsInitConfig'; +import Configuration from '../../src/options/Configuration'; +import { createBasicPlatform } from '../createBasicPlatform'; let mockPlatform: ReturnType; diff --git a/packages/shared/sdk-server/__tests__/evaluation/Bucketer.test.ts b/packages/shared/sdk-server/__tests__/evaluation/Bucketer.test.ts index a1c10754c..5da1868e5 100644 --- a/packages/shared/sdk-server/__tests__/evaluation/Bucketer.test.ts +++ b/packages/shared/sdk-server/__tests__/evaluation/Bucketer.test.ts @@ -9,9 +9,9 @@ import { Hasher, LDContext, } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import Bucketer from '../../src/evaluation/Bucketer'; +import { createBasicPlatform } from '../createBasicPlatform'; let mockPlatform: ReturnType; diff --git a/packages/shared/sdk-server/__tests__/evaluation/Evaluator.bucketing.test.ts b/packages/shared/sdk-server/__tests__/evaluation/Evaluator.bucketing.test.ts index 35ddcd431..568292887 100644 --- a/packages/shared/sdk-server/__tests__/evaluation/Evaluator.bucketing.test.ts +++ b/packages/shared/sdk-server/__tests__/evaluation/Evaluator.bucketing.test.ts @@ -1,9 +1,9 @@ import { Context } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import { Flag } from '../../src/evaluation/data/Flag'; import { Rollout } from '../../src/evaluation/data/Rollout'; import Evaluator from '../../src/evaluation/Evaluator'; +import { createBasicPlatform } from '../createBasicPlatform'; import noQueries from './mocks/noQueries'; describe('given a flag with a rollout', () => { diff --git a/packages/shared/sdk-server/__tests__/evaluation/Evaluator.clause.test.ts b/packages/shared/sdk-server/__tests__/evaluation/Evaluator.clause.test.ts index 8c32c816b..df260d17d 100644 --- a/packages/shared/sdk-server/__tests__/evaluation/Evaluator.clause.test.ts +++ b/packages/shared/sdk-server/__tests__/evaluation/Evaluator.clause.test.ts @@ -1,10 +1,10 @@ import { AttributeReference, Context, LDContext } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import { Clause } from '../../src/evaluation/data/Clause'; import { Flag } from '../../src/evaluation/data/Flag'; import { FlagRule } from '../../src/evaluation/data/FlagRule'; import Evaluator from '../../src/evaluation/Evaluator'; +import { createBasicPlatform } from '../createBasicPlatform'; import { makeBooleanFlagWithOneClause, makeBooleanFlagWithRules, diff --git a/packages/shared/sdk-server/__tests__/evaluation/Evaluator.rules.test.ts b/packages/shared/sdk-server/__tests__/evaluation/Evaluator.rules.test.ts index 7ee33eaba..3be3e2819 100644 --- a/packages/shared/sdk-server/__tests__/evaluation/Evaluator.rules.test.ts +++ b/packages/shared/sdk-server/__tests__/evaluation/Evaluator.rules.test.ts @@ -1,12 +1,12 @@ // Tests of flag evaluation at the rule level. Clause-level behavior is covered // in detail in Evaluator.clause.tests and (TODO: File for segments). import { AttributeReference, Context, LDContext } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import { Clause } from '../../src/evaluation/data/Clause'; import { Flag } from '../../src/evaluation/data/Flag'; import { FlagRule } from '../../src/evaluation/data/FlagRule'; import Evaluator from '../../src/evaluation/Evaluator'; +import { createBasicPlatform } from '../createBasicPlatform'; import { makeClauseThatDoesNotMatchUser, makeClauseThatMatchesUser, diff --git a/packages/shared/sdk-server/__tests__/evaluation/Evaluator.segments.test.ts b/packages/shared/sdk-server/__tests__/evaluation/Evaluator.segments.test.ts index d0f1f4eeb..d27ac07dd 100644 --- a/packages/shared/sdk-server/__tests__/evaluation/Evaluator.segments.test.ts +++ b/packages/shared/sdk-server/__tests__/evaluation/Evaluator.segments.test.ts @@ -7,13 +7,13 @@ import { Hmac, LDContext, } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import { BigSegmentStoreMembership } from '../../src/api/interfaces'; import { Flag } from '../../src/evaluation/data/Flag'; import { Segment } from '../../src/evaluation/data/Segment'; import Evaluator from '../../src/evaluation/Evaluator'; import { Queries } from '../../src/evaluation/Queries'; +import { createBasicPlatform } from '../createBasicPlatform'; import { makeClauseThatDoesNotMatchUser, makeClauseThatMatchesUser, diff --git a/packages/shared/sdk-server/__tests__/evaluation/Evaluator.test.ts b/packages/shared/sdk-server/__tests__/evaluation/Evaluator.test.ts index 99325b8ef..64640b0c4 100644 --- a/packages/shared/sdk-server/__tests__/evaluation/Evaluator.test.ts +++ b/packages/shared/sdk-server/__tests__/evaluation/Evaluator.test.ts @@ -1,10 +1,10 @@ import { Context, LDContext } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import { Flag } from '../../src/evaluation/data/Flag'; import EvalResult from '../../src/evaluation/EvalResult'; import Evaluator from '../../src/evaluation/Evaluator'; import Reasons from '../../src/evaluation/Reasons'; +import { createBasicPlatform } from '../createBasicPlatform'; import noQueries from './mocks/noQueries'; const offBaseFlag = { diff --git a/packages/shared/sdk-server/__tests__/events/EventProcessor.test.ts b/packages/shared/sdk-server/__tests__/events/EventProcessor.test.ts index 6ea238ac9..98d747890 100644 --- a/packages/shared/sdk-server/__tests__/events/EventProcessor.test.ts +++ b/packages/shared/sdk-server/__tests__/events/EventProcessor.test.ts @@ -14,11 +14,11 @@ import { Response, SdkData, } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import ContextDeduplicator from '../../src/events/ContextDeduplicator'; import Configuration from '../../src/options/Configuration'; import InMemoryFeatureStore from '../../src/store/InMemoryFeatureStore'; +import { createBasicPlatform } from '../createBasicPlatform'; let mockPlatform: ReturnType; diff --git a/packages/shared/sdk-server/__tests__/integrations/test_data/TestData.test.ts b/packages/shared/sdk-server/__tests__/integrations/test_data/TestData.test.ts index 06c97f0fe..facb21258 100644 --- a/packages/shared/sdk-server/__tests__/integrations/test_data/TestData.test.ts +++ b/packages/shared/sdk-server/__tests__/integrations/test_data/TestData.test.ts @@ -1,5 +1,4 @@ import { AttributeReference, ClientContext } from '@launchdarkly/js-sdk-common'; -import { createBasicPlatform } from '@launchdarkly/private-js-mocks'; import { Flag } from '../../../src/evaluation/data/Flag'; import { FlagRule } from '../../../src/evaluation/data/FlagRule'; @@ -8,6 +7,7 @@ import Configuration from '../../../src/options/Configuration'; import AsyncStoreFacade from '../../../src/store/AsyncStoreFacade'; import InMemoryFeatureStore from '../../../src/store/InMemoryFeatureStore'; import VersionedDataKinds from '../../../src/store/VersionedDataKinds'; +import { createBasicPlatform } from '../../createBasicPlatform'; const basicBooleanFlag: Flag = { fallthrough: { diff --git a/packages/shared/sdk-server/__tests__/setupCrypto.ts b/packages/shared/sdk-server/__tests__/setupCrypto.ts new file mode 100644 index 000000000..fc8d0b460 --- /dev/null +++ b/packages/shared/sdk-server/__tests__/setupCrypto.ts @@ -0,0 +1,20 @@ +import { Hasher } from '@launchdarkly/js-sdk-common'; + +export const setupCrypto = () => { + let counter = 0; + const hasher = { + update: jest.fn((): Hasher => hasher), + digest: jest.fn(() => '1234567890123456'), + }; + + return { + createHash: jest.fn(() => hasher), + createHmac: jest.fn(), + randomUUID: jest.fn(() => { + counter += 1; + // Will provide a unique value for tests. + // Very much not a UUID of course. + return `${counter}`; + }), + }; +}; diff --git a/packages/shared/mocks/src/streamingProcessor.ts b/packages/shared/sdk-server/__tests__/streamingProcessor.ts similarity index 98% rename from packages/shared/mocks/src/streamingProcessor.ts rename to packages/shared/sdk-server/__tests__/streamingProcessor.ts index cd038e7ef..2f6e2a802 100644 --- a/packages/shared/mocks/src/streamingProcessor.ts +++ b/packages/shared/sdk-server/__tests__/streamingProcessor.ts @@ -5,7 +5,7 @@ import type { LDHeaders, LDStreamingError, ProcessStreamResponse, -} from '@common'; +} from '@launchdarkly/js-sdk-common'; export const MockStreamingProcessor = jest.fn(); diff --git a/packages/shared/sdk-server/package.json b/packages/shared/sdk-server/package.json index 2ea1d6ac9..77de3fcbb 100644 --- a/packages/shared/sdk-server/package.json +++ b/packages/shared/sdk-server/package.json @@ -31,7 +31,6 @@ "semver": "7.5.4" }, "devDependencies": { - "@launchdarkly/private-js-mocks": "0.0.1", "@trivago/prettier-plugin-sort-imports": "^4.1.1", "@types/jest": "^29.4.0", "@types/semver": "^7.3.13", diff --git a/packages/shared/sdk-server/src/data_sources/createStreamListeners.test.ts b/packages/shared/sdk-server/src/data_sources/createStreamListeners.test.ts index 661a084c4..02df75aad 100644 --- a/packages/shared/sdk-server/src/data_sources/createStreamListeners.test.ts +++ b/packages/shared/sdk-server/src/data_sources/createStreamListeners.test.ts @@ -1,4 +1,4 @@ -import { createLogger } from '@launchdarkly/private-js-mocks'; +import { LDLogger } from '@launchdarkly/js-sdk-common'; import { LDDataSourceUpdates } from '../api/subsystems'; import { deserializeAll, deserializeDelete, deserializePatch } from '../store/serialization'; @@ -7,10 +7,15 @@ import { createStreamListeners } from './createStreamListeners'; jest.mock('../store/serialization'); -let logger: ReturnType; +let logger: LDLogger; beforeEach(() => { - logger = createLogger(); + logger = { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + }; }); const allData = { diff --git a/packages/telemetry/node-server-sdk-otel/package.json b/packages/telemetry/node-server-sdk-otel/package.json index c52bdba45..27f53c379 100644 --- a/packages/telemetry/node-server-sdk-otel/package.json +++ b/packages/telemetry/node-server-sdk-otel/package.json @@ -34,7 +34,6 @@ }, "devDependencies": { "@launchdarkly/node-server-sdk": "9.6.0", - "@launchdarkly/private-js-mocks": "0.0.1", "@opentelemetry/api": ">=1.3.0", "@opentelemetry/sdk-node": "0.49.1", "@opentelemetry/sdk-trace-node": "1.22.0", diff --git a/tsconfig.json b/tsconfig.json index 91b995e66..e7ffa9fe3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,9 +7,6 @@ { "path": "./packages/shared/common/tsconfig.ref.json" }, - { - "path": "./packages/shared/mocks/tsconfig.ref.json" - }, { "path": "./packages/shared/sdk-client/tsconfig.ref.json" },