From 2c57609c8d86773beba4cb76c652365468e6bd3e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 18:21:16 +0000 Subject: [PATCH 1/2] chore(deps): update dependency ts-jest to v29.2.0 (#991) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Signed-off-by: Simon Schrottner --- .github/workflows/ci.yml | 25 - libs/providers/flagd-web/jest.config.ts | 9 +- libs/providers/flagd-web/project.json | 6 +- libs/providers/flagd-web/schemas | 2 +- libs/providers/flagd-web/src/e2e/constants.ts | 3 + .../flagd-web/src/e2e/jest.config.ts | 4 +- libs/providers/flagd-web/src/e2e/setup.ts | 21 - .../e2e/step-definitions/evaluation.spec.ts | 341 ------- .../src/e2e/step-definitions/evaluation.ts | 339 +++++++ .../flagd-web/src/e2e/tests/provider.spec.ts | 46 + libs/providers/flagd/package.json | 2 +- libs/providers/flagd/project.json | 7 +- libs/providers/flagd/src/e2e/constants.ts | 2 + .../src/e2e/setup-in-process-provider.ts | 44 - .../flagd/src/e2e/setup-rpc-provider.ts | 35 - .../e2e/step-definitions/evaluation.spec.ts | 363 -------- .../src/e2e/step-definitions/evaluation.ts | 364 ++++++++ .../flagd-json-evaluator.spec.ts | 171 ---- .../step-definitions/flagd-json-evaluator.ts | 172 ++++ .../flagd-reconnect.unstable.spec.ts | 68 -- .../flagd-reconnect.unstable.ts | 66 ++ .../src/e2e/step-definitions/flagd.spec.ts | 135 --- .../flagd/src/e2e/step-definitions/flagd.ts | 133 +++ .../src/e2e/tests/in-process-provider.spec.ts | 88 ++ .../flagd/src/e2e/tests/rpc-provider.spec.ts | 81 ++ libs/shared/flagd-core/flagd-schemas | 2 +- package-lock.json | 835 ++++++++++++++++++ package.json | 2 + 28 files changed, 2143 insertions(+), 1223 deletions(-) create mode 100644 libs/providers/flagd-web/src/e2e/constants.ts delete mode 100644 libs/providers/flagd-web/src/e2e/setup.ts delete mode 100644 libs/providers/flagd-web/src/e2e/step-definitions/evaluation.spec.ts create mode 100644 libs/providers/flagd-web/src/e2e/step-definitions/evaluation.ts create mode 100644 libs/providers/flagd-web/src/e2e/tests/provider.spec.ts delete mode 100644 libs/providers/flagd/src/e2e/setup-in-process-provider.ts delete mode 100644 libs/providers/flagd/src/e2e/setup-rpc-provider.ts delete mode 100644 libs/providers/flagd/src/e2e/step-definitions/evaluation.spec.ts create mode 100644 libs/providers/flagd/src/e2e/step-definitions/evaluation.ts delete mode 100644 libs/providers/flagd/src/e2e/step-definitions/flagd-json-evaluator.spec.ts create mode 100644 libs/providers/flagd/src/e2e/step-definitions/flagd-json-evaluator.ts delete mode 100644 libs/providers/flagd/src/e2e/step-definitions/flagd-reconnect.unstable.spec.ts create mode 100644 libs/providers/flagd/src/e2e/step-definitions/flagd-reconnect.unstable.ts delete mode 100644 libs/providers/flagd/src/e2e/step-definitions/flagd.spec.ts create mode 100644 libs/providers/flagd/src/e2e/step-definitions/flagd.ts create mode 100644 libs/providers/flagd/src/e2e/tests/in-process-provider.spec.ts create mode 100644 libs/providers/flagd/src/e2e/tests/rpc-provider.spec.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2e8af757..a462599ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,9 +25,6 @@ jobs: with: node-version: ${{ matrix.node-version }} - - uses: bufbuild/buf-setup-action@v1.32.0 - with: - github_token: ${{ github.token }} - run: npm ci - uses: nrwl/nx-set-shas@v3 # This line is needed for nx affected to work when CI is running on a PR @@ -42,24 +39,6 @@ jobs: e2e: runs-on: ubuntu-latest - services: - flagd: - image: ghcr.io/open-feature/flagd-testbed:v0.5.6 - ports: - - 8013:8013 - flagd-unstable: - image: ghcr.io/open-feature/flagd-testbed-unstable:v0.5.6 - ports: - - 8014:8013 - sync: - image: ghcr.io/open-feature/sync-testbed:v0.5.6 - ports: - - 9090:9090 - sync-unstable: - image: ghcr.io/open-feature/sync-testbed-unstable:v0.5.6 - ports: - - 9091:9090 - steps: - uses: actions/checkout@v4 with: @@ -74,9 +53,5 @@ jobs: - name: Install run: npm ci - - uses: bufbuild/buf-setup-action@v1.32.0 - with: - github_token: ${{ github.token }} - - name: e2e run: npm run e2e diff --git a/libs/providers/flagd-web/jest.config.ts b/libs/providers/flagd-web/jest.config.ts index 0cff8c160..5a16c1770 100644 --- a/libs/providers/flagd-web/jest.config.ts +++ b/libs/providers/flagd-web/jest.config.ts @@ -2,13 +2,10 @@ export default { displayName: 'providers-flagd-web', preset: '../../../jest.preset.js', - globals: { - 'ts-jest': { - tsconfig: '/tsconfig.spec.json', - }, - }, transform: { - '^.+\\.[tj]s$': 'ts-jest', + '^.+\\.[tj]s$': ['ts-jest', { + tsconfig: '/tsconfig.spec.json' + }] }, testEnvironment: 'jsdom', moduleFileExtensions: ['ts', 'js', 'html'], diff --git a/libs/providers/flagd-web/project.json b/libs/providers/flagd-web/project.json index 9131c9927..5991774ee 100644 --- a/libs/providers/flagd-web/project.json +++ b/libs/providers/flagd-web/project.json @@ -21,9 +21,7 @@ "options": { "commands": [ "git submodule update --init schemas", - "rm -f -r ./src/proto", - "cd schemas && buf generate buf.build/open-feature/flagd --template protobuf/buf.gen.ts-connect.yaml", - "mv -v ./proto ./src" + "npx buf generate buf.build/open-feature/flagd --template schemas/protobuf/buf.gen.ts-connect.yaml --output ./src/lib" ], "cwd": "libs/providers/flagd-web", "parallel": false @@ -76,7 +74,7 @@ "executor": "nx:run-commands", "options": { "commands": [ - "npx jest" + "npx jest --detectOpenHandles --runInBand" ], "cwd": "libs/providers/flagd-web/src/e2e", "parallel": false diff --git a/libs/providers/flagd-web/schemas b/libs/providers/flagd-web/schemas index e72b08b71..1ba4b0324 160000 --- a/libs/providers/flagd-web/schemas +++ b/libs/providers/flagd-web/schemas @@ -1 +1 @@ -Subproject commit e72b08b71ad8654e8a31ec6f75a9c8b4d47db8ca +Subproject commit 1ba4b0324771273e6db7d4a808f6dc87a0b3c717 diff --git a/libs/providers/flagd-web/src/e2e/constants.ts b/libs/providers/flagd-web/src/e2e/constants.ts new file mode 100644 index 000000000..41d547459 --- /dev/null +++ b/libs/providers/flagd-web/src/e2e/constants.ts @@ -0,0 +1,3 @@ +export const FLAGD_NAME = 'flagd-web'; + +export const IMAGE_VERSION = 'v0.5.6'; diff --git a/libs/providers/flagd-web/src/e2e/jest.config.ts b/libs/providers/flagd-web/src/e2e/jest.config.ts index 6b4681648..29de43ff6 100644 --- a/libs/providers/flagd-web/src/e2e/jest.config.ts +++ b/libs/providers/flagd-web/src/e2e/jest.config.ts @@ -1,7 +1,7 @@ export default { displayName: 'providers-flagd-web-e2e', transform: { - '^.+\\.[tj]s$': ['ts-jest', { tsConfig: './tsconfig.lib.json' }], + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: './tsconfig.lib.json' }], }, moduleNameMapper: { '^(.*)\\.js$': ['$1.js', '$1.ts', '$1'], @@ -9,7 +9,7 @@ export default { testEnvironment: 'node', preset: 'ts-jest', clearMocks: true, - setupFiles: ['./setup.ts'], + setupFiles: [], verbose: true, silent: false, }; diff --git a/libs/providers/flagd-web/src/e2e/setup.ts b/libs/providers/flagd-web/src/e2e/setup.ts deleted file mode 100644 index c99452b62..000000000 --- a/libs/providers/flagd-web/src/e2e/setup.ts +++ /dev/null @@ -1,21 +0,0 @@ -import assert from 'assert'; -import { FlagdWebProvider } from '../lib/flagd-web-provider'; -import { OpenFeature } from '@openfeature/web-sdk'; - -const FLAGD_WEB_NAME = 'flagd-web'; - -// register the flagd provider before the tests. -console.log('Setting flagd web provider...'); -OpenFeature.setProvider( - new FlagdWebProvider({ - host: 'localhost', - port: 8013, - tls: false, - maxRetries: -1, - }), -); -assert( - OpenFeature.providerMetadata.name === FLAGD_WEB_NAME, - new Error(`Expected ${FLAGD_WEB_NAME} provider to be configured, instead got: ${OpenFeature.providerMetadata.name}`), -); -console.log('flagd web provider configured!'); diff --git a/libs/providers/flagd-web/src/e2e/step-definitions/evaluation.spec.ts b/libs/providers/flagd-web/src/e2e/step-definitions/evaluation.spec.ts deleted file mode 100644 index 9f0c2f305..000000000 --- a/libs/providers/flagd-web/src/e2e/step-definitions/evaluation.spec.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { - EvaluationContext, - EvaluationDetails, - JsonObject, - JsonValue, - OpenFeature, - ProviderEvents, - ResolutionDetails, - StandardResolutionReasons, -} from '@openfeature/web-sdk'; -import { defineFeature, loadFeature } from 'jest-cucumber'; - -// load the feature file. -const feature = loadFeature('features/evaluation.feature'); - -// get a client (flagd provider registered in setup) -const client = OpenFeature.getClient(); - -const givenAnOpenfeatureClientIsRegistered = ( - given: (stepMatcher: string, stepDefinitionCallback: () => void) => void, -) => { - given('a provider is registered', () => undefined); -}; - -defineFeature(feature, (test) => { - beforeAll((done) => { - client.addHandler(ProviderEvents.Ready, async () => { - done(); - }); - }); - - afterAll(async () => { - await OpenFeature.close(); - }); - - test('Resolves boolean value', ({ given, when, then }) => { - let value: boolean; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a boolean flag with key "(.*)" is evaluated with default value "(.*)"$/, - (key: string, defaultValue: string) => { - value = client.getBooleanValue(key, defaultValue === 'true'); - }, - ); - - then(/^the resolved boolean value should be "(.*)"$/, (expectedValue: string) => { - expect(value).toEqual(expectedValue === 'true'); - }); - }); - - test('Resolves string value', ({ given, when, then }) => { - let value: string; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a string flag with key "(.*)" is evaluated with default value "(.*)"$/, - (key: string, defaultValue: string) => { - value = client.getStringValue(key, defaultValue); - }, - ); - - then(/^the resolved string value should be "(.*)"$/, (expectedValue: string) => { - expect(value).toEqual(expectedValue); - }); - }); - - test('Resolves integer value', ({ given, when, then }) => { - let value: number; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^an integer flag with key "(.*)" is evaluated with default value (\d+)$/, - (key: string, defaultValue: number) => { - value = client.getNumberValue(key, defaultValue); - }, - ); - - then(/^the resolved integer value should be (\d+)$/, (expectedStringValue: string) => { - const expectedNumberValue = parseInt(expectedStringValue); - expect(value).toEqual(expectedNumberValue); - }); - }); - - test('Resolves float value', ({ given, when, then }) => { - let value: number; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a float flag with key "(.*)" is evaluated with default value (\d+\.?\d*)$/, - (key: string, defaultValue: string) => { - value = client.getNumberValue(key, Number.parseFloat(defaultValue)); - }, - ); - - then(/^the resolved float value should be (\d+\.?\d*)$/, (expectedValue: string) => { - expect(value).toEqual(Number.parseFloat(expectedValue)); - }); - }); - - test('Resolves object value', ({ given, when, then }) => { - let value: JsonValue; - givenAnOpenfeatureClientIsRegistered(given); - - when(/^an object flag with key "(.*)" is evaluated with a null default value$/, (key: string) => { - value = client.getObjectValue(key, {}); - }); - - then( - /^the resolved object value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/, - (field1: string, field2: string, field3: string, boolVal: string, strVal: string, intVal: string) => { - const jsonObject = value as JsonObject; - expect(jsonObject[field1]).toEqual(boolVal === 'true'); - expect(jsonObject[field2]).toEqual(strVal); - expect(jsonObject[field3]).toEqual(Number.parseInt(intVal)); - }, - ); - }); - - test('Resolves boolean details', ({ given, when, then }) => { - let details: EvaluationDetails; - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a boolean flag with key "(.*)" is evaluated with details and default value "(.*)"$/, - (key: string, defaultValue: string) => { - details = client.getBooleanDetails(key, defaultValue === 'true'); - }, - ); - - then( - /^the resolved boolean details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/, - (expectedValue: string, expectedVariant: string, expectedReason: string) => { - expect(details.value).toEqual(expectedValue === 'true'); - expect(details.variant).toEqual(expectedVariant); - expect(details.reason).toEqual(expectedReason); - }, - ); - }); - - test('Resolves string details', ({ given, when, then }) => { - let details: EvaluationDetails; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a string flag with key "(.*)" is evaluated with details and default value "(.*)"$/, - (key: string, defaultValue: string) => { - details = client.getStringDetails(key, defaultValue); - }, - ); - - then( - /^the resolved string details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/, - (expectedValue: string, expectedVariant: string, expectedReason: string) => { - expect(details.value).toEqual(expectedValue); - expect(details.variant).toEqual(expectedVariant); - expect(details.reason).toEqual(expectedReason); - }, - ); - }); - - test('Resolves integer details', ({ given, when, then }) => { - let details: EvaluationDetails; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^an integer flag with key "(.*)" is evaluated with details and default value (\d+)$/, - (key: string, defaultValue: string) => { - details = client.getNumberDetails(key, Number.parseInt(defaultValue)); - }, - ); - - then( - /^the resolved integer details value should be (\d+), the variant should be "(.*)", and the reason should be "(.*)"$/, - (expectedValue: string, expectedVariant: string, expectedReason: string) => { - expect(details.value).toEqual(Number.parseInt(expectedValue)); - expect(details.variant).toEqual(expectedVariant); - expect(details.reason).toEqual(expectedReason); - }, - ); - }); - - test('Resolves float details', ({ given, when, then }) => { - let details: EvaluationDetails; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a float flag with key "(.*)" is evaluated with details and default value (\d+\.?\d*)$/, - (key: string, defaultValue: string) => { - details = client.getNumberDetails(key, Number.parseFloat(defaultValue)); - }, - ); - - then( - /^the resolved float details value should be (\d+\.?\d*), the variant should be "(.*)", and the reason should be "(.*)"$/, - (expectedValue: string, expectedVariant: string, expectedReason: string) => { - expect(details.value).toEqual(Number.parseFloat(expectedValue)); - expect(details.variant).toEqual(expectedVariant); - expect(details.reason).toEqual(expectedReason); - }, - ); - }); - - test('Resolves object details', ({ given, when, then, and }) => { - let details: EvaluationDetails; // update this after merge - - givenAnOpenfeatureClientIsRegistered(given); - - when(/^an object flag with key "(.*)" is evaluated with details and a null default value$/, (key: string) => { - details = client.getObjectDetails(key, {}); // update this after merge - }); - - then( - /^the resolved object details value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/, - (field1: string, field2: string, field3: string, boolValue: string, stringValue: string, intValue: string) => { - const jsonObject = details.value as JsonObject; - - expect(jsonObject[field1]).toEqual(boolValue === 'true'); - expect(jsonObject[field2]).toEqual(stringValue); - expect(jsonObject[field3]).toEqual(Number.parseInt(intValue)); - }, - ); - - and( - /^the variant should be "(.*)", and the reason should be "(.*)"$/, - (expectedVariant: string, expectedReason: string) => { - expect(details.variant).toEqual(expectedVariant); - expect(details.reason).toEqual(expectedReason); - }, - ); - }); - - test('Resolves based on context', ({ given, when, and, then }) => { - const context: EvaluationContext = {}; - let value: string; - let flagKey: string; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^context contains keys "(.*)", "(.*)", "(.*)", "(.*)" with values "(.*)", "(.*)", (\d+), "(.*)"$/, - async ( - stringField1: string, - stringField2: string, - intField: string, - boolField: string, - stringValue1: string, - stringValue2: string, - intValue: string, - boolValue: string, - ) => { - context[stringField1] = stringValue1; - context[stringField2] = stringValue2; - context[intField] = Number.parseInt(intValue); - context[boolField] = boolValue === 'true'; - - await OpenFeature.setContext(context); - }, - ); - - and(/^a flag with key "(.*)" is evaluated with default value "(.*)"$/, (key: string, defaultValue: string) => { - flagKey = key; - value = client.getStringValue(flagKey, defaultValue); - }); - - then(/^the resolved string response should be "(.*)"$/, (expectedValue: string) => { - expect(value).toEqual(expectedValue); - }); - - and(/^the resolved flag value is "(.*)" when the context is empty$/, async (expectedValue) => { - await OpenFeature.setContext({}); - const emptyContextValue = client.getStringValue(flagKey, 'nope', {}); - expect(emptyContextValue).toEqual(expectedValue); - }); - }); - - test('Flag not found', ({ given, when, then, and }) => { - let flagKey: string; - let fallbackValue: string; - let details: ResolutionDetails; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a non-existent string flag with key "(.*)" is evaluated with details and a default value "(.*)"$/, - (key: string, defaultValue: string) => { - flagKey = key; - fallbackValue = defaultValue; - details = client.getStringDetails(flagKey, defaultValue); - }, - ); - - then(/^the default string value should be returned$/, () => { - expect(details.value).toEqual(fallbackValue); - }); - - and( - /^the reason should indicate an error and the error code should indicate a missing flag with "(.*)"$/, - (errorCode: string) => { - expect(details.reason).toEqual(StandardResolutionReasons.ERROR); - expect(details.errorCode).toEqual(errorCode); - }, - ); - }); - - test('Type error', ({ given, when, then, and }) => { - let flagKey: string; - let fallbackValue: number; - let details: ResolutionDetails; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a string flag with key "(.*)" is evaluated as an integer, with details and a default value (\d+)$/, - (key: string, defaultValue: string) => { - flagKey = key; - fallbackValue = Number.parseInt(defaultValue); - details = client.getNumberDetails(flagKey, Number.parseInt(defaultValue)); - }, - ); - - then(/^the default integer value should be returned$/, () => { - expect(details.value).toEqual(fallbackValue); - }); - - and( - /^the reason should indicate an error and the error code should indicate a type mismatch with "(.*)"$/, - (errorCode: string) => { - expect(details.reason).toEqual(StandardResolutionReasons.ERROR); - expect(details.errorCode).toEqual(errorCode); - }, - ); - }); -}); diff --git a/libs/providers/flagd-web/src/e2e/step-definitions/evaluation.ts b/libs/providers/flagd-web/src/e2e/step-definitions/evaluation.ts new file mode 100644 index 000000000..27ebd7b46 --- /dev/null +++ b/libs/providers/flagd-web/src/e2e/step-definitions/evaluation.ts @@ -0,0 +1,339 @@ +import { + EvaluationContext, + EvaluationDetails, + JsonObject, + JsonValue, + OpenFeature, + ProviderEvents, + ResolutionDetails, + StandardResolutionReasons, +} from '@openfeature/web-sdk'; +import { defineFeature, loadFeature } from 'jest-cucumber'; + +export function evaluation() { + // load the feature file. + const feature = loadFeature('features/evaluation.feature'); + + // get a client (flagd provider registered in setup) + const client = OpenFeature.getClient(); + + const givenAnOpenfeatureClientIsRegistered = ( + given: (stepMatcher: string, stepDefinitionCallback: () => void) => void, + ) => { + given('a provider is registered', () => undefined); + }; + + defineFeature(feature, (test) => { + beforeAll((done) => { + client.addHandler(ProviderEvents.Ready, async () => { + done(); + }); + }); + + test('Resolves boolean value', ({ given, when, then }) => { + let value: boolean; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a boolean flag with key "(.*)" is evaluated with default value "(.*)"$/, + (key: string, defaultValue: string) => { + value = client.getBooleanValue(key, defaultValue === 'true'); + }, + ); + + then(/^the resolved boolean value should be "(.*)"$/, (expectedValue: string) => { + expect(value).toEqual(expectedValue === 'true'); + }); + }); + + test('Resolves string value', ({ given, when, then }) => { + let value: string; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a string flag with key "(.*)" is evaluated with default value "(.*)"$/, + (key: string, defaultValue: string) => { + value = client.getStringValue(key, defaultValue); + }, + ); + + then(/^the resolved string value should be "(.*)"$/, (expectedValue: string) => { + expect(value).toEqual(expectedValue); + }); + }); + + test('Resolves integer value', ({ given, when, then }) => { + let value: number; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^an integer flag with key "(.*)" is evaluated with default value (\d+)$/, + (key: string, defaultValue: number) => { + value = client.getNumberValue(key, defaultValue); + }, + ); + + then(/^the resolved integer value should be (\d+)$/, (expectedStringValue: string) => { + const expectedNumberValue = parseInt(expectedStringValue); + expect(value).toEqual(expectedNumberValue); + }); + }); + + test('Resolves float value', ({ given, when, then }) => { + let value: number; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a float flag with key "(.*)" is evaluated with default value (\d+\.?\d*)$/, + (key: string, defaultValue: string) => { + value = client.getNumberValue(key, Number.parseFloat(defaultValue)); + }, + ); + + then(/^the resolved float value should be (\d+\.?\d*)$/, (expectedValue: string) => { + expect(value).toEqual(Number.parseFloat(expectedValue)); + }); + }); + + test('Resolves object value', ({ given, when, then }) => { + let value: JsonValue; + givenAnOpenfeatureClientIsRegistered(given); + + when(/^an object flag with key "(.*)" is evaluated with a null default value$/, (key: string) => { + value = client.getObjectValue(key, {}); + }); + + then( + /^the resolved object value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/, + (field1: string, field2: string, field3: string, boolVal: string, strVal: string, intVal: string) => { + const jsonObject = value as JsonObject; + expect(jsonObject[field1]).toEqual(boolVal === 'true'); + expect(jsonObject[field2]).toEqual(strVal); + expect(jsonObject[field3]).toEqual(Number.parseInt(intVal)); + }, + ); + }); + + test('Resolves boolean details', ({ given, when, then }) => { + let details: EvaluationDetails; + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a boolean flag with key "(.*)" is evaluated with details and default value "(.*)"$/, + (key: string, defaultValue: string) => { + details = client.getBooleanDetails(key, defaultValue === 'true'); + }, + ); + + then( + /^the resolved boolean details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/, + (expectedValue: string, expectedVariant: string, expectedReason: string) => { + expect(details.value).toEqual(expectedValue === 'true'); + expect(details.variant).toEqual(expectedVariant); + expect(details.reason).toEqual(expectedReason); + }, + ); + }); + + test('Resolves string details', ({ given, when, then }) => { + let details: EvaluationDetails; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a string flag with key "(.*)" is evaluated with details and default value "(.*)"$/, + (key: string, defaultValue: string) => { + details = client.getStringDetails(key, defaultValue); + }, + ); + + then( + /^the resolved string details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/, + (expectedValue: string, expectedVariant: string, expectedReason: string) => { + expect(details.value).toEqual(expectedValue); + expect(details.variant).toEqual(expectedVariant); + expect(details.reason).toEqual(expectedReason); + }, + ); + }); + + test('Resolves integer details', ({ given, when, then }) => { + let details: EvaluationDetails; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^an integer flag with key "(.*)" is evaluated with details and default value (\d+)$/, + (key: string, defaultValue: string) => { + details = client.getNumberDetails(key, Number.parseInt(defaultValue)); + }, + ); + + then( + /^the resolved integer details value should be (\d+), the variant should be "(.*)", and the reason should be "(.*)"$/, + (expectedValue: string, expectedVariant: string, expectedReason: string) => { + expect(details.value).toEqual(Number.parseInt(expectedValue)); + expect(details.variant).toEqual(expectedVariant); + expect(details.reason).toEqual(expectedReason); + }, + ); + }); + + test('Resolves float details', ({ given, when, then }) => { + let details: EvaluationDetails; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a float flag with key "(.*)" is evaluated with details and default value (\d+\.?\d*)$/, + (key: string, defaultValue: string) => { + details = client.getNumberDetails(key, Number.parseFloat(defaultValue)); + }, + ); + + then( + /^the resolved float details value should be (\d+\.?\d*), the variant should be "(.*)", and the reason should be "(.*)"$/, + (expectedValue: string, expectedVariant: string, expectedReason: string) => { + expect(details.value).toEqual(Number.parseFloat(expectedValue)); + expect(details.variant).toEqual(expectedVariant); + expect(details.reason).toEqual(expectedReason); + }, + ); + }); + + test('Resolves object details', ({ given, when, then, and }) => { + let details: EvaluationDetails; // update this after merge + + givenAnOpenfeatureClientIsRegistered(given); + + when(/^an object flag with key "(.*)" is evaluated with details and a null default value$/, (key: string) => { + details = client.getObjectDetails(key, {}); // update this after merge + }); + + then( + /^the resolved object details value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/, + (field1: string, field2: string, field3: string, boolValue: string, stringValue: string, intValue: string) => { + const jsonObject = details.value as JsonObject; + + expect(jsonObject[field1]).toEqual(boolValue === 'true'); + expect(jsonObject[field2]).toEqual(stringValue); + expect(jsonObject[field3]).toEqual(Number.parseInt(intValue)); + }, + ); + + and( + /^the variant should be "(.*)", and the reason should be "(.*)"$/, + (expectedVariant: string, expectedReason: string) => { + expect(details.variant).toEqual(expectedVariant); + expect(details.reason).toEqual(expectedReason); + }, + ); + }); + + test('Resolves based on context', ({ given, when, and, then }) => { + const context: EvaluationContext = {}; + let value: string; + let flagKey: string; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^context contains keys "(.*)", "(.*)", "(.*)", "(.*)" with values "(.*)", "(.*)", (\d+), "(.*)"$/, + async ( + stringField1: string, + stringField2: string, + intField: string, + boolField: string, + stringValue1: string, + stringValue2: string, + intValue: string, + boolValue: string, + ) => { + context[stringField1] = stringValue1; + context[stringField2] = stringValue2; + context[intField] = Number.parseInt(intValue); + context[boolField] = boolValue === 'true'; + + await OpenFeature.setContext(context); + }, + ); + + and(/^a flag with key "(.*)" is evaluated with default value "(.*)"$/, (key: string, defaultValue: string) => { + flagKey = key; + value = client.getStringValue(flagKey, defaultValue); + }); + + then(/^the resolved string response should be "(.*)"$/, (expectedValue: string) => { + expect(value).toEqual(expectedValue); + }); + + and(/^the resolved flag value is "(.*)" when the context is empty$/, async (expectedValue) => { + await OpenFeature.setContext({}); + const emptyContextValue = client.getStringValue(flagKey, 'nope', {}); + expect(emptyContextValue).toEqual(expectedValue); + }); + }); + + test('Flag not found', ({ given, when, then, and }) => { + let flagKey: string; + let fallbackValue: string; + let details: ResolutionDetails; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a non-existent string flag with key "(.*)" is evaluated with details and a default value "(.*)"$/, + (key: string, defaultValue: string) => { + flagKey = key; + fallbackValue = defaultValue; + details = client.getStringDetails(flagKey, defaultValue); + }, + ); + + then(/^the default string value should be returned$/, () => { + expect(details.value).toEqual(fallbackValue); + }); + + and( + /^the reason should indicate an error and the error code should indicate a missing flag with "(.*)"$/, + (errorCode: string) => { + expect(details.reason).toEqual(StandardResolutionReasons.ERROR); + expect(details.errorCode).toEqual(errorCode); + }, + ); + }); + + test('Type error', ({ given, when, then, and }) => { + let flagKey: string; + let fallbackValue: number; + let details: ResolutionDetails; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a string flag with key "(.*)" is evaluated as an integer, with details and a default value (\d+)$/, + (key: string, defaultValue: string) => { + flagKey = key; + fallbackValue = Number.parseInt(defaultValue); + details = client.getNumberDetails(flagKey, Number.parseInt(defaultValue)); + }, + ); + + then(/^the default integer value should be returned$/, () => { + expect(details.value).toEqual(fallbackValue); + }); + + and( + /^the reason should indicate an error and the error code should indicate a type mismatch with "(.*)"$/, + (errorCode: string) => { + expect(details.reason).toEqual(StandardResolutionReasons.ERROR); + expect(details.errorCode).toEqual(errorCode); + }, + ); + }); + }); +} diff --git a/libs/providers/flagd-web/src/e2e/tests/provider.spec.ts b/libs/providers/flagd-web/src/e2e/tests/provider.spec.ts new file mode 100644 index 000000000..927a5474b --- /dev/null +++ b/libs/providers/flagd-web/src/e2e/tests/provider.spec.ts @@ -0,0 +1,46 @@ +import assert from 'assert'; +import { OpenFeature } from '@openfeature/web-sdk'; +import { FLAGD_NAME, IMAGE_VERSION } from '../constants'; +import { GenericContainer, StartedTestContainer } from 'testcontainers'; +import { FlagdWebProvider } from '../../lib/flagd-web-provider'; +import { evaluation } from '../step-definitions/evaluation'; + +// register the flagd provider before the tests. +async function setup() { + const containers: StartedTestContainer[] = []; + + console.log('Setting flagd provider...'); + + const stable = await new GenericContainer(`ghcr.io/open-feature/flagd-testbed:${IMAGE_VERSION}`) + .withExposedPorts(8013) + .start(); + containers.push(stable); + OpenFeature.setProvider( + new FlagdWebProvider({ + host: stable.getHost(), + port: stable.getMappedPort(8013), + tls: false, + maxRetries: -1, + }), + ); + assert( + OpenFeature.providerMetadata.name === FLAGD_NAME, + new Error(`Expected ${FLAGD_NAME} provider to be configured, instead got: ${OpenFeature.providerMetadata.name}`), + ); + console.log('flagd provider configured!'); + return containers; +} + +describe('web provider', () => { + let containers: StartedTestContainer[] = []; + beforeAll(async () => { + containers = await setup(); + }, 60000); + afterAll(async () => { + await OpenFeature.close(); + for (const container of containers) { + container.stop(); + } + }); + evaluation(); +}); diff --git a/libs/providers/flagd/package.json b/libs/providers/flagd/package.json index 1f2ca5b3a..f2148f20c 100644 --- a/libs/providers/flagd/package.json +++ b/libs/providers/flagd/package.json @@ -9,4 +9,4 @@ "@grpc/grpc-js": "~1.8.0 || ~1.9.0 || ~1.10.0", "@openfeature/server-sdk": "^1.13.0" } -} \ No newline at end of file +} diff --git a/libs/providers/flagd/project.json b/libs/providers/flagd/project.json index b376ba682..556647124 100644 --- a/libs/providers/flagd/project.json +++ b/libs/providers/flagd/project.json @@ -21,9 +21,7 @@ "options": { "commands": [ "git submodule update --init schemas", - "rm -f -r ./src/proto", - "cd schemas && buf generate buf.build/open-feature/flagd --template protobuf/buf.gen.ts.yaml", - "mv -v ./proto ./src" + "npx buf generate buf.build/open-feature/flagd --template schemas/protobuf/buf.gen.ts.yaml --output ./src/lib" ], "cwd": "libs/providers/flagd", "parallel": false @@ -78,8 +76,7 @@ "executor": "nx:run-commands", "options": { "commands": [ - "npx jest --setupFiles './setup-rpc-provider.ts' --runInBand --detectOpenHandles", - "npx jest --setupFiles './setup-in-process-provider.ts' --runInBand --detectOpenHandles" + "npx jest --runInBand --detectOpenHandles" ], "cwd": "libs/providers/flagd/src/e2e", "parallel": false diff --git a/libs/providers/flagd/src/e2e/constants.ts b/libs/providers/flagd/src/e2e/constants.ts index fbd46b629..eb83fd17f 100644 --- a/libs/providers/flagd/src/e2e/constants.ts +++ b/libs/providers/flagd/src/e2e/constants.ts @@ -2,3 +2,5 @@ export const FLAGD_NAME = 'flagd Provider'; export const E2E_CLIENT_NAME = 'e2e'; export const UNSTABLE_CLIENT_NAME = 'unstable'; export const UNAVAILABLE_CLIENT_NAME = 'unavailable'; + +export const IMAGE_VERSION = 'v0.5.6'; diff --git a/libs/providers/flagd/src/e2e/setup-in-process-provider.ts b/libs/providers/flagd/src/e2e/setup-in-process-provider.ts deleted file mode 100644 index c40e14c9a..000000000 --- a/libs/providers/flagd/src/e2e/setup-in-process-provider.ts +++ /dev/null @@ -1,44 +0,0 @@ -import assert from 'assert'; -import { OpenFeature } from '@openfeature/server-sdk'; -import { FlagdProvider } from '../lib/flagd-provider'; -import { E2E_CLIENT_NAME, FLAGD_NAME, UNSTABLE_CLIENT_NAME, UNAVAILABLE_CLIENT_NAME } from './constants'; - -// register the flagd provider before the tests. -console.log('Setting flagd provider...'); -OpenFeature.setProvider( - E2E_CLIENT_NAME, - new FlagdProvider({ resolverType: 'in-process', host: 'localhost', port: 9090 }), -); -OpenFeature.setProvider( - UNSTABLE_CLIENT_NAME, - new FlagdProvider({ resolverType: 'in-process', host: 'localhost', port: 9091 }), -); -OpenFeature.setProvider( - UNAVAILABLE_CLIENT_NAME, - new FlagdProvider({ resolverType: 'in-process', host: 'localhost', port: 9092 }), -); -assert( - OpenFeature.getProviderMetadata(E2E_CLIENT_NAME).name === FLAGD_NAME, - new Error( - `Expected ${FLAGD_NAME} provider to be configured, instead got: ${ - OpenFeature.getProviderMetadata(E2E_CLIENT_NAME).name - }`, - ), -); -assert( - OpenFeature.getProviderMetadata(UNSTABLE_CLIENT_NAME).name === FLAGD_NAME, - new Error( - `Expected ${FLAGD_NAME} provider to be configured, instead got: ${ - OpenFeature.getProviderMetadata(UNSTABLE_CLIENT_NAME).name - }`, - ), -); -assert( - OpenFeature.getProviderMetadata(UNAVAILABLE_CLIENT_NAME).name === FLAGD_NAME, - new Error( - `Expected ${FLAGD_NAME} provider to be configured, instead got: ${ - OpenFeature.getProviderMetadata(UNAVAILABLE_CLIENT_NAME).name - }`, - ), -); -console.log('flagd provider configured!'); diff --git a/libs/providers/flagd/src/e2e/setup-rpc-provider.ts b/libs/providers/flagd/src/e2e/setup-rpc-provider.ts deleted file mode 100644 index f75c96aa3..000000000 --- a/libs/providers/flagd/src/e2e/setup-rpc-provider.ts +++ /dev/null @@ -1,35 +0,0 @@ -import assert from 'assert'; -import { OpenFeature } from '@openfeature/server-sdk'; -import { FlagdProvider } from '../lib/flagd-provider'; -import { E2E_CLIENT_NAME, FLAGD_NAME, UNSTABLE_CLIENT_NAME, UNAVAILABLE_CLIENT_NAME } from './constants'; - -// register the flagd provider before the tests. -console.log('Setting flagd provider...'); -OpenFeature.setProvider(E2E_CLIENT_NAME, new FlagdProvider({ cache: 'disabled' })); -OpenFeature.setProvider(UNSTABLE_CLIENT_NAME, new FlagdProvider({ cache: 'disabled', port: 8014 })); -OpenFeature.setProvider(UNAVAILABLE_CLIENT_NAME, new FlagdProvider({ cache: 'disabled', port: 8015 })); -assert( - OpenFeature.getProviderMetadata(E2E_CLIENT_NAME).name === FLAGD_NAME, - new Error( - `Expected ${FLAGD_NAME} provider to be configured, instead got: ${ - OpenFeature.getProviderMetadata(E2E_CLIENT_NAME).name - }`, - ), -); -assert( - OpenFeature.getProviderMetadata(UNSTABLE_CLIENT_NAME).name === FLAGD_NAME, - new Error( - `Expected ${FLAGD_NAME} provider to be configured, instead got: ${ - OpenFeature.getProviderMetadata(UNSTABLE_CLIENT_NAME).name - }`, - ), -); -assert( - OpenFeature.getProviderMetadata(UNAVAILABLE_CLIENT_NAME).name === FLAGD_NAME, - new Error( - `Expected ${FLAGD_NAME} provider to be configured, instead got: ${ - OpenFeature.getProviderMetadata(UNAVAILABLE_CLIENT_NAME).name - }`, - ), -); -console.log('flagd provider configured!'); diff --git a/libs/providers/flagd/src/e2e/step-definitions/evaluation.spec.ts b/libs/providers/flagd/src/e2e/step-definitions/evaluation.spec.ts deleted file mode 100644 index 687bdedab..000000000 --- a/libs/providers/flagd/src/e2e/step-definitions/evaluation.spec.ts +++ /dev/null @@ -1,363 +0,0 @@ -import { - EvaluationContext, - EvaluationDetails, - JsonObject, - JsonValue, - OpenFeature, - ProviderEvents, - ResolutionDetails, - StandardResolutionReasons, -} from '@openfeature/server-sdk'; -import { defineFeature, loadFeature } from 'jest-cucumber'; -import { E2E_CLIENT_NAME } from '../constants'; - -// load the feature file. -const feature = loadFeature('features/evaluation.feature'); - -// get a client (flagd provider registered in setup) -const client = OpenFeature.getClient(E2E_CLIENT_NAME); - -const givenAnOpenfeatureClientIsRegistered = ( - given: (stepMatcher: string, stepDefinitionCallback: () => void) => void, -) => { - given('a provider is registered', () => undefined); -}; - -defineFeature(feature, (test) => { - beforeAll((done) => { - client.addHandler(ProviderEvents.Ready, async () => { - done(); - }); - }); - - afterAll(async () => { - await OpenFeature.close(); - }); - - test('Resolves boolean value', ({ given, when, then }) => { - let value: boolean; - let flagKey: string; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a boolean flag with key "(.*)" is evaluated with default value "(.*)"$/, - async (key: string, defaultValue: string) => { - flagKey = key; - value = await client.getBooleanValue(flagKey, defaultValue === 'true'); - }, - ); - - then(/^the resolved boolean value should be "(.*)"$/, (expectedValue: string) => { - expect(value).toEqual(expectedValue === 'true'); - }); - }); - - test('Resolves string value', ({ given, when, then }) => { - let value: string; - let flagKey: string; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a string flag with key "(.*)" is evaluated with default value "(.*)"$/, - async (key: string, defaultValue: string) => { - flagKey = key; - value = await client.getStringValue(flagKey, defaultValue); - }, - ); - - then(/^the resolved string value should be "(.*)"$/, (expectedValue: string) => { - expect(value).toEqual(expectedValue); - }); - }); - - test('Resolves integer value', ({ given, when, then }) => { - let value: number; - let flagKey: string; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^an integer flag with key "(.*)" is evaluated with default value (\d+)$/, - async (key: string, defaultValue: string) => { - flagKey = key; - value = await client.getNumberValue(flagKey, Number.parseInt(defaultValue)); - }, - ); - - then(/^the resolved integer value should be (\d+)$/, (expectedValue: string) => { - expect(value).toEqual(Number.parseInt(expectedValue)); - }); - }); - - test('Resolves float value', ({ given, when, then }) => { - let value: number; - let flagKey: string; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a float flag with key "(.*)" is evaluated with default value (\d+\.?\d*)$/, - async (key: string, defaultValue: string) => { - flagKey = key; - value = await client.getNumberValue(flagKey, Number.parseFloat(defaultValue)); - }, - ); - - then(/^the resolved float value should be (\d+\.?\d*)$/, (expectedValue: string) => { - expect(value).toEqual(Number.parseFloat(expectedValue)); - }); - }); - - test('Resolves object value', ({ given, when, then }) => { - let value: JsonValue; - let flagKey: string; - - givenAnOpenfeatureClientIsRegistered(given); - - when(/^an object flag with key "(.*)" is evaluated with a null default value$/, async (key: string) => { - flagKey = key; - value = await client.getObjectValue(flagKey, {}); - }); - - then( - /^the resolved object value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/, - (field1: string, field2: string, field3: string, boolValue: string, stringValue: string, intValue: string) => { - const jsonObject = value as JsonObject; - expect(jsonObject[field1]).toEqual(boolValue === 'true'); - expect(jsonObject[field2]).toEqual(stringValue); - expect(jsonObject[field3]).toEqual(Number.parseInt(intValue)); - }, - ); - }); - - test('Resolves boolean details', ({ given, when, then }) => { - let details: EvaluationDetails; - let flagKey: string; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a boolean flag with key "(.*)" is evaluated with details and default value "(.*)"$/, - async (key: string, defaultValue: string) => { - flagKey = key; - details = await client.getBooleanDetails(flagKey, defaultValue === 'true'); - }, - ); - - then( - /^the resolved boolean details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/, - (expectedValue: string, expectedVariant: string, expectedReason: string) => { - expect(details.value).toEqual(expectedValue === 'true'); - expect(details.variant).toEqual(expectedVariant); - expect(details.reason).toEqual(expectedReason); - }, - ); - }); - - test('Resolves string details', ({ given, when, then }) => { - let details: EvaluationDetails; - let flagKey: string; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a string flag with key "(.*)" is evaluated with details and default value "(.*)"$/, - async (key: string, defaultValue: string) => { - flagKey = key; - details = await client.getStringDetails(flagKey, defaultValue); - }, - ); - - then( - /^the resolved string details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/, - (expectedValue: string, expectedVariant: string, expectedReason: string) => { - expect(details.value).toEqual(expectedValue); - expect(details.variant).toEqual(expectedVariant); - expect(details.reason).toEqual(expectedReason); - }, - ); - }); - - test('Resolves integer details', ({ given, when, then }) => { - let details: EvaluationDetails; - let flagKey: string; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^an integer flag with key "(.*)" is evaluated with details and default value (\d+)$/, - async (key: string, defaultValue: string) => { - flagKey = key; - details = await client.getNumberDetails(flagKey, Number.parseInt(defaultValue)); - }, - ); - - then( - /^the resolved integer details value should be (\d+), the variant should be "(.*)", and the reason should be "(.*)"$/, - (expectedValue: string, expectedVariant: string, expectedReason: string) => { - expect(details.value).toEqual(Number.parseInt(expectedValue)); - expect(details.variant).toEqual(expectedVariant); - expect(details.reason).toEqual(expectedReason); - }, - ); - }); - - test('Resolves float details', ({ given, when, then }) => { - let details: EvaluationDetails; - let flagKey: string; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a float flag with key "(.*)" is evaluated with details and default value (\d+\.?\d*)$/, - async (key: string, defaultValue: string) => { - flagKey = key; - details = await client.getNumberDetails(flagKey, Number.parseFloat(defaultValue)); - }, - ); - - then( - /^the resolved float details value should be (\d+\.?\d*), the variant should be "(.*)", and the reason should be "(.*)"$/, - (expectedValue: string, expectedVariant: string, expectedReason: string) => { - expect(details.value).toEqual(Number.parseFloat(expectedValue)); - expect(details.variant).toEqual(expectedVariant); - expect(details.reason).toEqual(expectedReason); - }, - ); - }); - - test('Resolves object details', ({ given, when, then, and }) => { - let details: EvaluationDetails; // update this after merge - let flagKey: string; - - givenAnOpenfeatureClientIsRegistered(given); - - when(/^an object flag with key "(.*)" is evaluated with details and a null default value$/, async (key: string) => { - flagKey = key; - details = await client.getObjectDetails(flagKey, {}); // update this after merge - }); - - then( - /^the resolved object details value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/, - (field1: string, field2: string, field3: string, boolValue: string, stringValue: string, intValue: string) => { - const jsonObject = details.value as JsonObject; - - expect(jsonObject[field1]).toEqual(boolValue === 'true'); - expect(jsonObject[field2]).toEqual(stringValue); - expect(jsonObject[field3]).toEqual(Number.parseInt(intValue)); - }, - ); - - and( - /^the variant should be "(.*)", and the reason should be "(.*)"$/, - (expectedVariant: string, expectedReason: string) => { - expect(details.variant).toEqual(expectedVariant); - expect(details.reason).toEqual(expectedReason); - }, - ); - }); - - test('Resolves based on context', ({ given, when, and, then }) => { - const context: EvaluationContext = {}; - let value: string; - let flagKey: string; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^context contains keys "(.*)", "(.*)", "(.*)", "(.*)" with values "(.*)", "(.*)", (\d+), "(.*)"$/, - ( - stringField1: string, - stringField2: string, - intField: string, - boolField: string, - stringValue1: string, - stringValue2: string, - intValue: string, - boolValue: string, - ) => { - context[stringField1] = stringValue1; - context[stringField2] = stringValue2; - context[intField] = Number.parseInt(intValue); - context[boolField] = boolValue === 'true'; - }, - ); - - and( - /^a flag with key "(.*)" is evaluated with default value "(.*)"$/, - async (key: string, defaultValue: string) => { - flagKey = key; - value = await client.getStringValue(flagKey, defaultValue, context); - }, - ); - - then(/^the resolved string response should be "(.*)"$/, (expectedValue: string) => { - expect(value).toEqual(expectedValue); - }); - - and(/^the resolved flag value is "(.*)" when the context is empty$/, async (expectedValue) => { - const emptyContextValue = await client.getStringValue(flagKey, 'nope', {}); - expect(emptyContextValue).toEqual(expectedValue); - }); - }); - - test('Flag not found', ({ given, when, then, and }) => { - let flagKey: string; - let fallbackValue: string; - let details: ResolutionDetails; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a non-existent string flag with key "(.*)" is evaluated with details and a default value "(.*)"$/, - async (key: string, defaultValue: string) => { - flagKey = key; - fallbackValue = defaultValue; - details = await client.getStringDetails(flagKey, defaultValue); - }, - ); - - then(/^the default string value should be returned$/, () => { - expect(details.value).toEqual(fallbackValue); - }); - - and( - /^the reason should indicate an error and the error code should indicate a missing flag with "(.*)"$/, - (errorCode: string) => { - expect(details.reason).toEqual(StandardResolutionReasons.ERROR); - expect(details.errorCode).toEqual(errorCode); - }, - ); - }); - - test('Type error', ({ given, when, then, and }) => { - let flagKey: string; - let fallbackValue: number; - let details: ResolutionDetails; - - givenAnOpenfeatureClientIsRegistered(given); - - when( - /^a string flag with key "(.*)" is evaluated as an integer, with details and a default value (\d+)$/, - async (key: string, defaultValue: string) => { - flagKey = key; - fallbackValue = Number.parseInt(defaultValue); - details = await client.getNumberDetails(flagKey, Number.parseInt(defaultValue)); - }, - ); - - then(/^the default integer value should be returned$/, () => { - expect(details.value).toEqual(fallbackValue); - }); - - and( - /^the reason should indicate an error and the error code should indicate a type mismatch with "(.*)"$/, - (errorCode: string) => { - expect(details.reason).toEqual(StandardResolutionReasons.ERROR); - expect(details.errorCode).toEqual(errorCode); - }, - ); - }); -}); diff --git a/libs/providers/flagd/src/e2e/step-definitions/evaluation.ts b/libs/providers/flagd/src/e2e/step-definitions/evaluation.ts new file mode 100644 index 000000000..7d392630e --- /dev/null +++ b/libs/providers/flagd/src/e2e/step-definitions/evaluation.ts @@ -0,0 +1,364 @@ +import { + EvaluationContext, + EvaluationDetails, + JsonObject, + JsonValue, + OpenFeature, + ProviderEvents, + ResolutionDetails, + StandardResolutionReasons, +} from '@openfeature/server-sdk'; +import { defineFeature, loadFeature } from 'jest-cucumber'; +import { E2E_CLIENT_NAME } from '../constants'; + +export function evaluation() { + // load the feature file. + const feature = loadFeature('features/evaluation.feature'); + + // get a client (flagd provider registered in setup) + const client = OpenFeature.getClient(E2E_CLIENT_NAME); + + const givenAnOpenfeatureClientIsRegistered = ( + given: (stepMatcher: string, stepDefinitionCallback: () => void) => void, + ) => { + given('a provider is registered', () => undefined); + }; + + defineFeature(feature, (test) => { + beforeAll((done) => { + client.addHandler(ProviderEvents.Ready, async () => { + done(); + }); + }); + + test('Resolves boolean value', ({ given, when, then }) => { + let value: boolean; + let flagKey: string; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a boolean flag with key "(.*)" is evaluated with default value "(.*)"$/, + async (key: string, defaultValue: string) => { + flagKey = key; + value = await client.getBooleanValue(flagKey, defaultValue === 'true'); + }, + ); + + then(/^the resolved boolean value should be "(.*)"$/, (expectedValue: string) => { + expect(value).toEqual(expectedValue === 'true'); + }); + }); + + test('Resolves string value', ({ given, when, then }) => { + let value: string; + let flagKey: string; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a string flag with key "(.*)" is evaluated with default value "(.*)"$/, + async (key: string, defaultValue: string) => { + flagKey = key; + value = await client.getStringValue(flagKey, defaultValue); + }, + ); + + then(/^the resolved string value should be "(.*)"$/, (expectedValue: string) => { + expect(value).toEqual(expectedValue); + }); + }); + + test('Resolves integer value', ({ given, when, then }) => { + let value: number; + let flagKey: string; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^an integer flag with key "(.*)" is evaluated with default value (\d+)$/, + async (key: string, defaultValue: string) => { + flagKey = key; + value = await client.getNumberValue(flagKey, Number.parseInt(defaultValue)); + }, + ); + + then(/^the resolved integer value should be (\d+)$/, (expectedValue: string) => { + expect(value).toEqual(Number.parseInt(expectedValue)); + }); + }); + + test('Resolves float value', ({ given, when, then }) => { + let value: number; + let flagKey: string; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a float flag with key "(.*)" is evaluated with default value (\d+\.?\d*)$/, + async (key: string, defaultValue: string) => { + flagKey = key; + value = await client.getNumberValue(flagKey, Number.parseFloat(defaultValue)); + }, + ); + + then(/^the resolved float value should be (\d+\.?\d*)$/, (expectedValue: string) => { + expect(value).toEqual(Number.parseFloat(expectedValue)); + }); + }); + + test('Resolves object value', ({ given, when, then }) => { + let value: JsonValue; + let flagKey: string; + + givenAnOpenfeatureClientIsRegistered(given); + + when(/^an object flag with key "(.*)" is evaluated with a null default value$/, async (key: string) => { + flagKey = key; + value = await client.getObjectValue(flagKey, {}); + }); + + then( + /^the resolved object value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/, + (field1: string, field2: string, field3: string, boolValue: string, stringValue: string, intValue: string) => { + const jsonObject = value as JsonObject; + expect(jsonObject[field1]).toEqual(boolValue === 'true'); + expect(jsonObject[field2]).toEqual(stringValue); + expect(jsonObject[field3]).toEqual(Number.parseInt(intValue)); + }, + ); + }); + + test('Resolves boolean details', ({ given, when, then }) => { + let details: EvaluationDetails; + let flagKey: string; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a boolean flag with key "(.*)" is evaluated with details and default value "(.*)"$/, + async (key: string, defaultValue: string) => { + flagKey = key; + details = await client.getBooleanDetails(flagKey, defaultValue === 'true'); + }, + ); + + then( + /^the resolved boolean details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/, + (expectedValue: string, expectedVariant: string, expectedReason: string) => { + expect(details.value).toEqual(expectedValue === 'true'); + expect(details.variant).toEqual(expectedVariant); + expect(details.reason).toEqual(expectedReason); + }, + ); + }); + + test('Resolves string details', ({ given, when, then }) => { + let details: EvaluationDetails; + let flagKey: string; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a string flag with key "(.*)" is evaluated with details and default value "(.*)"$/, + async (key: string, defaultValue: string) => { + flagKey = key; + details = await client.getStringDetails(flagKey, defaultValue); + }, + ); + + then( + /^the resolved string details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/, + (expectedValue: string, expectedVariant: string, expectedReason: string) => { + expect(details.value).toEqual(expectedValue); + expect(details.variant).toEqual(expectedVariant); + expect(details.reason).toEqual(expectedReason); + }, + ); + }); + + test('Resolves integer details', ({ given, when, then }) => { + let details: EvaluationDetails; + let flagKey: string; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^an integer flag with key "(.*)" is evaluated with details and default value (\d+)$/, + async (key: string, defaultValue: string) => { + flagKey = key; + details = await client.getNumberDetails(flagKey, Number.parseInt(defaultValue)); + }, + ); + + then( + /^the resolved integer details value should be (\d+), the variant should be "(.*)", and the reason should be "(.*)"$/, + (expectedValue: string, expectedVariant: string, expectedReason: string) => { + expect(details.value).toEqual(Number.parseInt(expectedValue)); + expect(details.variant).toEqual(expectedVariant); + expect(details.reason).toEqual(expectedReason); + }, + ); + }); + + test('Resolves float details', ({ given, when, then }) => { + let details: EvaluationDetails; + let flagKey: string; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a float flag with key "(.*)" is evaluated with details and default value (\d+\.?\d*)$/, + async (key: string, defaultValue: string) => { + flagKey = key; + details = await client.getNumberDetails(flagKey, Number.parseFloat(defaultValue)); + }, + ); + + then( + /^the resolved float details value should be (\d+\.?\d*), the variant should be "(.*)", and the reason should be "(.*)"$/, + (expectedValue: string, expectedVariant: string, expectedReason: string) => { + expect(details.value).toEqual(Number.parseFloat(expectedValue)); + expect(details.variant).toEqual(expectedVariant); + expect(details.reason).toEqual(expectedReason); + }, + ); + }); + + test('Resolves object details', ({ given, when, then, and }) => { + let details: EvaluationDetails; // update this after merge + let flagKey: string; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^an object flag with key "(.*)" is evaluated with details and a null default value$/, + async (key: string) => { + flagKey = key; + details = await client.getObjectDetails(flagKey, {}); // update this after merge + }, + ); + + then( + /^the resolved object details value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/, + (field1: string, field2: string, field3: string, boolValue: string, stringValue: string, intValue: string) => { + const jsonObject = details.value as JsonObject; + + expect(jsonObject[field1]).toEqual(boolValue === 'true'); + expect(jsonObject[field2]).toEqual(stringValue); + expect(jsonObject[field3]).toEqual(Number.parseInt(intValue)); + }, + ); + + and( + /^the variant should be "(.*)", and the reason should be "(.*)"$/, + (expectedVariant: string, expectedReason: string) => { + expect(details.variant).toEqual(expectedVariant); + expect(details.reason).toEqual(expectedReason); + }, + ); + }); + + test('Resolves based on context', ({ given, when, and, then }) => { + const context: EvaluationContext = {}; + let value: string; + let flagKey: string; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^context contains keys "(.*)", "(.*)", "(.*)", "(.*)" with values "(.*)", "(.*)", (\d+), "(.*)"$/, + ( + stringField1: string, + stringField2: string, + intField: string, + boolField: string, + stringValue1: string, + stringValue2: string, + intValue: string, + boolValue: string, + ) => { + context[stringField1] = stringValue1; + context[stringField2] = stringValue2; + context[intField] = Number.parseInt(intValue); + context[boolField] = boolValue === 'true'; + }, + ); + + and( + /^a flag with key "(.*)" is evaluated with default value "(.*)"$/, + async (key: string, defaultValue: string) => { + flagKey = key; + value = await client.getStringValue(flagKey, defaultValue, context); + }, + ); + + then(/^the resolved string response should be "(.*)"$/, (expectedValue: string) => { + expect(value).toEqual(expectedValue); + }); + + and(/^the resolved flag value is "(.*)" when the context is empty$/, async (expectedValue) => { + const emptyContextValue = await client.getStringValue(flagKey, 'nope', {}); + expect(emptyContextValue).toEqual(expectedValue); + }); + }); + + test('Flag not found', ({ given, when, then, and }) => { + let flagKey: string; + let fallbackValue: string; + let details: ResolutionDetails; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a non-existent string flag with key "(.*)" is evaluated with details and a default value "(.*)"$/, + async (key: string, defaultValue: string) => { + flagKey = key; + fallbackValue = defaultValue; + details = await client.getStringDetails(flagKey, defaultValue); + }, + ); + + then(/^the default string value should be returned$/, () => { + expect(details.value).toEqual(fallbackValue); + }); + + and( + /^the reason should indicate an error and the error code should indicate a missing flag with "(.*)"$/, + (errorCode: string) => { + expect(details.reason).toEqual(StandardResolutionReasons.ERROR); + expect(details.errorCode).toEqual(errorCode); + }, + ); + }); + + test('Type error', ({ given, when, then, and }) => { + let flagKey: string; + let fallbackValue: number; + let details: ResolutionDetails; + + givenAnOpenfeatureClientIsRegistered(given); + + when( + /^a string flag with key "(.*)" is evaluated as an integer, with details and a default value (\d+)$/, + async (key: string, defaultValue: string) => { + flagKey = key; + fallbackValue = Number.parseInt(defaultValue); + details = await client.getNumberDetails(flagKey, Number.parseInt(defaultValue)); + }, + ); + + then(/^the default integer value should be returned$/, () => { + expect(details.value).toEqual(fallbackValue); + }); + + and( + /^the reason should indicate an error and the error code should indicate a type mismatch with "(.*)"$/, + (errorCode: string) => { + expect(details.reason).toEqual(StandardResolutionReasons.ERROR); + expect(details.errorCode).toEqual(errorCode); + }, + ); + }); + }); +} diff --git a/libs/providers/flagd/src/e2e/step-definitions/flagd-json-evaluator.spec.ts b/libs/providers/flagd/src/e2e/step-definitions/flagd-json-evaluator.spec.ts deleted file mode 100644 index 70679da8a..000000000 --- a/libs/providers/flagd/src/e2e/step-definitions/flagd-json-evaluator.spec.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { EvaluationContext, EvaluationDetails, OpenFeature, ProviderEvents } from '@openfeature/server-sdk'; -import { defineFeature, loadFeature } from 'jest-cucumber'; -import { StepsDefinitionCallbackFunction } from 'jest-cucumber/dist/src/feature-definition-creation'; -import { E2E_CLIENT_NAME } from '../constants'; - -// load the feature file. -const feature = loadFeature('features/flagd-json-evaluator.feature'); - -// get a client (flagd provider registered in setup) -const client = OpenFeature.getClient(E2E_CLIENT_NAME); - -const aFlagProviderIsSet = (given: (stepMatcher: string, stepDefinitionCallback: () => void) => void) => { - given('a flagd provider is set', () => undefined); -}; - -// this is common to 4 tests -const evaluateStringFlagWithContext: StepsDefinitionCallbackFunction = ({ given, when, and, then }) => { - let flagKey: string; - let defaultValue: string; - const evaluationContext: EvaluationContext = {}; - - aFlagProviderIsSet(given); - when(/^a string flag with key "(.*)" is evaluated with default value "(.*)"$/, (key: string, defaultVal: string) => { - flagKey = key; - defaultValue = defaultVal; - }); - // the below has to match quotes strings ("str") and numbers (3) to test an error input - and(/^a context containing a key "(.*)", with value "?([^"]*)"?$/, (key: string, value: string) => { - evaluationContext[key] = value; - }); - then(/^the returned value should be "(.*)"$/, async (expectedValue: string) => { - const value = await client.getStringValue(flagKey, defaultValue, evaluationContext); - expect(value).toEqual(expectedValue); - }); -}; - -const evaluateStringFlagWithFractional: StepsDefinitionCallbackFunction = ({ given, when, and, then }) => { - let flagKey: string; - let defaultValue: string; - const evaluationContext: EvaluationContext = {}; - - aFlagProviderIsSet(given); - when(/^a string flag with key "(.*)" is evaluated with default value "(.*)"$/, (key, defaultVal) => { - flagKey = key; - defaultValue = defaultVal; - }); - and( - /^a context containing a nested property with outer key "(.*)" and inner key "(.*)", with value (.*)$/, - (outerKey: string, innerKey: string, value: string) => { - // we have to support string and non-string params in this test (we test invalid context value 3) - const valueNoQuotes = value.replaceAll('"', ''); - evaluationContext[outerKey] = { - [innerKey]: parseInt(valueNoQuotes) || valueNoQuotes, - }; - }, - ); - then(/^the returned value should be "(.*)"$/, async (expectedValue: string) => { - const value = await client.getStringValue(flagKey, defaultValue, evaluationContext); - expect(value).toEqual(expectedValue); - }); -}; - -defineFeature(feature, (test) => { - beforeAll((done) => { - client.addHandler(ProviderEvents.Ready, async () => { - done(); - }); - }); - - afterAll(async () => { - await OpenFeature.close(); - }); - - test('Evaluator reuse', evaluateStringFlagWithContext); - - test('Fractional operator', evaluateStringFlagWithFractional); - - test('Fractional operator shorthand', ({ given, when, and, then }) => { - let flagKey: string; - let defaultValue: string; - let details: EvaluationDetails; - - aFlagProviderIsSet(given); - - when(/^a string flag with key "(.*)" is evaluated with default value "(.*)"$/, (key, defaultVal) => { - flagKey = key; - defaultValue = defaultVal; - }); - - and(/^a context containing a targeting key with value "(.*)"$/, async (targetingKeyValue) => { - details = await client.getStringDetails(flagKey, defaultValue, { targetingKey: targetingKeyValue }); - }); - - then(/^the returned value should be "(.*)"$/, (expectedValue) => { - expect(details.value).toEqual(expectedValue); - }); - }); - - test('Fractional operator with shared seed', evaluateStringFlagWithFractional); - - test('Second fractional operator with shared seed', evaluateStringFlagWithFractional); - - test('Substring operators', evaluateStringFlagWithContext); - - test('Semantic version operator numeric comparison', evaluateStringFlagWithContext); - - test('Semantic version operator semantic comparison', evaluateStringFlagWithContext); - - test('Time-based operations', ({ given, when, and, then }) => { - let flagKey: string; - let defaultValue: number; - const evaluationContext: EvaluationContext = {}; - - aFlagProviderIsSet(given); - - when(/^an integer flag with key "(.*)" is evaluated with default value (\d+)$/, (key, defaultVal) => { - flagKey = key; - defaultValue = defaultVal; - }); - - and(/^a context containing a key "(.*)", with value (.*)$/, (key, value) => { - evaluationContext[key] = value; - }); - then(/^the returned value should be (.*)$/, async (expectedValue) => { - const value = await client.getNumberValue(flagKey, defaultValue, evaluationContext); - expect(value).toEqual(parseInt(expectedValue)); - }); - }); - - test('Targeting by targeting key', ({ given, when, and, then }) => { - let flagKey: string; - let defaultValue: string; - let details: EvaluationDetails; - - aFlagProviderIsSet(given); - - when(/^a string flag with key "(.*)" is evaluated with default value "(.*)"$/, (key, defaultVal) => { - flagKey = key; - defaultValue = defaultVal; - }); - - and(/^a context containing a targeting key with value "(.*)"$/, async (targetingKeyValue) => { - details = await client.getStringDetails(flagKey, defaultValue, { targetingKey: targetingKeyValue }); - }); - - then(/^the returned value should be "(.*)"$/, (expectedValue) => { - expect(details.value).toEqual(expectedValue); - }); - - then(/^the returned reason should be "(.*)"$/, (expectedReason) => { - expect(details.reason).toEqual(expectedReason); - }); - }); - - test('Errors and edge cases', ({ given, when, then }) => { - let flagKey: string; - let defaultValue: number; - - aFlagProviderIsSet(given); - - when(/^an integer flag with key "(.*)" is evaluated with default value (.*)$/, (key, defaultVal) => { - flagKey = key; - defaultValue = parseInt(defaultVal); - }); - - then(/^the returned value should be (.*)$/, async (expectedValue) => { - const value = await client.getNumberValue(flagKey, defaultValue); - expect(value).toEqual(parseInt(expectedValue)); - }); - }); -}); diff --git a/libs/providers/flagd/src/e2e/step-definitions/flagd-json-evaluator.ts b/libs/providers/flagd/src/e2e/step-definitions/flagd-json-evaluator.ts new file mode 100644 index 000000000..8751b05d3 --- /dev/null +++ b/libs/providers/flagd/src/e2e/step-definitions/flagd-json-evaluator.ts @@ -0,0 +1,172 @@ +import { EvaluationContext, EvaluationDetails, OpenFeature, ProviderEvents } from '@openfeature/server-sdk'; +import { defineFeature, loadFeature } from 'jest-cucumber'; +import { StepsDefinitionCallbackFunction } from 'jest-cucumber/dist/src/feature-definition-creation'; +import { E2E_CLIENT_NAME } from '../constants'; + +export function flagdJsonEvaluator() { + // load the feature file. + const feature = loadFeature('features/flagd-json-evaluator.feature'); + + // get a client (flagd provider registered in setup) + const client = OpenFeature.getClient(E2E_CLIENT_NAME); + + const aFlagProviderIsSet = (given: (stepMatcher: string, stepDefinitionCallback: () => void) => void) => { + given('a flagd provider is set', () => undefined); + }; + + // this is common to 4 tests + const evaluateStringFlagWithContext: StepsDefinitionCallbackFunction = ({ given, when, and, then }) => { + let flagKey: string; + let defaultValue: string; + const evaluationContext: EvaluationContext = {}; + + aFlagProviderIsSet(given); + when( + /^a string flag with key "(.*)" is evaluated with default value "(.*)"$/, + (key: string, defaultVal: string) => { + flagKey = key; + defaultValue = defaultVal; + }, + ); + // the below has to match quotes strings ("str") and numbers (3) to test an error input + and(/^a context containing a key "(.*)", with value "?([^"]*)"?$/, (key: string, value: string) => { + evaluationContext[key] = value; + }); + then(/^the returned value should be "(.*)"$/, async (expectedValue: string) => { + const value = await client.getStringValue(flagKey, defaultValue, evaluationContext); + expect(value).toEqual(expectedValue); + }); + }; + + const evaluateStringFlagWithFractional: StepsDefinitionCallbackFunction = ({ given, when, and, then }) => { + let flagKey: string; + let defaultValue: string; + const evaluationContext: EvaluationContext = {}; + + aFlagProviderIsSet(given); + when(/^a string flag with key "(.*)" is evaluated with default value "(.*)"$/, (key, defaultVal) => { + flagKey = key; + defaultValue = defaultVal; + }); + and( + /^a context containing a nested property with outer key "(.*)" and inner key "(.*)", with value (.*)$/, + (outerKey: string, innerKey: string, value: string) => { + // we have to support string and non-string params in this test (we test invalid context value 3) + const valueNoQuotes = value.replaceAll('"', ''); + evaluationContext[outerKey] = { + [innerKey]: parseInt(valueNoQuotes) || valueNoQuotes, + }; + }, + ); + then(/^the returned value should be "(.*)"$/, async (expectedValue: string) => { + const value = await client.getStringValue(flagKey, defaultValue, evaluationContext); + expect(value).toEqual(expectedValue); + }); + }; + + defineFeature(feature, (test) => { + beforeAll((done) => { + client.addHandler(ProviderEvents.Ready, async () => { + done(); + }); + }); + + test('Evaluator reuse', evaluateStringFlagWithContext); + + test('Fractional operator', evaluateStringFlagWithFractional); + + test('Fractional operator shorthand', ({ given, when, and, then }) => { + let flagKey: string; + let defaultValue: string; + let details: EvaluationDetails; + + aFlagProviderIsSet(given); + + when(/^a string flag with key "(.*)" is evaluated with default value "(.*)"$/, (key, defaultVal) => { + flagKey = key; + defaultValue = defaultVal; + }); + + and(/^a context containing a targeting key with value "(.*)"$/, async (targetingKeyValue) => { + details = await client.getStringDetails(flagKey, defaultValue, { targetingKey: targetingKeyValue }); + }); + + then(/^the returned value should be "(.*)"$/, (expectedValue) => { + expect(details.value).toEqual(expectedValue); + }); + }); + + test('Fractional operator with shared seed', evaluateStringFlagWithFractional); + + test('Second fractional operator with shared seed', evaluateStringFlagWithFractional); + + test('Substring operators', evaluateStringFlagWithContext); + + test('Semantic version operator numeric comparison', evaluateStringFlagWithContext); + + test('Semantic version operator semantic comparison', evaluateStringFlagWithContext); + + test('Time-based operations', ({ given, when, and, then }) => { + let flagKey: string; + let defaultValue: number; + const evaluationContext: EvaluationContext = {}; + + aFlagProviderIsSet(given); + + when(/^an integer flag with key "(.*)" is evaluated with default value (\d+)$/, (key, defaultVal) => { + flagKey = key; + defaultValue = defaultVal; + }); + + and(/^a context containing a key "(.*)", with value (.*)$/, (key, value) => { + evaluationContext[key] = value; + }); + then(/^the returned value should be (.*)$/, async (expectedValue) => { + const value = await client.getNumberValue(flagKey, defaultValue, evaluationContext); + expect(value).toEqual(parseInt(expectedValue)); + }); + }); + + test('Targeting by targeting key', ({ given, when, and, then }) => { + let flagKey: string; + let defaultValue: string; + let details: EvaluationDetails; + + aFlagProviderIsSet(given); + + when(/^a string flag with key "(.*)" is evaluated with default value "(.*)"$/, (key, defaultVal) => { + flagKey = key; + defaultValue = defaultVal; + }); + + and(/^a context containing a targeting key with value "(.*)"$/, async (targetingKeyValue) => { + details = await client.getStringDetails(flagKey, defaultValue, { targetingKey: targetingKeyValue }); + }); + + then(/^the returned value should be "(.*)"$/, (expectedValue) => { + expect(details.value).toEqual(expectedValue); + }); + + then(/^the returned reason should be "(.*)"$/, (expectedReason) => { + expect(details.reason).toEqual(expectedReason); + }); + }); + + test('Errors and edge cases', ({ given, when, then }) => { + let flagKey: string; + let defaultValue: number; + + aFlagProviderIsSet(given); + + when(/^an integer flag with key "(.*)" is evaluated with default value (.*)$/, (key, defaultVal) => { + flagKey = key; + defaultValue = parseInt(defaultVal); + }); + + then(/^the returned value should be (.*)$/, async (expectedValue) => { + const value = await client.getNumberValue(flagKey, defaultValue); + expect(value).toEqual(parseInt(expectedValue)); + }); + }); + }); +} diff --git a/libs/providers/flagd/src/e2e/step-definitions/flagd-reconnect.unstable.spec.ts b/libs/providers/flagd/src/e2e/step-definitions/flagd-reconnect.unstable.spec.ts deleted file mode 100644 index b886062d5..000000000 --- a/libs/providers/flagd/src/e2e/step-definitions/flagd-reconnect.unstable.spec.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { OpenFeature, ProviderEvents } from '@openfeature/server-sdk'; -import { defineFeature, loadFeature } from 'jest-cucumber'; -import { UNAVAILABLE_CLIENT_NAME, UNSTABLE_CLIENT_NAME } from '../constants'; - -jest.setTimeout(30000); - -// load the feature file. -const feature = loadFeature('features/flagd-reconnect.feature'); - -// get a client (flagd provider registered in setup) -const client = OpenFeature.getClient(UNSTABLE_CLIENT_NAME); - -defineFeature(feature, (test) => { - let readyRunCount = 0; - let errorRunCount = 0; - - beforeAll((done) => { - client.addHandler(ProviderEvents.Ready, async () => { - readyRunCount++; - done(); - }); - }); - - afterAll(async () => { - await OpenFeature.close(); - }); - - test('Provider reconnection', ({ given, when, then, and }) => { - given('a flagd provider is set', () => { - // handled in beforeAll - }); - when('a PROVIDER_READY handler and a PROVIDER_ERROR handler are added', () => { - client.addHandler(ProviderEvents.Error, () => { - errorRunCount++; - }); - }); - then('the PROVIDER_READY handler must run when the provider connects', async () => { - // should already be at 1 from `beforeAll` - expect(readyRunCount).toEqual(1); - }); - and("the PROVIDER_ERROR handler must run when the provider's connection is lost", async () => { - await new Promise((resolve) => setTimeout(resolve, 10000)); - expect(errorRunCount).toBeGreaterThan(0); - }); - and('when the connection is reestablished the PROVIDER_READY handler must run again', async () => { - await new Promise((resolve) => setTimeout(resolve, 10000)); - expect(readyRunCount).toBeGreaterThan(1); - }); - }); - - test('Provider unavailable', ({ given, when, then }) => { - let errorHandlerRun = 0; - - given('flagd is unavailable', async () => { - // handled in setup - }); - - when('a flagd provider is set and initialization is awaited', () => { - OpenFeature.getClient(UNAVAILABLE_CLIENT_NAME).addHandler(ProviderEvents.Error, () => { - errorHandlerRun++; - }); - }); - - then('an error should be indicated within the configured deadline', () => { - expect(errorHandlerRun).toBeGreaterThan(0); - }); - }); -}); diff --git a/libs/providers/flagd/src/e2e/step-definitions/flagd-reconnect.unstable.ts b/libs/providers/flagd/src/e2e/step-definitions/flagd-reconnect.unstable.ts new file mode 100644 index 000000000..d66af00ee --- /dev/null +++ b/libs/providers/flagd/src/e2e/step-definitions/flagd-reconnect.unstable.ts @@ -0,0 +1,66 @@ +import { OpenFeature, ProviderEvents } from '@openfeature/server-sdk'; +import { defineFeature, loadFeature } from 'jest-cucumber'; +import { UNAVAILABLE_CLIENT_NAME, UNSTABLE_CLIENT_NAME } from '../constants'; + +jest.setTimeout(30000); + +export function flagdRecconnectUnstable() { + // load the feature file. + const feature = loadFeature('features/flagd-reconnect.feature'); + + // get a client (flagd provider registered in setup) + const client = OpenFeature.getClient(UNSTABLE_CLIENT_NAME); + + defineFeature(feature, (test) => { + let readyRunCount = 0; + let errorRunCount = 0; + + beforeAll((done) => { + client.addHandler(ProviderEvents.Ready, async () => { + readyRunCount++; + done(); + }); + }); + + test('Provider reconnection', ({ given, when, then, and }) => { + given('a flagd provider is set', () => { + // handled in beforeAll + }); + when('a PROVIDER_READY handler and a PROVIDER_ERROR handler are added', () => { + client.addHandler(ProviderEvents.Error, () => { + errorRunCount++; + }); + }); + then('the PROVIDER_READY handler must run when the provider connects', async () => { + // should already be at 1 from `beforeAll` + expect(readyRunCount).toEqual(1); + }); + and("the PROVIDER_ERROR handler must run when the provider's connection is lost", async () => { + await new Promise((resolve) => setTimeout(resolve, 10000)); + expect(errorRunCount).toBeGreaterThan(0); + }); + and('when the connection is reestablished the PROVIDER_READY handler must run again', async () => { + await new Promise((resolve) => setTimeout(resolve, 10000)); + expect(readyRunCount).toBeGreaterThan(1); + }); + }); + + test('Provider unavailable', ({ given, when, then }) => { + let errorHandlerRun = 0; + + given('flagd is unavailable', async () => { + // handled in setup + }); + + when('a flagd provider is set and initialization is awaited', () => { + OpenFeature.getClient(UNAVAILABLE_CLIENT_NAME).addHandler(ProviderEvents.Error, () => { + errorHandlerRun++; + }); + }); + + then('an error should be indicated within the configured deadline', () => { + expect(errorHandlerRun).toBeGreaterThan(0); + }); + }); + }); +} diff --git a/libs/providers/flagd/src/e2e/step-definitions/flagd.spec.ts b/libs/providers/flagd/src/e2e/step-definitions/flagd.spec.ts deleted file mode 100644 index ac3217af8..000000000 --- a/libs/providers/flagd/src/e2e/step-definitions/flagd.spec.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { OpenFeature, ProviderEvents } from '@openfeature/server-sdk'; -import { defineFeature, loadFeature } from 'jest-cucumber'; -import { E2E_CLIENT_NAME } from '../constants'; - -// load the feature file. -const feature = loadFeature('features/flagd.feature'); - -// get a client (flagd provider registered in setup) -const client = OpenFeature.getClient(E2E_CLIENT_NAME); - -const aFlagProviderIsSet = (given: (stepMatcher: string, stepDefinitionCallback: () => void) => void) => { - given('a flagd provider is set', () => undefined); -}; - -defineFeature(feature, (test) => { - beforeAll((done) => { - client.addHandler(ProviderEvents.Ready, async () => { - done(); - }); - }); - - afterAll(async () => { - await OpenFeature.close(); - }); - - test('Provider ready event', ({ given, when, then }) => { - let ran = false; - - aFlagProviderIsSet(given); - when('a PROVIDER_READY handler is added', () => { - client.addHandler(ProviderEvents.Ready, async () => { - ran = true; - }); - }); - then('the PROVIDER_READY handler must run', () => { - expect(ran).toBeTruthy(); - }); - }); - - test('Flag change event', ({ given, when, and, then }) => { - let ran = false; - let flagsChanged: string[]; - - aFlagProviderIsSet(given); - when('a PROVIDER_CONFIGURATION_CHANGED handler is added', () => { - client.addHandler(ProviderEvents.ConfigurationChanged, async (details) => { - ran = true; - // file writes are not atomic, so we get a few events in quick succession from the testbed - // some will not contain changes, this tolerates that; at least 1 should have our change - if (details?.flagsChanged?.length) { - flagsChanged = details?.flagsChanged; - } - }); - }); - and(/^a flag with key "(.*)" is modified$/, async () => { - // this happens every 1s in the associated container, so wait 3s - await new Promise((resolve) => setTimeout(resolve, 3000)); - }); - then('the PROVIDER_CONFIGURATION_CHANGED handler must run', () => { - expect(ran).toBeTruthy(); - }); - and(/^the event details must indicate "(.*)" was altered$/, (flagName) => { - expect(flagsChanged).toContain(flagName); - }); - }); - - test('Resolves boolean zero value', ({ given, when, then }) => { - let flagKey: string; - let defaultValue: boolean; - - aFlagProviderIsSet(given); - when( - /^a zero-value boolean flag with key "(.*)" is evaluated with default value "(.*)"$/, - (key, defaultVal: string) => { - flagKey = key; - defaultValue = defaultVal === 'true'; - }, - ); - then(/^the resolved boolean zero-value should be "(.*)"$/, async (expectedVal: string) => { - const expectedValue = expectedVal === 'true'; - const value = await client.getBooleanValue(flagKey, defaultValue); - expect(value).toEqual(expectedValue); - }); - }); - - test('Resolves string zero value', ({ given, when, then }) => { - let flagKey: string; - let defaultValue: string; - - aFlagProviderIsSet(given); - when(/^a zero-value string flag with key "(.*)" is evaluated with default value "(.*)"$/, (key, defaultVal) => { - flagKey = key; - defaultValue = defaultVal; - }); - then('the resolved string zero-value should be ""', async () => { - const value = await client.getStringValue(flagKey, defaultValue); - expect(value).toEqual(''); - }); - }); - - test('Resolves integer zero value', ({ given, when, then }) => { - let flagKey: string; - let defaultValue: number; - - aFlagProviderIsSet(given); - when(/^a zero-value integer flag with key "(.*)" is evaluated with default value (\d+)$/, (key, defaultVal) => { - flagKey = key; - defaultValue = defaultVal; - }); - then(/^the resolved integer zero-value should be (\d+)$/, async (expectedValueString) => { - const expectedValue = Number.parseInt(expectedValueString); - const value = await client.getNumberValue(flagKey, defaultValue); - expect(value).toEqual(expectedValue); - }); - }); - - test('Resolves float zero value', ({ given, when, then }) => { - let flagKey: string; - let defaultValue: number; - - aFlagProviderIsSet(given); - when( - /^a zero-value float flag with key "(.*)" is evaluated with default value (\d+\.\d+)$/, - (key, defaultValueString) => { - flagKey = key; - defaultValue = Number.parseFloat(defaultValueString); - }, - ); - then(/^the resolved float zero-value should be (\d+\.\d+)$/, async (expectedValueString) => { - const expectedValue = Number.parseFloat(expectedValueString); - const value = await client.getNumberValue(flagKey, defaultValue); - expect(value).toEqual(expectedValue); - }); - }); -}); diff --git a/libs/providers/flagd/src/e2e/step-definitions/flagd.ts b/libs/providers/flagd/src/e2e/step-definitions/flagd.ts new file mode 100644 index 000000000..0a59c073b --- /dev/null +++ b/libs/providers/flagd/src/e2e/step-definitions/flagd.ts @@ -0,0 +1,133 @@ +import { OpenFeature, ProviderEvents } from '@openfeature/server-sdk'; +import { defineFeature, loadFeature } from 'jest-cucumber'; +import { E2E_CLIENT_NAME } from '../constants'; + +export function flagd() { + // load the feature file. + const feature = loadFeature('features/flagd.feature'); + + // get a client (flagd provider registered in setup) + const client = OpenFeature.getClient(E2E_CLIENT_NAME); + + const aFlagProviderIsSet = (given: (stepMatcher: string, stepDefinitionCallback: () => void) => void) => { + given('a flagd provider is set', () => undefined); + }; + + defineFeature(feature, (test) => { + beforeAll((done) => { + client.addHandler(ProviderEvents.Ready, async () => { + done(); + }); + }); + + test('Provider ready event', ({ given, when, then }) => { + let ran = false; + + aFlagProviderIsSet(given); + when('a PROVIDER_READY handler is added', () => { + client.addHandler(ProviderEvents.Ready, async () => { + ran = true; + }); + }); + then('the PROVIDER_READY handler must run', () => { + expect(ran).toBeTruthy(); + }); + }); + + test('Flag change event', ({ given, when, and, then }) => { + let ran = false; + let flagsChanged: string[]; + + aFlagProviderIsSet(given); + when('a PROVIDER_CONFIGURATION_CHANGED handler is added', () => { + client.addHandler(ProviderEvents.ConfigurationChanged, async (details) => { + ran = true; + // file writes are not atomic, so we get a few events in quick succession from the testbed + // some will not contain changes, this tolerates that; at least 1 should have our change + if (details?.flagsChanged?.length) { + flagsChanged = details?.flagsChanged; + } + }); + }); + and(/^a flag with key "(.*)" is modified$/, async () => { + // this happens every 1s in the associated container, so wait 3s + await new Promise((resolve) => setTimeout(resolve, 3000)); + }); + then('the PROVIDER_CONFIGURATION_CHANGED handler must run', () => { + expect(ran).toBeTruthy(); + }); + and(/^the event details must indicate "(.*)" was altered$/, (flagName) => { + expect(flagsChanged).toContain(flagName); + }); + }); + + test('Resolves boolean zero value', ({ given, when, then }) => { + let flagKey: string; + let defaultValue: boolean; + + aFlagProviderIsSet(given); + when( + /^a zero-value boolean flag with key "(.*)" is evaluated with default value "(.*)"$/, + (key, defaultVal: string) => { + flagKey = key; + defaultValue = defaultVal === 'true'; + }, + ); + then(/^the resolved boolean zero-value should be "(.*)"$/, async (expectedVal: string) => { + const expectedValue = expectedVal === 'true'; + const value = await client.getBooleanValue(flagKey, defaultValue); + expect(value).toEqual(expectedValue); + }); + }); + + test('Resolves string zero value', ({ given, when, then }) => { + let flagKey: string; + let defaultValue: string; + + aFlagProviderIsSet(given); + when(/^a zero-value string flag with key "(.*)" is evaluated with default value "(.*)"$/, (key, defaultVal) => { + flagKey = key; + defaultValue = defaultVal; + }); + then('the resolved string zero-value should be ""', async () => { + const value = await client.getStringValue(flagKey, defaultValue); + expect(value).toEqual(''); + }); + }); + + test('Resolves integer zero value', ({ given, when, then }) => { + let flagKey: string; + let defaultValue: number; + + aFlagProviderIsSet(given); + when(/^a zero-value integer flag with key "(.*)" is evaluated with default value (\d+)$/, (key, defaultVal) => { + flagKey = key; + defaultValue = defaultVal; + }); + then(/^the resolved integer zero-value should be (\d+)$/, async (expectedValueString) => { + const expectedValue = Number.parseInt(expectedValueString); + const value = await client.getNumberValue(flagKey, defaultValue); + expect(value).toEqual(expectedValue); + }); + }); + + test('Resolves float zero value', ({ given, when, then }) => { + let flagKey: string; + let defaultValue: number; + + aFlagProviderIsSet(given); + when( + /^a zero-value float flag with key "(.*)" is evaluated with default value (\d+\.\d+)$/, + (key, defaultValueString) => { + flagKey = key; + defaultValue = Number.parseFloat(defaultValueString); + }, + ); + then(/^the resolved float zero-value should be (\d+\.\d+)$/, async (expectedValueString) => { + const expectedValue = Number.parseFloat(expectedValueString); + const value = await client.getNumberValue(flagKey, defaultValue); + expect(value).toEqual(expectedValue); + }); + }); + }); +} diff --git a/libs/providers/flagd/src/e2e/tests/in-process-provider.spec.ts b/libs/providers/flagd/src/e2e/tests/in-process-provider.spec.ts new file mode 100644 index 000000000..eef1dd558 --- /dev/null +++ b/libs/providers/flagd/src/e2e/tests/in-process-provider.spec.ts @@ -0,0 +1,88 @@ +import assert from 'assert'; +import { OpenFeature } from '@openfeature/server-sdk'; +import { FlagdProvider } from '../../lib/flagd-provider'; +import { + E2E_CLIENT_NAME, + FLAGD_NAME, + UNSTABLE_CLIENT_NAME, + UNAVAILABLE_CLIENT_NAME, + IMAGE_VERSION, +} from '../constants'; +import { evaluation } from '../step-definitions/evaluation'; +import { GenericContainer, StartedTestContainer, TestContainer } from 'testcontainers'; +import { flagd } from '../step-definitions/flagd'; +import { flagdJsonEvaluator } from '../step-definitions/flagd-json-evaluator'; +import { flagdRecconnectUnstable } from '../step-definitions/flagd-reconnect.unstable'; + +// register the flagd provider before the tests. +async function setup() { + const containers: StartedTestContainer[] = []; + + console.log('Setting flagd provider...'); + + const stable = await new GenericContainer(`ghcr.io/open-feature/sync-testbed:${IMAGE_VERSION}`) + .withExposedPorts(9090) + .start(); + containers.push(stable); + OpenFeature.setProvider( + E2E_CLIENT_NAME, + new FlagdProvider({ resolverType: 'in-process', host: 'localhost', port: stable.getFirstMappedPort() }), + ); + + const unstable = await new GenericContainer(`ghcr.io/open-feature/sync-testbed-unstable:${IMAGE_VERSION}`) + .withExposedPorts(9090) + .start(); + containers.push(unstable); + OpenFeature.setProvider( + UNSTABLE_CLIENT_NAME, + new FlagdProvider({ resolverType: 'in-process', host: 'localhost', port: unstable.getFirstMappedPort() }), + ); + + OpenFeature.setProvider( + UNAVAILABLE_CLIENT_NAME, + new FlagdProvider({ resolverType: 'in-process', host: 'localhost', port: 9092 }), + ); + assert( + OpenFeature.getProviderMetadata(E2E_CLIENT_NAME).name === FLAGD_NAME, + new Error( + `Expected ${FLAGD_NAME} provider to be configured, instead got: ${ + OpenFeature.getProviderMetadata(E2E_CLIENT_NAME).name + }`, + ), + ); + assert( + OpenFeature.getProviderMetadata(UNSTABLE_CLIENT_NAME).name === FLAGD_NAME, + new Error( + `Expected ${FLAGD_NAME} provider to be configured, instead got: ${ + OpenFeature.getProviderMetadata(UNSTABLE_CLIENT_NAME).name + }`, + ), + ); + assert( + OpenFeature.getProviderMetadata(UNAVAILABLE_CLIENT_NAME).name === FLAGD_NAME, + new Error( + `Expected ${FLAGD_NAME} provider to be configured, instead got: ${ + OpenFeature.getProviderMetadata(UNAVAILABLE_CLIENT_NAME).name + }`, + ), + ); + console.log('flagd provider configured!'); + return containers; +} + +describe('in process', () => { + let containers: StartedTestContainer[] = []; + beforeAll(async () => { + containers = await setup(); + }, 60000); + afterAll(async () => { + await OpenFeature.close(); + for (const container of containers) { + container.stop(); + } + }); + evaluation(); + flagd(); + flagdJsonEvaluator(); + flagdRecconnectUnstable(); +}); diff --git a/libs/providers/flagd/src/e2e/tests/rpc-provider.spec.ts b/libs/providers/flagd/src/e2e/tests/rpc-provider.spec.ts new file mode 100644 index 000000000..87426d111 --- /dev/null +++ b/libs/providers/flagd/src/e2e/tests/rpc-provider.spec.ts @@ -0,0 +1,81 @@ +import assert from 'assert'; +import { OpenFeature } from '@openfeature/server-sdk'; +import { FlagdProvider } from '../../lib/flagd-provider'; +import { + E2E_CLIENT_NAME, + FLAGD_NAME, + UNSTABLE_CLIENT_NAME, + UNAVAILABLE_CLIENT_NAME, + IMAGE_VERSION, +} from '../constants'; +import { evaluation } from '../step-definitions/evaluation'; +import { GenericContainer, StartedTestContainer, TestContainer } from 'testcontainers'; +import { flagd } from '../step-definitions/flagd'; +import { flagdJsonEvaluator } from '../step-definitions/flagd-json-evaluator'; +import { flagdRecconnectUnstable } from '../step-definitions/flagd-reconnect.unstable'; + +// register the flagd provider before the tests. +async function setup() { + const containers: StartedTestContainer[] = []; + + console.log('Setting flagd provider...'); + + const stable = await new GenericContainer(`ghcr.io/open-feature/flagd-testbed:${IMAGE_VERSION}`) + .withExposedPorts(8013) + .start(); + containers.push(stable); + OpenFeature.setProvider(E2E_CLIENT_NAME, new FlagdProvider({ cache: 'disabled', port: stable.getFirstMappedPort() })); + + const unstable = await new GenericContainer(`ghcr.io/open-feature/flagd-testbed-unstable:${IMAGE_VERSION}`) + .withExposedPorts(8013) + .start(); + containers.push(unstable); + OpenFeature.setProvider( + UNSTABLE_CLIENT_NAME, + new FlagdProvider({ cache: 'disabled', port: unstable.getFirstMappedPort() }), + ); + OpenFeature.setProvider(UNAVAILABLE_CLIENT_NAME, new FlagdProvider({ cache: 'disabled', port: 8015 })); + assert( + OpenFeature.getProviderMetadata(E2E_CLIENT_NAME).name === FLAGD_NAME, + new Error( + `Expected ${FLAGD_NAME} provider to be configured, instead got: ${ + OpenFeature.getProviderMetadata(E2E_CLIENT_NAME).name + }`, + ), + ); + assert( + OpenFeature.getProviderMetadata(UNSTABLE_CLIENT_NAME).name === FLAGD_NAME, + new Error( + `Expected ${FLAGD_NAME} provider to be configured, instead got: ${ + OpenFeature.getProviderMetadata(UNSTABLE_CLIENT_NAME).name + }`, + ), + ); + assert( + OpenFeature.getProviderMetadata(UNAVAILABLE_CLIENT_NAME).name === FLAGD_NAME, + new Error( + `Expected ${FLAGD_NAME} provider to be configured, instead got: ${ + OpenFeature.getProviderMetadata(UNAVAILABLE_CLIENT_NAME).name + }`, + ), + ); + console.log('flagd provider configured!'); + return containers; +} + +describe('rpc', () => { + let containers: StartedTestContainer[] = []; + beforeAll(async () => { + containers = await setup(); + }, 60000); + afterAll(async () => { + await OpenFeature.close(); + for (const container of containers) { + container.stop(); + } + }); + evaluation(); + flagd(); + flagdJsonEvaluator(); + flagdRecconnectUnstable(); +}); diff --git a/libs/shared/flagd-core/flagd-schemas b/libs/shared/flagd-core/flagd-schemas index 2aa89b314..1ba4b0324 160000 --- a/libs/shared/flagd-core/flagd-schemas +++ b/libs/shared/flagd-core/flagd-schemas @@ -1 +1 @@ -Subproject commit 2aa89b31432284507af3873de9b0bb7b68dd02c7 +Subproject commit 1ba4b0324771273e6db7d4a808f6dc87a0b3c717 diff --git a/package-lock.json b/package-lock.json index eeacc93d2..1d4a7a4fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "tslib": "2.6.2" }, "devDependencies": { + "@bufbuild/buf": "^1.34.0", "@nx/devkit": "16.9.1", "@nx/eslint-plugin": "16.9.1", "@nx/jest": "16.9.1", @@ -81,6 +82,7 @@ "msw": "^2.2.3", "nx": "16.9.1", "prettier": "^3.0.3", + "testcontainers": "10.9.0", "ts-jest": "29.2.0", "ts-node": "10.9.2", "typescript": "5.5.3", @@ -2061,12 +2063,137 @@ "node": ">=6.9.0" } }, + "node_modules/@balena/dockerignore": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", + "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", + "dev": true + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@bufbuild/buf": { + "version": "1.34.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.34.0.tgz", + "integrity": "sha512-DR0P746bYiY7ziQTui0bKAvPa7ihCNxONWLtW54HQXvTkGnTc6C1keVaSz4UhNdSsBu/Xsj69GO9SizodfjUtQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "buf": "bin/buf", + "protoc-gen-buf-breaking": "bin/protoc-gen-buf-breaking", + "protoc-gen-buf-lint": "bin/protoc-gen-buf-lint" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@bufbuild/buf-darwin-arm64": "1.34.0", + "@bufbuild/buf-darwin-x64": "1.34.0", + "@bufbuild/buf-linux-aarch64": "1.34.0", + "@bufbuild/buf-linux-x64": "1.34.0", + "@bufbuild/buf-win32-arm64": "1.34.0", + "@bufbuild/buf-win32-x64": "1.34.0" + } + }, + "node_modules/@bufbuild/buf-darwin-arm64": { + "version": "1.34.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.34.0.tgz", + "integrity": "sha512-3+h/jSAr7H+KT8MWWRMbN/gQ87KlGLkTGwm4/mpry1ap9Thw/UdOrk5MfmbK3CRM/rlw4mAn1Egu/Q7R5eO98g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-darwin-x64": { + "version": "1.34.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.34.0.tgz", + "integrity": "sha512-Jdm0COuA2CMKoef2H8rBsRnc16mJUmCQ2KvJH5otvFrMhzPmr1MUyicCybY26HXFD/6DcnbWZvf6W8LfDMMyGQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-linux-aarch64": { + "version": "1.34.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.34.0.tgz", + "integrity": "sha512-utSspJlPmVPh4Ugvn9k7MEEMHDZMI13jvwHkBE6wNSkYxxYTRR5zLHtmysaYQo51Fx+3ar6mL4HnhTqLrgO5GA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-linux-x64": { + "version": "1.34.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.34.0.tgz", + "integrity": "sha512-INCGsPLBL4aK2jHBMdZzEJUPv7f6f8skIUMMip7YdJl1nsIh27C/Dl7Q6A6/sv9IhYibWKAoxP7SuiOv2iTdEw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-win32-arm64": { + "version": "1.34.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.34.0.tgz", + "integrity": "sha512-g1EogebjJ93bzmyn/fEi47tTz57M+7WYZ7/vX+DFXgLLYIxTWHDK4YN+3Hs+K7Sbx7KaVdsdEqof8xZ4WoVFnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-win32-x64": { + "version": "1.34.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.34.0.tgz", + "integrity": "sha512-0rPXP7pV7+2twhcpN8hDdgV68UCiazLRcMBjWKubwcSJhAP8jRLqSJv3VGnXmpdYPbYGDQ0htfcgLNUvzllRhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@bufbuild/protobuf": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz", @@ -4418,6 +4545,27 @@ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", "dev": true }, + "node_modules/@types/docker-modem": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", + "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/ssh2": "*" + } + }, + "node_modules/@types/dockerode": { + "version": "3.3.29", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.29.tgz", + "integrity": "sha512-5PRRq/yt5OT/Jf77ltIdz4EiR9+VLnPF+HpU4xGFwUqmV24Co2HKBNW3w+slqZ1CYchbcDeqJASHDYWzZCcMiQ==", + "dev": true, + "dependencies": { + "@types/docker-modem": "*", + "@types/node": "*", + "@types/ssh2": "*" + } + }, "node_modules/@types/estree": { "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", @@ -4614,6 +4762,33 @@ "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, + "node_modules/@types/ssh2": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.0.tgz", + "integrity": "sha512-YcT8jP5F8NzWeevWvcyrrLB3zcneVjzYY9ZDSMAMboI+2zR1qYWFhwsyOFVzT7Jorn67vqxC0FRiw8YyG9P1ww==", + "dev": true, + "dependencies": { + "@types/node": "^18.11.18" + } + }, + "node_modules/@types/ssh2-streams": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@types/ssh2-streams/-/ssh2-streams-0.1.12.tgz", + "integrity": "sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ssh2/node_modules/@types/node": { + "version": "18.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz", + "integrity": "sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -5052,6 +5227,75 @@ } ] }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dev": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -5076,6 +5320,15 @@ "node": ">=8" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, "node_modules/assert": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", @@ -5095,6 +5348,12 @@ "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "dev": true }, + "node_modules/async-lock": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", + "dev": true + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -5175,6 +5434,12 @@ "axios": ">= 0.17.0" } }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", + "dev": true + }, "node_modules/babel-helper-evaluate-path": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/babel-helper-evaluate-path/-/babel-helper-evaluate-path-0.5.0.tgz", @@ -5624,6 +5889,52 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bare-events": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "dev": true, + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", + "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.0.tgz", + "integrity": "sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==", + "dev": true, + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", + "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", + "dev": true, + "optional": true, + "dependencies": { + "streamx": "^2.18.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -5661,6 +5972,15 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/bin-check": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", @@ -5870,12 +6190,31 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/buildcheck": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", + "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -5888,6 +6227,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/byline": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", + "integrity": "sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -6024,6 +6372,12 @@ "node": ">=10" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -6216,6 +6570,21 @@ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dev": true, + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6329,6 +6698,12 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, "node_modules/corser": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", @@ -6354,6 +6729,46 @@ "node": ">=8" } }, + "node_modules/cpu-features": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.19.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dev": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -6822,6 +7237,71 @@ "node": ">=8" } }, + "node_modules/docker-compose": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.8.tgz", + "integrity": "sha512-plizRs/Vf15H+GCVxq2EUvyPK7ei9b/cVesHvjnX4xaXjM9spHe2Ytq0BitndFgvTJ3E3NljPNUEl7BAN43iZw==", + "dev": true, + "dependencies": { + "yaml": "^2.2.2" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/docker-compose/node_modules/yaml": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/docker-modem": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.8.tgz", + "integrity": "sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "readable-stream": "^3.5.0", + "split-ca": "^1.0.1", + "ssh2": "^1.11.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/dockerode": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.3.5.tgz", + "integrity": "sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==", + "dev": true, + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "docker-modem": "^3.0.0", + "tar-fs": "~2.0.1" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/dockerode/node_modules/tar-fs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", + "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -7658,6 +8138,12 @@ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", @@ -8185,6 +8671,18 @@ "node": ">=8.0.0" } }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", @@ -9048,6 +9546,12 @@ "node": ">=8" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -10220,6 +10724,48 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -10298,6 +10844,24 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, "node_modules/lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", @@ -10308,6 +10872,12 @@ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -10326,6 +10896,12 @@ "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", "dev": true }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true + }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -10543,6 +11119,12 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, "node_modules/mock-socket": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", @@ -10628,6 +11210,13 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/nan": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", + "dev": true, + "optional": true + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -12062,6 +12651,12 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, "node_modules/promise-polyfill": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", @@ -12090,6 +12685,45 @@ "node": ">= 6" } }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/properties-reader": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/properties-reader/-/properties-reader-2.3.0.tgz", + "integrity": "sha512-z597WicA7nDZxK12kZqHr2TcvwNU1GCfA5UwfDY/HDp3hXPoPlb5rlEx9bwGTiJnc0OqbBTkU975jDToth8Gxw==", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/properties?sponsor=1" + } + }, + "node_modules/properties-reader/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/protobufjs": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.0.tgz", @@ -12215,6 +12849,12 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -12263,6 +12903,36 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/reflect-metadata": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", @@ -12441,6 +13111,15 @@ "node": ">=8" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -12949,12 +13628,56 @@ "deprecated": "Please use @jridgewell/sourcemap-codec instead", "dev": true }, + "node_modules/split-ca": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", + "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", + "dev": true + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/ssh-remote-port-forward": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz", + "integrity": "sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==", + "dev": true, + "dependencies": { + "@types/ssh2": "^0.5.48", + "ssh2": "^1.4.0" + } + }, + "node_modules/ssh-remote-port-forward/node_modules/@types/ssh2": { + "version": "0.5.52", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.52.tgz", + "integrity": "sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/ssh2-streams": "*" + } + }, + "node_modules/ssh2": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.15.0.tgz", + "integrity": "sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.9", + "nan": "^2.18.0" + } + }, "node_modules/stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", @@ -12992,6 +13715,20 @@ "node": ">= 0.8" } }, + "node_modules/streamx": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/strict-event-emitter": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", @@ -13261,6 +13998,31 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/tar-fs/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -13291,6 +14053,38 @@ "node": ">=8" } }, + "node_modules/testcontainers": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.9.0.tgz", + "integrity": "sha512-LN+cKAOd61Up9SVMJW+3VFVGeVQG8JBqZhEQo2U0HBfIsAynyAXcsLBSo+KZrOfy9SBz7pGHctWN/KabLDbNFA==", + "dev": true, + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "@types/dockerode": "^3.3.24", + "archiver": "^5.3.2", + "async-lock": "^1.4.1", + "byline": "^5.0.0", + "debug": "^4.3.4", + "docker-compose": "^0.24.6", + "dockerode": "^3.3.5", + "get-port": "^5.1.1", + "node-fetch": "^2.7.0", + "proper-lockfile": "^4.1.2", + "properties-reader": "^2.3.0", + "ssh-remote-port-forward": "^1.0.4", + "tar-fs": "^3.0.5", + "tmp": "^0.2.1" + } + }, + "node_modules/text-decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz", + "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -13553,6 +14347,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -14103,6 +14903,41 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dev": true, + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } } } } diff --git a/package.json b/package.json index 04c857d27..7753d6d2e 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "assert": "^2.0.0", "axios-mock-adapter": "1.22.0", "babel-preset-minify": "0.5.2", + "@bufbuild/buf": "^1.34.0", "eslint": "8.57.0", "eslint-config-prettier": "8.10.0", "eslint-plugin-prettier": "^5.0.1", @@ -86,6 +87,7 @@ "msw": "^2.2.3", "nx": "16.9.1", "prettier": "^3.0.3", + "testcontainers": "10.9.0", "ts-jest": "29.2.0", "ts-node": "10.9.2", "typescript": "5.5.3", From 69ca1c27293bcf50c08a9cbab3d53b856b691ce8 Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Mon, 8 Jul 2024 19:28:19 +0200 Subject: [PATCH 2/2] Update libs/providers/flagd/src/e2e/tests/rpc-provider.spec.ts Co-authored-by: Lukas Reining Signed-off-by: Simon Schrottner --- libs/providers/flagd-web/project.json | 2 +- libs/providers/flagd/src/e2e/tests/rpc-provider.spec.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/libs/providers/flagd-web/project.json b/libs/providers/flagd-web/project.json index 5991774ee..428f1b7db 100644 --- a/libs/providers/flagd-web/project.json +++ b/libs/providers/flagd-web/project.json @@ -74,7 +74,7 @@ "executor": "nx:run-commands", "options": { "commands": [ - "npx jest --detectOpenHandles --runInBand" + "npx jest" ], "cwd": "libs/providers/flagd-web/src/e2e", "parallel": false diff --git a/libs/providers/flagd/src/e2e/tests/rpc-provider.spec.ts b/libs/providers/flagd/src/e2e/tests/rpc-provider.spec.ts index 87426d111..3e17f7d9b 100644 --- a/libs/providers/flagd/src/e2e/tests/rpc-provider.spec.ts +++ b/libs/providers/flagd/src/e2e/tests/rpc-provider.spec.ts @@ -71,7 +71,11 @@ describe('rpc', () => { afterAll(async () => { await OpenFeature.close(); for (const container of containers) { - container.stop(); + try { + await container.stop(); + } catch { + console.warn(`Failed to stop container ${container.getName()}`); + } } }); evaluation();