From fe5d2aefab9ae2aa5c15b94c168784e1c56bef90 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Wed, 14 Sep 2022 12:39:26 -0400 Subject: [PATCH] chore: add integration tests Signed-off-by: Todd Baert --- .github/workflows/pr-checks.yaml | 13 +- .gitignore | 4 + .gitmodules | 3 + README.md | 4 + integration/features/.gitignore | 1 + integration/features/.gitkeep | 0 .../step-definitions/evaluation.spec.ts | 320 ++++++++++++++++++ integration/step-definitions/jest.config.ts | 16 + integration/step-definitions/setup.ts | 11 + integration/step-definitions/tsconfig.json | 8 + jest.config.ts | 6 +- package-lock.json | 124 ++----- package.json | 15 +- test-harness | 1 + 14 files changed, 420 insertions(+), 106 deletions(-) create mode 100644 .gitmodules create mode 100644 integration/features/.gitignore create mode 100644 integration/features/.gitkeep create mode 100644 integration/step-definitions/evaluation.spec.ts create mode 100644 integration/step-definitions/jest.config.ts create mode 100644 integration/step-definitions/setup.ts create mode 100644 integration/step-definitions/tsconfig.json create mode 160000 test-harness diff --git a/.github/workflows/pr-checks.yaml b/.github/workflows/pr-checks.yaml index fd2c3e103..9460c8662 100644 --- a/.github/workflows/pr-checks.yaml +++ b/.github/workflows/pr-checks.yaml @@ -13,6 +13,12 @@ jobs: build-test-lint: runs-on: ubuntu-latest + services: + flagd: + image: ghcr.io/open-feature/flagd-testbed:latest + ports: + - 8013:8013 + steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 @@ -20,11 +26,14 @@ jobs: - name: Install run: npm ci + - name: Build + run: npm run build + - name: Lint run: npm run lint - - name: Build - run: npm run build + - name: Integration + run: npm run integration - name: Test run: npm run test diff --git a/.gitignore b/.gitignore index 67045665d..15612debe 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,7 @@ dist # TernJS port file .tern-port + +# yalc stuff +yalc.lock +.yalc/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..61d2eb45d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "test-harness"] + path = test-harness + url = https://github.com/open-feature/test-harness.git diff --git a/README.md b/README.md index 6820ce70f..c5ee2bb6d 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,10 @@ We value having as few runtime dependencies as possible. The addition of any dep Run tests with `npm test`. +### Integration tests + +The continuous integration runs a set of [gherkin integration tests](https://github.com/open-feature/test-harness/blob/main/features/evaluation.feature) using [`flagd`](https://github.com/open-feature/flagd). These tests run with the "integration" npm script. If you'd like to run them locally, you can start the flagd testbed with `docker run -p 8013:8013 ghcr.io/open-feature/flagd-testbed:latest` and then run `npm run integration`. + ### Packaging Both ES modules and CommonJS modules are supported, so consumers can use both `require` and `import` functions to utilize this module. This is accomplished by building 2 variations of the output, under `dist/esm` and `dist/cjs`, respectively. To force resolution of the `dist/esm/**.js*` files as modules, a package json with only the context `{"type": "module"}` is included at a in a `postbuild` step. Type declarations are included at `/dist/types/` diff --git a/integration/features/.gitignore b/integration/features/.gitignore new file mode 100644 index 000000000..ce4de1a72 --- /dev/null +++ b/integration/features/.gitignore @@ -0,0 +1 @@ +evaluation.feature \ No newline at end of file diff --git a/integration/features/.gitkeep b/integration/features/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/integration/step-definitions/evaluation.spec.ts b/integration/step-definitions/evaluation.spec.ts new file mode 100644 index 000000000..2bbe0e84b --- /dev/null +++ b/integration/step-definitions/evaluation.spec.ts @@ -0,0 +1,320 @@ +import { defineFeature, loadFeature } from 'jest-cucumber'; +import { OpenFeature } from '../../src/open-feature'; +import { + EvaluationContext, + EvaluationDetails, + JsonObject, + JsonValue, + ResolutionDetails, + ResolutionReason, + StandardResolutionReasons +} from '../../src/types'; + +// load the feature file. +const feature = loadFeature('integration/features/evaluation.feature'); + +// get a client (flagd provider registered in setup) +const client = OpenFeature.getClient(); + +defineFeature(feature, (test) => { + test('Resolves boolean value', ({ when, then }) => { + let value: boolean; + let flagKey: string; + + 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', ({ when, then }) => { + let value: string; + let flagKey: string; + + 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', ({ when, then }) => { + let value: number; + let flagKey: string; + + 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', ({ when, then }) => { + let value: number; + let flagKey: string; + + 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', ({ when, then }) => { + let value: JsonValue; + let flagKey: string; + + 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', ({ when, then }) => { + let details: EvaluationDetails; + let flagKey: string; + + 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(convertExpectationToStatic(expectedReason)); + } + ); + }); + + test('Resolves string details', ({ when, then }) => { + let details: EvaluationDetails; + let flagKey: string; + + 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(convertExpectationToStatic(expectedReason)); + } + ); + }); + + test('Resolves integer details', ({ when, then }) => { + let details: EvaluationDetails; + let flagKey: string; + + 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(convertExpectationToStatic(expectedReason)); + } + ); + }); + + test('Resolves float details', ({ when, then }) => { + let details: EvaluationDetails; + let flagKey: string; + + 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(convertExpectationToStatic(expectedReason)); + } + ); + }); + + test('Resolves object details', ({ when, then, and }) => { + let details: EvaluationDetails; // update this after merge + let flagKey: string; + + 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(convertExpectationToStatic(expectedReason)); + } + ); + }); + + test('Resolves based on context', ({ when, and, then }) => { + const context: EvaluationContext = {}; + let value: string; + let flagKey: string; + + 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', ({ when, then, and }) => { + let flagKey: string; + let fallbackValue: string; + let details: ResolutionDetails; + + 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(/^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', ({ when, then, and }) => { + let flagKey: string; + let fallbackValue: number; + let details: ResolutionDetails; + + 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(/^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); + } + ); + }); +}); + +const convertExpectationToStatic = (reason: ResolutionReason) => + reason === StandardResolutionReasons.DEFAULT ? StandardResolutionReasons.STATIC : undefined; diff --git a/integration/step-definitions/jest.config.ts b/integration/step-definitions/jest.config.ts new file mode 100644 index 000000000..76fcb5f31 --- /dev/null +++ b/integration/step-definitions/jest.config.ts @@ -0,0 +1,16 @@ +export default { + clearMocks: true, + collectCoverage: true, + coverageDirectory: 'coverage', + coverageProvider: 'v8', + globals: { + 'ts-jest': { + tsConfig: 'integration/step-definitions/tsconfig.json', + }, + }, + moduleNameMapper: { + '^(.*)\\.js$': ['$1', '$1.js'], + }, + setupFiles: ['./setup.ts'], + preset: 'ts-jest', +}; diff --git a/integration/step-definitions/setup.ts b/integration/step-definitions/setup.ts new file mode 100644 index 000000000..1ccf0613f --- /dev/null +++ b/integration/step-definitions/setup.ts @@ -0,0 +1,11 @@ +import { OpenFeature } from '../../src/open-feature.js'; +import { FlagdProvider } from '@openfeature/flagd-provider'; +import assert from 'assert'; + +const FLAGD_NAME = 'flagd Provider'; + +// register the flagd provider before the tests. +console.log('Setting flagd provider...'); +OpenFeature.setProvider(new FlagdProvider()); +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!'); diff --git a/integration/step-definitions/tsconfig.json b/integration/step-definitions/tsconfig.json new file mode 100644 index 000000000..788a92c26 --- /dev/null +++ b/integration/step-definitions/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "moduleResolution": "node" + }, + "include": ["./**/*.ts"], + "exclude": ["node_modules", "**/*.test.js"] +} diff --git a/jest.config.ts b/jest.config.ts index b7992d218..4fab614f8 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -27,7 +27,7 @@ export default { // we need this so jest doesn't break with our .js naming rule. moduleNameMapper: { - '(.*).js': '$1', + '^(.*)\\.js$': ['$1', '$1.js'], }, // An array of regexp pattern strings used to skip coverage collection @@ -129,9 +129,7 @@ export default { // rootDir: undefined, // A list of paths to directories that Jest should use to search for files in - // roots: [ - // "" - // ], + roots: ['/test'], // Allows you to use a custom runner instead of Jest's default test runner // runner: "jest-runner", diff --git a/package-lock.json b/package-lock.json index b75c6a61b..586018bcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.4.0", "license": "Apache-2.0", "devDependencies": { + "@openfeature/flagd-provider": "^0.5.1", "@types/jest": "^28.1.4", "@types/node": "^18.0.3", "@typescript-eslint/eslint-plugin": "^5.23.0", @@ -16,15 +17,18 @@ "esbuild": "^0.15.1", "eslint": "^8.14.0", "eslint-config-prettier": "^8.5.0", + "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-check-file": "^1.2.1", + "eslint-plugin-import": "^2.26.0", "eslint-plugin-jest": "^27.0.1", "eslint-plugin-jsdoc": "^39.3.6", - "jest": "^28.1.0", + "jest": "^28.1.3", + "jest-cucumber": "^3.0.1", "jest-junit": "^14.0.0", "prettier": "^2.6.2", - "ts-jest": "^28.0.2", - "ts-node": "^10.7.0", - "typescript": "^4.6.4" + "ts-jest": "^28.0.8", + "ts-node": "^10.8.2", + "typescript": "^4.7.4" }, "engines": { "node": ">=14" @@ -1205,23 +1209,22 @@ } }, "node_modules/@openfeature/flagd-provider": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@openfeature/flagd-provider/-/flagd-provider-0.4.0.tgz", - "integrity": "sha512-LX+o4TuC1SqvxbUe+VtuzqPzhvhYsMPOde1OUu/SD2XQE3MAXjKC0oVULySSg7g6XCRfsHA7Z10r+fDXT08kaA==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@openfeature/flagd-provider/-/flagd-provider-0.5.1.tgz", + "integrity": "sha512-BQFy5mmje3H2Sg9UfRS/Dqz4s/HbTx2DvBXr44cEmKfyOrlHYCsMloEu0J3lajoy01lQ7qyVPMRhSPdUpB42BQ==", "dev": true, "dependencies": { "@grpc/grpc-js": "^1.6.7", - "@protobuf-ts/grpc-transport": "^2.7.0", - "axios": "^0.27.2" + "@protobuf-ts/grpc-transport": "^2.7.0" }, "peerDependencies": { - "@openfeature/nodejs-sdk": "^0.2.0" + "@openfeature/js-sdk": "^0.4.0" } }, - "node_modules/@openfeature/nodejs-sdk": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@openfeature/nodejs-sdk/-/nodejs-sdk-0.2.0.tgz", - "integrity": "sha512-icIqrVQ+OhCTPBtJCNxCJviFVeOT5XIVwKD8UE4wWiUnyWnw+PEoccOnfQcSuNX0wp5qnorQ6c+lliK3LBEJMQ==", + "node_modules/@openfeature/js-sdk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@openfeature/js-sdk/-/js-sdk-0.4.0.tgz", + "integrity": "sha512-tfZNxg7QCKIVDrBRktIaqnphANXloyHSZJs2weumXRF6e1OiTSVTbnPtYZZCr15PMiIzEQUQheulloTepLzAmA==", "dev": true, "peer": true, "engines": { @@ -2027,16 +2030,6 @@ "node": ">= 4.5.0" } }, - "node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "dev": true, - "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - }, "node_modules/babel-jest": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", @@ -3907,26 +3900,6 @@ "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", "dev": true }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -3936,20 +3909,6 @@ "node": ">=0.10.0" } }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -12523,19 +12482,19 @@ } }, "@openfeature/flagd-provider": { - "version": "https://registry.npmjs.org/@openfeature/flagd-provider/-/flagd-provider-0.4.0.tgz", - "integrity": "sha512-LX+o4TuC1SqvxbUe+VtuzqPzhvhYsMPOde1OUu/SD2XQE3MAXjKC0oVULySSg7g6XCRfsHA7Z10r+fDXT08kaA==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@openfeature/flagd-provider/-/flagd-provider-0.5.1.tgz", + "integrity": "sha512-BQFy5mmje3H2Sg9UfRS/Dqz4s/HbTx2DvBXr44cEmKfyOrlHYCsMloEu0J3lajoy01lQ7qyVPMRhSPdUpB42BQ==", "dev": true, "requires": { "@grpc/grpc-js": "^1.6.7", - "@protobuf-ts/grpc-transport": "^2.7.0", - "axios": "^0.27.2" + "@protobuf-ts/grpc-transport": "^2.7.0" } }, - "@openfeature/nodejs-sdk": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@openfeature/nodejs-sdk/-/nodejs-sdk-0.2.0.tgz", - "integrity": "sha512-icIqrVQ+OhCTPBtJCNxCJviFVeOT5XIVwKD8UE4wWiUnyWnw+PEoccOnfQcSuNX0wp5qnorQ6c+lliK3LBEJMQ==", + "@openfeature/js-sdk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@openfeature/js-sdk/-/js-sdk-0.4.0.tgz", + "integrity": "sha512-tfZNxg7QCKIVDrBRktIaqnphANXloyHSZJs2weumXRF6e1OiTSVTbnPtYZZCr15PMiIzEQUQheulloTepLzAmA==", "dev": true, "peer": true }, @@ -13156,16 +13115,6 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, - "axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "dev": true, - "requires": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - }, "babel-jest": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", @@ -14085,7 +14034,8 @@ "requires": {} }, "eslint-import-resolver-alias": { - "version": "https://registry.npmjs.org/eslint-import-resolver-alias/-/eslint-import-resolver-alias-1.1.2.tgz", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-alias/-/eslint-import-resolver-alias-1.1.2.tgz", "integrity": "sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==", "dev": true, "requires": {} @@ -14604,29 +14554,12 @@ "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", "dev": true }, - "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "dev": true - }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", "dev": true }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -16283,7 +16216,8 @@ } }, "jest-cucumber": { - "version": "https://registry.npmjs.org/jest-cucumber/-/jest-cucumber-3.0.1.tgz", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jest-cucumber/-/jest-cucumber-3.0.1.tgz", "integrity": "sha512-S2EelgezfwWP10VCgUkSOiJYiTIM0yM82KxrwBOn68wMmlqU5jNSf7xDIBS0tGwoFnNwUTFp7LPFmEnfilSJrA==", "dev": true, "requires": { diff --git a/package.json b/package.json index 1d708dfa6..ab1615370 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "types": "./dist/types/index.d.ts", "scripts": { "test": "jest --verbose", + "integration": "git submodule update --init --recursive && cp test-harness/features/evaluation.feature integration/features && npx jest --config integration/step-definitions/jest.config.ts", "lint": "eslint ./", "clean": "rm -fr ./dist", "type": "tsc --project tsconfig.json --declaration --emitDeclarationOnly", @@ -43,6 +44,7 @@ } }, "devDependencies": { + "@openfeature/flagd-provider": "^0.5.1", "@types/jest": "^28.1.4", "@types/node": "^18.0.3", "@typescript-eslint/eslint-plugin": "^5.23.0", @@ -50,14 +52,17 @@ "esbuild": "^0.15.1", "eslint": "^8.14.0", "eslint-config-prettier": "^8.5.0", + "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-check-file": "^1.2.1", + "eslint-plugin-import": "^2.26.0", "eslint-plugin-jest": "^27.0.1", "eslint-plugin-jsdoc": "^39.3.6", - "jest": "^28.1.0", + "jest": "^28.1.3", + "jest-cucumber": "^3.0.1", "jest-junit": "^14.0.0", "prettier": "^2.6.2", - "ts-jest": "^28.0.2", - "ts-node": "^10.7.0", - "typescript": "^4.6.4" + "ts-jest": "^28.0.8", + "ts-node": "^10.8.2", + "typescript": "^4.7.4" } -} \ No newline at end of file +} diff --git a/test-harness b/test-harness new file mode 160000 index 000000000..e7379cd00 --- /dev/null +++ b/test-harness @@ -0,0 +1 @@ +Subproject commit e7379cd0070f8907cacdc535184f8f626bf25e01