From 5d85fa0276dbdc4d1165583fdaec77ee462e984c Mon Sep 17 00:00:00 2001 From: Yusinto Ngadiman Date: Thu, 27 Apr 2023 17:23:26 -0700 Subject: [PATCH 01/13] fix: fixed bug where the feature store does not deserialize KV values correctly. --- packages/sdk/cloudflare/package.json | 2 +- .../cloudflare/src/createFeatureStore.test.ts | 28 ++++-- .../sdk/cloudflare/src/createFeatureStore.ts | 29 ++++-- packages/sdk/cloudflare/src/index.test.ts | 91 ++++++++++++++----- .../sdk/cloudflare/src/utils/testData.json | 79 +++++++++++++++- packages/shared/sdk-server/src/store/index.ts | 3 +- .../sdk-server/src/store/serialization.ts | 1 + 7 files changed, 188 insertions(+), 45 deletions(-) diff --git a/packages/sdk/cloudflare/package.json b/packages/sdk/cloudflare/package.json index d4735de2ce..8a5f8c92b3 100644 --- a/packages/sdk/cloudflare/package.json +++ b/packages/sdk/cloudflare/package.json @@ -33,7 +33,7 @@ "start": "rimraf dist && yarn tsw", "lint": "eslint . --ext .ts", "prettier": "prettier --write '**/*.@(js|ts|tsx|json|css)' --ignore-path ../../../.prettierignore", - "test": "NODE_OPTIONS=\"--experimental-vm-modules --no-warnings\" jest --ci --runInBand --coverage", + "test": "NODE_OPTIONS=\"--experimental-vm-modules --no-warnings\" jest --ci --runInBand", "check": "yarn prettier && yarn lint && yarn build && yarn test && yarn doc" }, "dependencies": { diff --git a/packages/sdk/cloudflare/src/createFeatureStore.test.ts b/packages/sdk/cloudflare/src/createFeatureStore.test.ts index cda7e2f9c8..717a4424b6 100644 --- a/packages/sdk/cloudflare/src/createFeatureStore.test.ts +++ b/packages/sdk/cloudflare/src/createFeatureStore.test.ts @@ -7,6 +7,16 @@ import * as testData from './utils/testData.json'; describe('createFeatureStore', () => { const sdkKey = 'sdkKey'; const kvKey = `LD-Env-${sdkKey}`; + const { + testFlag1: { debugEventsUntilDate: d1, ...testFlag1Subset }, + testFlag2: { debugEventsUntilDate: d2, ...testFlag2Subset }, + testFlag3: { debugEventsUntilDate: d3, ...testFlag3Subset }, + } = testData.flags; + const testDataFlagsSubset = { + testFlag1: testFlag1Subset, + testFlag2: testFlag2Subset, + testFlag3: testFlag3Subset, + }; const mockLogger = { error: jest.fn(), warn: jest.fn(), @@ -18,7 +28,7 @@ describe('createFeatureStore', () => { let asyncFeatureStore: AsyncStoreFacade; beforeEach(() => { - mockGet.mockImplementation(() => Promise.resolve(testData)); + mockGet.mockImplementation(() => Promise.resolve(JSON.stringify(testData))); featureStore = createFeatureStore(mockKV, sdkKey, mockLogger); asyncFeatureStore = new AsyncStoreFacade(featureStore); }); @@ -31,8 +41,8 @@ describe('createFeatureStore', () => { test('get flag', async () => { const flag = await asyncFeatureStore.get({ namespace: 'features' }, 'testFlag1'); - expect(mockGet).toHaveBeenCalledWith(kvKey, { type: 'json' }); - expect(flag).toEqual(testData.flags.testFlag1); + expect(mockGet).toHaveBeenCalledWith(kvKey); + expect(flag).toMatchObject(testFlag1Subset); }); test('invalid flag key', async () => { @@ -44,8 +54,8 @@ describe('createFeatureStore', () => { test('get segment', async () => { const segment = await asyncFeatureStore.get({ namespace: 'segments' }, 'testSegment1'); - expect(mockGet).toHaveBeenCalledWith(kvKey, { type: 'json' }); - expect(segment).toEqual(testData.segments.testSegment1); + expect(mockGet).toHaveBeenCalledWith(kvKey); + expect(segment).toMatchObject(testData.segments.testSegment1); }); test('invalid segment key', async () => { @@ -66,15 +76,15 @@ describe('createFeatureStore', () => { test('all flags', async () => { const flag = await asyncFeatureStore.all({ namespace: 'features' }); - expect(mockGet).toHaveBeenCalledWith(kvKey, { type: 'json' }); - expect(flag).toEqual(testData.flags); + expect(mockGet).toHaveBeenCalledWith(kvKey); + expect(flag).toMatchObject(testDataFlagsSubset); }); test('all segments', async () => { const segment = await asyncFeatureStore.all({ namespace: 'segments' }); - expect(mockGet).toHaveBeenCalledWith(kvKey, { type: 'json' }); - expect(segment).toEqual(testData.segments); + expect(mockGet).toHaveBeenCalledWith(kvKey); + expect(segment).toMatchObject(testData.segments); }); test('invalid DataKind', async () => { diff --git a/packages/sdk/cloudflare/src/createFeatureStore.ts b/packages/sdk/cloudflare/src/createFeatureStore.ts index 87b19a615a..c4f8540e88 100644 --- a/packages/sdk/cloudflare/src/createFeatureStore.ts +++ b/packages/sdk/cloudflare/src/createFeatureStore.ts @@ -7,6 +7,7 @@ import { LDFeatureStoreItem, LDFeatureStoreKindData, noop, + deserializePoll, } from '@launchdarkly/js-server-sdk-common-edge'; const createFeatureStore = (kvNamespace: KVNamespace, sdkKey: string, logger: LDLogger) => { @@ -17,15 +18,20 @@ const createFeatureStore = (kvNamespace: KVNamespace, sdkKey: string, logger: LD flagKey: string, callback: (res: LDFeatureStoreItem | null) => void = noop ): void { - logger.debug(`Requesting ${flagKey} from ${key}`); + const kindKey = kind.namespace === 'features' ? 'flags' : kind.namespace; + logger.debug(`Requesting ${flagKey} from ${key}:${kindKey}`); + kvNamespace - .get(key, { type: 'json' }) + .get(key) .then((i) => { if (i === null) { - logger.error('Feature data not found in KV.'); + throw new Error(`The ${kindKey} key: ${key} is not found in KV.`); + } + + const item = deserializePoll(i); + if (!item) { + throw new Error(`Error deserializing ${kindKey}`); } - const kindKey = kind.namespace === 'features' ? 'flags' : kind.namespace; - const item = i as LDFeatureStoreItem; callback(item[kindKey][flagKey]); }) .catch((err) => { @@ -35,14 +41,19 @@ const createFeatureStore = (kvNamespace: KVNamespace, sdkKey: string, logger: LD }, all(kind: DataKind, callback: (res: LDFeatureStoreKindData) => void = noop): void { const kindKey = kind.namespace === 'features' ? 'flags' : kind.namespace; - logger.debug(`Requesting all ${kindKey} data from KV.`); + logger.debug(`Requesting all from ${key}:${kindKey}`); kvNamespace - .get(key, { type: 'json' }) + .get(key) .then((i) => { if (i === null) { - logger.error('Feature data not found in KV.'); + throw new Error(`The ${kindKey} key: ${key} is not found in KV.`); } - const item = i as LDFeatureStoreItem; + + const item = deserializePoll(i); + if (!item) { + throw new Error(`Error deserializing ${kindKey}`); + } + callback(item[kindKey]); }) .catch((err) => { diff --git a/packages/sdk/cloudflare/src/index.test.ts b/packages/sdk/cloudflare/src/index.test.ts index 4db9f790a1..77f5b15796 100644 --- a/packages/sdk/cloudflare/src/index.test.ts +++ b/packages/sdk/cloudflare/src/index.test.ts @@ -1,5 +1,5 @@ import type { KVNamespace } from '@cloudflare/workers-types'; -import { LDClient } from '@launchdarkly/js-server-sdk-common-edge'; +import { LDClient, LDContext } from '@launchdarkly/js-server-sdk-common-edge'; import { Miniflare } from 'miniflare'; import { init } from './index'; import * as allFlagsSegments from './utils/testData.json'; @@ -11,8 +11,10 @@ const mf = new Miniflare({ }); const sdkKey = 'test-sdk-key'; -const flagKey = 'testFlag1'; -const context = { kind: 'user', key: 'test-user-key-1' }; +const flagKey1 = 'testFlag1'; +const flagKey2 = 'testFlag2'; +const flagKey3 = 'testFlag3'; +const context: LDContext = { kind: 'user', key: 'test-user-key-1' }; const namespace = 'LD_KV'; const rootEnvKey = `LD-Env-${sdkKey}`; @@ -31,28 +33,73 @@ describe('init', () => { ldClient.close(); }); - test('variation', async () => { - const flagDetail = await ldClient.variation(flagKey, context, false); - expect(flagDetail).toBeTruthy(); - }); + describe('flags', () => { + test('variation default', async () => { + const value = await ldClient.variation(flagKey1, context, false); + expect(value).toBeTruthy(); + }); + + test('variation default rollout', async () => { + const contextWithEmail = { ...context, email: 'test@yahoo.com' }; + const value = await ldClient.variation(flagKey2, contextWithEmail, false); + const detail = await ldClient.variationDetail(flagKey2, contextWithEmail, false); + + expect(detail).toEqual({ reason: { kind: 'FALLTHROUGH' }, value: true, variationIndex: 0 }); + expect(value).toBeTruthy(); + }); + + test('rule match', async () => { + const contextWithEmail = { ...context, email: 'test@gmail.com' }; + const value = await ldClient.variation(flagKey1, contextWithEmail, false); + const detail = await ldClient.variationDetail(flagKey1, contextWithEmail, false); + + expect(detail).toEqual({ + reason: { kind: 'RULE_MATCH', ruleId: 'rule1', ruleIndex: 0 }, + value: false, + variationIndex: 1, + }); + expect(value).toBeFalsy(); + }); + + test('fallthrough', async () => { + const contextWithEmail = { ...context, email: 'test@yahoo.com' }; + const value = await ldClient.variation(flagKey1, contextWithEmail, false); + const detail = await ldClient.variationDetail(flagKey1, contextWithEmail, false); + + expect(detail).toEqual({ reason: { kind: 'FALLTHROUGH' }, value: true, variationIndex: 0 }); + expect(value).toBeTruthy(); + }); - test('variationDetail', async () => { - const flagDetail = await ldClient.variationDetail(flagKey, context, false); - expect(flagDetail).toEqual({ reason: { kind: 'FALLTHROUGH' }, value: true, variationIndex: 0 }); + test('allFlags fallthrough', async () => { + const allFlags = await ldClient.allFlagsState(context); + + expect(allFlags).toBeDefined(); + expect(allFlags.toJSON()).toEqual({ + $flagsState: { + testFlag1: { variation: 0, version: 2 }, + testFlag2: { variation: 0, version: 2 }, + testFlag3: { variation: 0, version: 2 }, + }, + $valid: true, + testFlag1: true, + testFlag2: true, + testFlag3: true, + }); + }); }); - test('allFlags', async () => { - const allFlags = await ldClient.allFlagsState(context); - - expect(allFlags).toBeDefined(); - expect(allFlags.toJSON()).toEqual({ - $flagsState: { - testFlag1: { debugEventsUntilDate: null, variation: 0, version: 2 }, - testFlag2: { debugEventsUntilDate: null, variation: 1, version: 2 }, - }, - $valid: true, - testFlag1: true, - testFlag2: false, + describe('segments', () => { + test('segment by country', async () => { + const contextWithCountry = { ...context, country: 'australia' }; + const value = await ldClient.variation(flagKey3, contextWithCountry, false); + const detail = await ldClient.variationDetail(flagKey3, contextWithCountry, false); + + expect(detail).toEqual({ + reason: { kind: 'RULE_MATCH', ruleId: 'rule1', ruleIndex: 0 }, + value: false, + variationIndex: 1, + }); + expect(value).toBeFalsy(); }); }); }); diff --git a/packages/sdk/cloudflare/src/utils/testData.json b/packages/sdk/cloudflare/src/utils/testData.json index c3498bd291..495819a24d 100644 --- a/packages/sdk/cloudflare/src/utils/testData.json +++ b/packages/sdk/cloudflare/src/utils/testData.json @@ -5,7 +5,26 @@ "on": true, "prerequisites": [], "targets": [], - "rules": [], + "rules": [ + { + "variation": 1, + "id": "rule1", + "clauses": [ + { + "contextKind": "user", + "attribute": "/email", + "op": "contains", + "values": ["gmail"], + "negate": false + } + ], + "trackEvents": false, + "rollout": { + "bucketBy": "bucket", + "variations": [{ "variation": 1, "weight": 100 }] + } + } + ], "fallthrough": { "variation": 0 }, @@ -25,10 +44,52 @@ }, "testFlag2": { "key": "testFlag2", - "on": false, + "on": true, "prerequisites": [], "targets": [], "rules": [], + "fallthrough": { + "variation": 0, + "rollout": { + "bucketBy": "bucket", + "variations": [{ "variation": 1, "weight": 100 }], + "contextKind:": "user", + "attribute": "/email" + } + }, + "offVariation": 1, + "variations": [true, false], + "clientSideAvailability": { + "usingMobileKey": true, + "usingEnvironmentId": true + }, + "clientSide": true, + "salt": "aef830243d6640d0a973be89988e008d", + "trackEvents": false, + "trackEventsFallthrough": false, + "debugEventsUntilDate": null, + "version": 2, + "deleted": false + }, + "testFlag3": { + "key": "testFlag3", + "on": true, + "prerequisites": [], + "targets": [], + "rules": [ + { + "variation": 1, + "id": "rule1", + "clauses": [ + { + "op": "segmentMatch", + "values": ["testSegment1"], + "negate": false + } + ], + "trackEvents": false + } + ], "fallthrough": { "variation": 0 }, @@ -65,7 +126,19 @@ }, "site": { "href": "/default/test/segments/beta-users-1", "type": "text/html" } }, - "rules": [], + "rules": [ + { + "id": "rule-country", + "clauses": [ + { + "attribute": "country", + "op": "in", + "values": ["australia"], + "negate": false + } + ] + } + ], "version": 1, "deleted": false, "_access": { "denied": [], "allowed": [] }, diff --git a/packages/shared/sdk-server/src/store/index.ts b/packages/shared/sdk-server/src/store/index.ts index ff3b0c3049..941c99aa72 100644 --- a/packages/shared/sdk-server/src/store/index.ts +++ b/packages/shared/sdk-server/src/store/index.ts @@ -1,4 +1,5 @@ import AsyncStoreFacade from './AsyncStoreFacade'; +import { deserializePoll } from './serialization'; // eslint-disable-next-line import/prefer-default-export -export { AsyncStoreFacade }; +export { AsyncStoreFacade, deserializePoll }; diff --git a/packages/shared/sdk-server/src/store/serialization.ts b/packages/shared/sdk-server/src/store/serialization.ts index 62cef866bb..a75f803a4e 100644 --- a/packages/shared/sdk-server/src/store/serialization.ts +++ b/packages/shared/sdk-server/src/store/serialization.ts @@ -22,6 +22,7 @@ export function reviver(this: any, key: string, value: any): any { } interface FlagsAndSegments { + [key: string]: { [name: string]: Flag } | { [name: string]: Segment }; flags: { [name: string]: Flag }; segments: { [name: string]: Segment }; } From b8f8fc6d3f215a061744b21eec33960404b11388 Mon Sep 17 00:00:00 2001 From: Yusinto Ngadiman Date: Thu, 27 Apr 2023 17:31:17 -0700 Subject: [PATCH 02/13] chore: remove eslint comment --- packages/shared/sdk-server/src/store/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/shared/sdk-server/src/store/index.ts b/packages/shared/sdk-server/src/store/index.ts index 941c99aa72..c8c7c0e463 100644 --- a/packages/shared/sdk-server/src/store/index.ts +++ b/packages/shared/sdk-server/src/store/index.ts @@ -1,5 +1,4 @@ import AsyncStoreFacade from './AsyncStoreFacade'; import { deserializePoll } from './serialization'; -// eslint-disable-next-line import/prefer-default-export export { AsyncStoreFacade, deserializePoll }; From bfdcae3d519e31d1c34405a80a42a712486322c5 Mon Sep 17 00:00:00 2001 From: Yusinto Ngadiman Date: Thu, 27 Apr 2023 17:55:40 -0700 Subject: [PATCH 03/13] chore:update example to target rules rather than a contrived scenario. --- packages/sdk/cloudflare/example/package.json | 2 +- packages/sdk/cloudflare/example/src/index.ts | 9 ++- .../sdk/cloudflare/example/src/testData.json | 79 ++++++++++++++++++- 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/packages/sdk/cloudflare/example/package.json b/packages/sdk/cloudflare/example/package.json index 2af63e6b9b..0a611ef0c0 100644 --- a/packages/sdk/cloudflare/example/package.json +++ b/packages/sdk/cloudflare/example/package.json @@ -5,7 +5,7 @@ "module": "./dist/index.mjs", "packageManager": "yarn@3.4.1", "dependencies": { - "@launchdarkly/cloudflare-server-sdk": "0.0.1" + "@launchdarkly/cloudflare-server-sdk": "2.0.1" }, "devDependencies": { "@cloudflare/workers-types": "^4.20230321.0", diff --git a/packages/sdk/cloudflare/example/src/index.ts b/packages/sdk/cloudflare/example/src/index.ts index fc8804da43..9658d32c07 100644 --- a/packages/sdk/cloudflare/example/src/index.ts +++ b/packages/sdk/cloudflare/example/src/index.ts @@ -4,7 +4,7 @@ export default { async fetch(request: Request, env: Bindings): Promise { const sdkKey = 'test-sdk-key'; const flagKey = 'testFlag1'; - const context = { kind: 'user', key: 'test-user-key-1' }; + const context = { kind: 'user', key: 'test-user-key-1', email: 'test@gmail.com' }; // start using ld const client = initLD(sdkKey, env.LD_KV); @@ -13,9 +13,10 @@ export default { const flagDetail = await client.variationDetail(flagKey, context, false); const allFlags = await client.allFlagsState(context); - const resp = `${flagKey}: ${flagValue}, detail: ${JSON.stringify( - flagDetail - )}, allFlags: ${JSON.stringify(allFlags)}`; + const resp = ` + ${flagKey}: ${flagValue} + detail: ${JSON.stringify(flagDetail)} + allFlags: ${JSON.stringify(allFlags)}`; // eslint-disable-next-line console.log(`------------- ${resp}`); diff --git a/packages/sdk/cloudflare/example/src/testData.json b/packages/sdk/cloudflare/example/src/testData.json index c3498bd291..495819a24d 100644 --- a/packages/sdk/cloudflare/example/src/testData.json +++ b/packages/sdk/cloudflare/example/src/testData.json @@ -5,7 +5,26 @@ "on": true, "prerequisites": [], "targets": [], - "rules": [], + "rules": [ + { + "variation": 1, + "id": "rule1", + "clauses": [ + { + "contextKind": "user", + "attribute": "/email", + "op": "contains", + "values": ["gmail"], + "negate": false + } + ], + "trackEvents": false, + "rollout": { + "bucketBy": "bucket", + "variations": [{ "variation": 1, "weight": 100 }] + } + } + ], "fallthrough": { "variation": 0 }, @@ -25,10 +44,52 @@ }, "testFlag2": { "key": "testFlag2", - "on": false, + "on": true, "prerequisites": [], "targets": [], "rules": [], + "fallthrough": { + "variation": 0, + "rollout": { + "bucketBy": "bucket", + "variations": [{ "variation": 1, "weight": 100 }], + "contextKind:": "user", + "attribute": "/email" + } + }, + "offVariation": 1, + "variations": [true, false], + "clientSideAvailability": { + "usingMobileKey": true, + "usingEnvironmentId": true + }, + "clientSide": true, + "salt": "aef830243d6640d0a973be89988e008d", + "trackEvents": false, + "trackEventsFallthrough": false, + "debugEventsUntilDate": null, + "version": 2, + "deleted": false + }, + "testFlag3": { + "key": "testFlag3", + "on": true, + "prerequisites": [], + "targets": [], + "rules": [ + { + "variation": 1, + "id": "rule1", + "clauses": [ + { + "op": "segmentMatch", + "values": ["testSegment1"], + "negate": false + } + ], + "trackEvents": false + } + ], "fallthrough": { "variation": 0 }, @@ -65,7 +126,19 @@ }, "site": { "href": "/default/test/segments/beta-users-1", "type": "text/html" } }, - "rules": [], + "rules": [ + { + "id": "rule-country", + "clauses": [ + { + "attribute": "country", + "op": "in", + "values": ["australia"], + "negate": false + } + ] + } + ], "version": 1, "deleted": false, "_access": { "denied": [], "allowed": [] }, From 6b2f0d8d10898ec67ac50767eb2d9abfa5a1104a Mon Sep 17 00:00:00 2001 From: Yusinto Ngadiman Date: Fri, 28 Apr 2023 00:51:51 -0700 Subject: [PATCH 04/13] fix:fixed deserialization bug by using deserializePoll --- packages/sdk/vercel/package.json | 2 +- packages/sdk/vercel/src/createPlatformInfo.ts | 6 +- packages/sdk/vercel/src/index.test.ts | 98 ++++++++++---- packages/sdk/vercel/src/utils/testData.json | 125 +++++++++++++++++- .../sdk/vercel/src/vercelFeatureStore.test.ts | 44 ++++-- packages/sdk/vercel/src/vercelFeatureStore.ts | 37 ++++-- 6 files changed, 256 insertions(+), 56 deletions(-) diff --git a/packages/sdk/vercel/package.json b/packages/sdk/vercel/package.json index a7e1ac96cc..70472fb9d7 100644 --- a/packages/sdk/vercel/package.json +++ b/packages/sdk/vercel/package.json @@ -30,7 +30,7 @@ "start": "rimraf dist && yarn tsw", "lint": "eslint . --ext .ts", "prettier": "prettier --write '**/*.@(js|ts|tsx|json|css)'", - "test": "jest --ci --runInBand --coverage", + "test": "jest --ci --runInBand", "check": "yarn prettier && yarn lint && yarn build && yarn test && yarn doc" }, "dependencies": { diff --git a/packages/sdk/vercel/src/createPlatformInfo.ts b/packages/sdk/vercel/src/createPlatformInfo.ts index 9610afed52..4bcb2880ef 100644 --- a/packages/sdk/vercel/src/createPlatformInfo.ts +++ b/packages/sdk/vercel/src/createPlatformInfo.ts @@ -1,6 +1,6 @@ import type { Info, PlatformData, SdkData } from '@launchdarkly/js-server-sdk-common-edge'; -const packageJson = require('../package.json'); +import { name, version } from '../package.json'; class VercelPlatformInfo implements Info { platformData(): PlatformData { @@ -11,8 +11,8 @@ class VercelPlatformInfo implements Info { sdkData(): SdkData { return { - name: packageJson.name, - version: packageJson.version, + name, + version, }; } } diff --git a/packages/sdk/vercel/src/index.test.ts b/packages/sdk/vercel/src/index.test.ts index 76706116fc..90d45ed4b8 100644 --- a/packages/sdk/vercel/src/index.test.ts +++ b/packages/sdk/vercel/src/index.test.ts @@ -1,21 +1,20 @@ -import type { EdgeConfigValue } from '@vercel/edge-config'; -import { LDClient } from '@launchdarkly/js-server-sdk-common-edge'; +import { LDClient, LDContext } from '@launchdarkly/js-server-sdk-common-edge'; import { init } from './index'; import * as testData from './utils/testData.json'; import mockEdge from './utils/mockEdge'; const sdkKey = 'test-sdk-key'; -const flagKey = 'testFlag1'; -const context = { kind: 'user', key: 'test-user-key-1' }; +const flagKey1 = 'testFlag1'; +const flagKey2 = 'testFlag2'; +const flagKey3 = 'testFlag3'; +const context: LDContext = { kind: 'user', key: 'test-user-key-1' }; describe('init', () => { let ldClient: LDClient; + const mockGet = mockEdge.get as jest.Mock; beforeAll(async () => { - // I can't figure out a way around the generic types used in @vercel/edge-config causing us type issues here. - // The tests work as expected - // @ts-ignore - mockEdge.get = jest.fn(async () => testData as EdgeConfigValue); + mockGet.mockImplementation(() => Promise.resolve(JSON.stringify(testData))); ldClient = init(sdkKey, mockEdge); await ldClient.waitForInitialization(); }); @@ -24,28 +23,73 @@ describe('init', () => { ldClient.close(); }); - test('variation', async () => { - const flagDetail = await ldClient.variation(flagKey, context, false); - expect(flagDetail).toBeTruthy(); - }); + describe('flags', () => { + test('variation default', async () => { + const value = await ldClient.variation(flagKey1, context, false); + expect(value).toBeTruthy(); + }); + + test('variation default rollout', async () => { + const contextWithEmail = { ...context, email: 'test@yahoo.com' }; + const value = await ldClient.variation(flagKey2, contextWithEmail, false); + const detail = await ldClient.variationDetail(flagKey2, contextWithEmail, false); + + expect(detail).toEqual({ reason: { kind: 'FALLTHROUGH' }, value: true, variationIndex: 0 }); + expect(value).toBeTruthy(); + }); + + test('rule match', async () => { + const contextWithEmail = { ...context, email: 'test@gmail.com' }; + const value = await ldClient.variation(flagKey1, contextWithEmail, false); + const detail = await ldClient.variationDetail(flagKey1, contextWithEmail, false); + + expect(detail).toEqual({ + reason: { kind: 'RULE_MATCH', ruleId: 'rule1', ruleIndex: 0 }, + value: false, + variationIndex: 1, + }); + expect(value).toBeFalsy(); + }); + + test('fallthrough', async () => { + const contextWithEmail = { ...context, email: 'test@yahoo.com' }; + const value = await ldClient.variation(flagKey1, contextWithEmail, false); + const detail = await ldClient.variationDetail(flagKey1, contextWithEmail, false); + + expect(detail).toEqual({ reason: { kind: 'FALLTHROUGH' }, value: true, variationIndex: 0 }); + expect(value).toBeTruthy(); + }); - test('variationDetail', async () => { - const flagDetail = await ldClient.variationDetail(flagKey, context, false); - expect(flagDetail).toEqual({ reason: { kind: 'FALLTHROUGH' }, value: true, variationIndex: 0 }); + test('allFlags fallthrough', async () => { + const allFlags = await ldClient.allFlagsState(context); + + expect(allFlags).toBeDefined(); + expect(allFlags.toJSON()).toEqual({ + $flagsState: { + testFlag1: { variation: 0, version: 2 }, + testFlag2: { variation: 0, version: 2 }, + testFlag3: { variation: 0, version: 2 }, + }, + $valid: true, + testFlag1: true, + testFlag2: true, + testFlag3: true, + }); + }); }); - test('allFlags', async () => { - const allFlags = await ldClient.allFlagsState(context); - - expect(allFlags).toBeDefined(); - expect(allFlags.toJSON()).toEqual({ - $flagsState: { - testFlag1: { debugEventsUntilDate: null, variation: 0, version: 2 }, - testFlag2: { debugEventsUntilDate: null, variation: 1, version: 2 }, - }, - $valid: true, - testFlag1: true, - testFlag2: false, + describe('segments', () => { + test('segment by country', async () => { + const contextWithCountry = { ...context, country: 'australia' }; + const value = await ldClient.variation(flagKey3, contextWithCountry, false); + const detail = await ldClient.variationDetail(flagKey3, contextWithCountry, false); + + expect(detail).toEqual({ + reason: { kind: 'RULE_MATCH', ruleId: 'rule1', ruleIndex: 0 }, + value: false, + variationIndex: 1, + }); + expect(value).toBeFalsy(); }); }); }); diff --git a/packages/sdk/vercel/src/utils/testData.json b/packages/sdk/vercel/src/utils/testData.json index e5ebbdb071..495819a24d 100644 --- a/packages/sdk/vercel/src/utils/testData.json +++ b/packages/sdk/vercel/src/utils/testData.json @@ -5,7 +5,26 @@ "on": true, "prerequisites": [], "targets": [], - "rules": [], + "rules": [ + { + "variation": 1, + "id": "rule1", + "clauses": [ + { + "contextKind": "user", + "attribute": "/email", + "op": "contains", + "values": ["gmail"], + "negate": false + } + ], + "trackEvents": false, + "rollout": { + "bucketBy": "bucket", + "variations": [{ "variation": 1, "weight": 100 }] + } + } + ], "fallthrough": { "variation": 0 }, @@ -25,10 +44,52 @@ }, "testFlag2": { "key": "testFlag2", - "on": false, + "on": true, "prerequisites": [], "targets": [], "rules": [], + "fallthrough": { + "variation": 0, + "rollout": { + "bucketBy": "bucket", + "variations": [{ "variation": 1, "weight": 100 }], + "contextKind:": "user", + "attribute": "/email" + } + }, + "offVariation": 1, + "variations": [true, false], + "clientSideAvailability": { + "usingMobileKey": true, + "usingEnvironmentId": true + }, + "clientSide": true, + "salt": "aef830243d6640d0a973be89988e008d", + "trackEvents": false, + "trackEventsFallthrough": false, + "debugEventsUntilDate": null, + "version": 2, + "deleted": false + }, + "testFlag3": { + "key": "testFlag3", + "on": true, + "prerequisites": [], + "targets": [], + "rules": [ + { + "variation": 1, + "id": "rule1", + "clauses": [ + { + "op": "segmentMatch", + "values": ["testSegment1"], + "negate": false + } + ], + "trackEvents": false + } + ], "fallthrough": { "variation": 0 }, @@ -46,5 +107,65 @@ "version": 2, "deleted": false } + }, + "segments": { + "testSegment1": { + "name": "testSegment1", + "tags": [], + "creationDate": 1676063792158, + "key": "testSegment1", + "included": [], + "excluded": [], + "includedContexts": [], + "excludedContexts": [], + "_links": { + "parent": { "href": "/api/v2/segments/default/test", "type": "application/json" }, + "self": { + "href": "/api/v2/segments/default/test/beta-users-1", + "type": "application/json" + }, + "site": { "href": "/default/test/segments/beta-users-1", "type": "text/html" } + }, + "rules": [ + { + "id": "rule-country", + "clauses": [ + { + "attribute": "country", + "op": "in", + "values": ["australia"], + "negate": false + } + ] + } + ], + "version": 1, + "deleted": false, + "_access": { "denied": [], "allowed": [] }, + "generation": 1 + }, + "testSegment2": { + "name": "testSegment2", + "tags": [], + "creationDate": 1676063792158, + "key": "testSegment2", + "included": [], + "excluded": [], + "includedContexts": [], + "excludedContexts": [], + "_links": { + "parent": { "href": "/api/v2/segments/default/test", "type": "application/json" }, + "self": { + "href": "/api/v2/segments/default/test/beta-users-1", + "type": "application/json" + }, + "site": { "href": "/default/test/segments/beta-users-1", "type": "text/html" } + }, + "rules": [], + "version": 1, + "deleted": false, + "_access": { "denied": [], "allowed": [] }, + "generation": 1 + } } } diff --git a/packages/sdk/vercel/src/vercelFeatureStore.test.ts b/packages/sdk/vercel/src/vercelFeatureStore.test.ts index 956f41c70e..57b6b8d943 100644 --- a/packages/sdk/vercel/src/vercelFeatureStore.test.ts +++ b/packages/sdk/vercel/src/vercelFeatureStore.test.ts @@ -7,6 +7,16 @@ import * as testData from './utils/testData.json'; describe('VercelFeatureStore', () => { const sdkKey = 'sdkKey'; const configKey = `LD-Env-${sdkKey}`; + const { + testFlag1: { debugEventsUntilDate: d1, ...testFlag1Subset }, + testFlag2: { debugEventsUntilDate: d2, ...testFlag2Subset }, + testFlag3: { debugEventsUntilDate: d3, ...testFlag3Subset }, + } = testData.flags; + const testDataFlagsSubset = { + testFlag1: testFlag1Subset, + testFlag2: testFlag2Subset, + testFlag3: testFlag3Subset, + }; const mockLogger = { error: jest.fn(), warn: jest.fn(), @@ -18,7 +28,7 @@ describe('VercelFeatureStore', () => { let asyncFeatureStore: AsyncStoreFacade; beforeEach(() => { - mockGet.mockImplementation(() => Promise.resolve(testData)); + mockGet.mockImplementation(() => Promise.resolve(JSON.stringify(testData))); featureStore = new VercelFeatureStore(mockEdge, sdkKey, mockLogger); asyncFeatureStore = new AsyncStoreFacade(featureStore); }); @@ -32,7 +42,7 @@ describe('VercelFeatureStore', () => { const flag = await asyncFeatureStore.get({ namespace: 'features' }, 'testFlag1'); expect(mockGet).toHaveBeenCalledWith(configKey); - expect(flag).toEqual(testData.flags.testFlag1); + expect(flag).toMatchObject(testFlag1Subset); }); test('invalid flag key', async () => { @@ -41,13 +51,20 @@ describe('VercelFeatureStore', () => { expect(flag).toBeUndefined(); }); - test('invalid namespace key', async () => { - const flag = await asyncFeatureStore.get({ namespace: 'invalid' }, 'testFlag1'); + test('get segment', async () => { + const segment = await asyncFeatureStore.get({ namespace: 'segments' }, 'testSegment1'); + + expect(mockGet).toHaveBeenCalledWith(configKey); + expect(segment).toMatchObject(testData.segments.testSegment1); + }); + + test('invalid segment key', async () => { + const segment = await asyncFeatureStore.get({ namespace: 'segments' }, 'invalid'); - expect(flag).toBe(null); + expect(segment).toBeUndefined(); }); - test('invalid edge config key', async () => { + test('invalid kv key', async () => { mockGet.mockImplementation(() => Promise.resolve(null)); const flag = await asyncFeatureStore.get({ namespace: 'features' }, 'testFlag1'); @@ -60,7 +77,14 @@ describe('VercelFeatureStore', () => { const flag = await asyncFeatureStore.all({ namespace: 'features' }); expect(mockGet).toHaveBeenCalledWith(configKey); - expect(flag).toEqual(testData.flags); + expect(flag).toMatchObject(testDataFlagsSubset); + }); + + test('all segments', async () => { + const segment = await asyncFeatureStore.all({ namespace: 'segments' }); + + expect(mockGet).toHaveBeenCalledWith(configKey); + expect(segment).toMatchObject(testData.segments); }); test('invalid DataKind', async () => { @@ -69,11 +93,11 @@ describe('VercelFeatureStore', () => { expect(flag).toBeUndefined(); }); - test('invalid edge config key', async () => { + test('invalid kv key', async () => { mockGet.mockImplementation(() => Promise.resolve(null)); - const flag = await asyncFeatureStore.all({ namespace: 'flags11' }); + const segment = await asyncFeatureStore.all({ namespace: 'segments' }); - expect(flag).toEqual({}); + expect(segment).toEqual({}); }); }); diff --git a/packages/sdk/vercel/src/vercelFeatureStore.ts b/packages/sdk/vercel/src/vercelFeatureStore.ts index 851b8ba31a..9e47e62fe6 100644 --- a/packages/sdk/vercel/src/vercelFeatureStore.ts +++ b/packages/sdk/vercel/src/vercelFeatureStore.ts @@ -7,7 +7,7 @@ import type { LDFeatureStoreItem, LDFeatureStoreKindData, } from '@launchdarkly/js-server-sdk-common-edge'; -import { noop } from '@launchdarkly/js-server-sdk-common-edge'; +import { deserializePoll, noop } from '@launchdarkly/js-server-sdk-common-edge'; class VercelFeatureStore implements LDFeatureStore { private edgeConfig: EdgeConfigClient; @@ -27,14 +27,20 @@ class VercelFeatureStore implements LDFeatureStore { flagKey: string, callback: (res: LDFeatureStoreItem | null) => void ): Promise { - this.logger.debug(`Requesting ${flagKey} from ${this.configKey}`); + const kindKey = kind.namespace === 'features' ? 'flags' : kind.namespace; + this.logger.debug(`Requesting ${flagKey} from ${this.configKey}.${kindKey}`); + try { - const config = await this.edgeConfig.get(this.configKey); - if (config === null) { - this.logger.error('Feature data not found in Edge Config.'); + const config = await this.edgeConfig.get(this.configKey); + + if (!config) { + throw new Error(`${this.configKey} is not found in Edge Config.`); + } + + const item = deserializePoll(config); + if (!item) { + throw new Error(`Error deserializing ${this.configKey}`); } - const kindKey = kind.namespace === 'features' ? 'flags' : kind.namespace; - const item = config as LDFeatureStoreItem; callback(item[kindKey][flagKey]); } catch (err) { this.logger.error(err); @@ -44,13 +50,18 @@ class VercelFeatureStore implements LDFeatureStore { async all(kind: DataKind, callback: (res: LDFeatureStoreKindData) => void = noop): Promise { const kindKey = kind.namespace === 'features' ? 'flags' : kind.namespace; - this.logger.debug(`Requesting all ${kindKey} data from Edge Config.`); + this.logger.debug(`Requesting all from ${this.configKey}.${kindKey}`); try { - const config = await this.edgeConfig.get(this.configKey); - if (config === null) { - this.logger.error('Feature data not found in Edge Config.'); + const config = await this.edgeConfig.get(this.configKey); + if (!config) { + throw new Error(`${this.configKey} is not found in Edge Config.`); + } + + const item = deserializePoll(config); + if (!item) { + throw new Error(`Error deserializing ${this.configKey}`); } - const item = config as LDFeatureStoreItem; + callback(item[kindKey]); } catch (err) { this.logger.error(err); @@ -59,7 +70,7 @@ class VercelFeatureStore implements LDFeatureStore { } async initialized(callback: (isInitialized: boolean) => void = noop): Promise { - const config = await this.edgeConfig.get(this.configKey); + const config = await this.edgeConfig.get(this.configKey); const result = config !== null; this.logger.debug(`Is ${this.configKey} initialized? ${result}`); callback(result); From 1d662e2f6ad28ea9755dc7a37c4c4e49f06dd8f4 Mon Sep 17 00:00:00 2001 From: Fabian Feldberg Date: Fri, 28 Apr 2023 09:33:45 +0100 Subject: [PATCH 05/13] use packagageJson as default import/export --- packages/sdk/vercel/src/createPlatformInfo.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/sdk/vercel/src/createPlatformInfo.ts b/packages/sdk/vercel/src/createPlatformInfo.ts index 4bcb2880ef..71d3f51a3a 100644 --- a/packages/sdk/vercel/src/createPlatformInfo.ts +++ b/packages/sdk/vercel/src/createPlatformInfo.ts @@ -1,6 +1,6 @@ import type { Info, PlatformData, SdkData } from '@launchdarkly/js-server-sdk-common-edge'; -import { name, version } from '../package.json'; +import packageJson from '../package.json'; class VercelPlatformInfo implements Info { platformData(): PlatformData { @@ -11,8 +11,8 @@ class VercelPlatformInfo implements Info { sdkData(): SdkData { return { - name, - version, + name: packageJson.name, + version: packageJson.version, }; } } From 2e70e83c90b291217a7814541995d9661e4eec9b Mon Sep 17 00:00:00 2001 From: Fabian Feldberg Date: Fri, 28 Apr 2023 09:46:41 +0100 Subject: [PATCH 06/13] stringify the edge config output so we can use deserializepoll --- packages/sdk/vercel/src/vercelFeatureStore.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/sdk/vercel/src/vercelFeatureStore.ts b/packages/sdk/vercel/src/vercelFeatureStore.ts index 9e47e62fe6..44acc01309 100644 --- a/packages/sdk/vercel/src/vercelFeatureStore.ts +++ b/packages/sdk/vercel/src/vercelFeatureStore.ts @@ -37,7 +37,8 @@ class VercelFeatureStore implements LDFeatureStore { throw new Error(`${this.configKey} is not found in Edge Config.`); } - const item = deserializePoll(config); + // We are serializing/deserialising here as deserializePoll replaces null with undefined (and we do get null values out of the Edge Config which can cause issues) + const item = deserializePoll(JSON.stringify(config)); if (!item) { throw new Error(`Error deserializing ${this.configKey}`); } @@ -57,7 +58,8 @@ class VercelFeatureStore implements LDFeatureStore { throw new Error(`${this.configKey} is not found in Edge Config.`); } - const item = deserializePoll(config); + // We are serializing/deserialising here as deserializePoll replaces null with undefined (and we do get null values out of the Edge Config which can cause issues) + const item = deserializePoll(JSON.stringify(config)); if (!item) { throw new Error(`Error deserializing ${this.configKey}`); } From 017fe65223ec909d3e591fdc80ab60853feee00f Mon Sep 17 00:00:00 2001 From: Fabian Feldberg Date: Fri, 28 Apr 2023 09:53:52 +0100 Subject: [PATCH 07/13] fix functionality tests --- packages/sdk/vercel/src/createPlatformInfo.test.ts | 2 +- packages/sdk/vercel/src/index.test.ts | 3 ++- packages/sdk/vercel/src/vercelFeatureStore.test.ts | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/sdk/vercel/src/createPlatformInfo.test.ts b/packages/sdk/vercel/src/createPlatformInfo.test.ts index 815ac4ecbf..abebf04c32 100644 --- a/packages/sdk/vercel/src/createPlatformInfo.test.ts +++ b/packages/sdk/vercel/src/createPlatformInfo.test.ts @@ -1,6 +1,6 @@ import createPlatformInfo from './createPlatformInfo'; -const packageJson = require('../package.json'); +import packageJson from '../package.json'; describe('Vercel Platform Info', () => { it('platformData shows correct information', () => { diff --git a/packages/sdk/vercel/src/index.test.ts b/packages/sdk/vercel/src/index.test.ts index 90d45ed4b8..2babf2ff41 100644 --- a/packages/sdk/vercel/src/index.test.ts +++ b/packages/sdk/vercel/src/index.test.ts @@ -14,7 +14,8 @@ describe('init', () => { const mockGet = mockEdge.get as jest.Mock; beforeAll(async () => { - mockGet.mockImplementation(() => Promise.resolve(JSON.stringify(testData))); + // Vercel Edge can't return simple strings + mockGet.mockImplementation(() => Promise.resolve(testData)); ldClient = init(sdkKey, mockEdge); await ldClient.waitForInitialization(); }); diff --git a/packages/sdk/vercel/src/vercelFeatureStore.test.ts b/packages/sdk/vercel/src/vercelFeatureStore.test.ts index 57b6b8d943..425b5f8fcb 100644 --- a/packages/sdk/vercel/src/vercelFeatureStore.test.ts +++ b/packages/sdk/vercel/src/vercelFeatureStore.test.ts @@ -28,7 +28,8 @@ describe('VercelFeatureStore', () => { let asyncFeatureStore: AsyncStoreFacade; beforeEach(() => { - mockGet.mockImplementation(() => Promise.resolve(JSON.stringify(testData))); + // Vercel Edge can't return simple strings + mockGet.mockImplementation(() => Promise.resolve(testData)); featureStore = new VercelFeatureStore(mockEdge, sdkKey, mockLogger); asyncFeatureStore = new AsyncStoreFacade(featureStore); }); From 1e768c4ad85116ad1f07867635d841bc48d30031 Mon Sep 17 00:00:00 2001 From: Fabian Feldberg Date: Fri, 28 Apr 2023 10:00:23 +0100 Subject: [PATCH 08/13] remove type assertions from edge config calls --- packages/sdk/vercel/src/vercelFeatureStore.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/sdk/vercel/src/vercelFeatureStore.ts b/packages/sdk/vercel/src/vercelFeatureStore.ts index 44acc01309..3c15434df4 100644 --- a/packages/sdk/vercel/src/vercelFeatureStore.ts +++ b/packages/sdk/vercel/src/vercelFeatureStore.ts @@ -31,7 +31,7 @@ class VercelFeatureStore implements LDFeatureStore { this.logger.debug(`Requesting ${flagKey} from ${this.configKey}.${kindKey}`); try { - const config = await this.edgeConfig.get(this.configKey); + const config = await this.edgeConfig.get(this.configKey); if (!config) { throw new Error(`${this.configKey} is not found in Edge Config.`); @@ -53,7 +53,7 @@ class VercelFeatureStore implements LDFeatureStore { const kindKey = kind.namespace === 'features' ? 'flags' : kind.namespace; this.logger.debug(`Requesting all from ${this.configKey}.${kindKey}`); try { - const config = await this.edgeConfig.get(this.configKey); + const config = await this.edgeConfig.get(this.configKey); if (!config) { throw new Error(`${this.configKey} is not found in Edge Config.`); } @@ -72,7 +72,7 @@ class VercelFeatureStore implements LDFeatureStore { } async initialized(callback: (isInitialized: boolean) => void = noop): Promise { - const config = await this.edgeConfig.get(this.configKey); + const config = await this.edgeConfig.get(this.configKey); const result = config !== null; this.logger.debug(`Is ${this.configKey} initialized? ${result}`); callback(result); From 71c9999dbe78922fe44d5011bf9d46069d11614d Mon Sep 17 00:00:00 2001 From: Fabian Feldberg Date: Fri, 28 Apr 2023 10:29:56 +0100 Subject: [PATCH 09/13] fix packagejson import/tsconfig.json --- packages/sdk/vercel/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/sdk/vercel/tsconfig.json b/packages/sdk/vercel/tsconfig.json index d52294d88d..2448b1c9ac 100644 --- a/packages/sdk/vercel/tsconfig.json +++ b/packages/sdk/vercel/tsconfig.json @@ -16,7 +16,8 @@ "stripInternal": true, "moduleResolution": "node", "types": ["jest", "node"], - "skipLibCheck": true + "skipLibCheck": true, + "esModuleInterop": true, }, "exclude": ["**/*.test.ts", "dist", "node_modules", "__tests__", "example"] } From 8f4a78efe983e9feb53c50a9a7a36a3b3eb98f82 Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 28 Apr 2023 10:34:43 +0100 Subject: [PATCH 10/13] Comment feedback Co-authored-by: Yusinto Ngadiman --- packages/sdk/vercel/src/vercelFeatureStore.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/sdk/vercel/src/vercelFeatureStore.ts b/packages/sdk/vercel/src/vercelFeatureStore.ts index 3c15434df4..e167eacad6 100644 --- a/packages/sdk/vercel/src/vercelFeatureStore.ts +++ b/packages/sdk/vercel/src/vercelFeatureStore.ts @@ -37,7 +37,9 @@ class VercelFeatureStore implements LDFeatureStore { throw new Error(`${this.configKey} is not found in Edge Config.`); } - // We are serializing/deserialising here as deserializePoll replaces null with undefined (and we do get null values out of the Edge Config which can cause issues) + // We are serializing/deserialising here as deserializePoll replaces null with undefined (and we do get null values out of the Edge Config which can cause issues). + // deserializePoll also converts rules, rollouts, buckets and other nested structures into objects which the sdk understands. + // We have to JSON.stringify the response from edge because they only support returning a json response at this time. const item = deserializePoll(JSON.stringify(config)); if (!item) { throw new Error(`Error deserializing ${this.configKey}`); From 7c0122aeca6d65855761b0a491f1bee9fc3168ca Mon Sep 17 00:00:00 2001 From: Yusinto Ngadiman Date: Fri, 28 Apr 2023 03:09:14 -0700 Subject: [PATCH 11/13] Update vercelFeatureStore.ts --- packages/sdk/vercel/src/vercelFeatureStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/vercel/src/vercelFeatureStore.ts b/packages/sdk/vercel/src/vercelFeatureStore.ts index e167eacad6..842571a6d7 100644 --- a/packages/sdk/vercel/src/vercelFeatureStore.ts +++ b/packages/sdk/vercel/src/vercelFeatureStore.ts @@ -38,7 +38,7 @@ class VercelFeatureStore implements LDFeatureStore { } // We are serializing/deserialising here as deserializePoll replaces null with undefined (and we do get null values out of the Edge Config which can cause issues). - // deserializePoll also converts rules, rollouts, buckets and other nested structures into objects which the sdk understands. + // deserializePoll also converts rules, rollouts, buckets and other nested structures into objects which the sdk understands. // We have to JSON.stringify the response from edge because they only support returning a json response at this time. const item = deserializePoll(JSON.stringify(config)); if (!item) { From a2d19bfda401ea1b0f87376ae18445b638f12483 Mon Sep 17 00:00:00 2001 From: Yusinto Ngadiman Date: Fri, 28 Apr 2023 03:14:35 -0700 Subject: [PATCH 12/13] chore: add cloudflare platformInfo tests --- .../cloudflare/src/createPlatformInfo.test.ts | 22 +++++++++++++++++++ .../sdk/cloudflare/src/createPlatformInfo.ts | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 packages/sdk/cloudflare/src/createPlatformInfo.test.ts diff --git a/packages/sdk/cloudflare/src/createPlatformInfo.test.ts b/packages/sdk/cloudflare/src/createPlatformInfo.test.ts new file mode 100644 index 0000000000..a33546cd43 --- /dev/null +++ b/packages/sdk/cloudflare/src/createPlatformInfo.test.ts @@ -0,0 +1,22 @@ +import createPlatformInfo from './createPlatformInfo'; + +import { name, version } from '../package.json'; + +describe('Cloudflare Platform Info', () => { + it('platformData shows correct information', () => { + const platformData = createPlatformInfo(); + + expect(platformData.platformData()).toEqual({ + name: 'Cloudflare', + }); + }); + + it('sdkData shows correct information', () => { + const platformData = createPlatformInfo(); + + expect(platformData.sdkData()).toEqual({ + name, + version, + }); + }); +}); diff --git a/packages/sdk/cloudflare/src/createPlatformInfo.ts b/packages/sdk/cloudflare/src/createPlatformInfo.ts index 37ce6b5ca5..23ffef15e1 100644 --- a/packages/sdk/cloudflare/src/createPlatformInfo.ts +++ b/packages/sdk/cloudflare/src/createPlatformInfo.ts @@ -5,7 +5,7 @@ import { name, version } from '../package.json'; class CloudflarePlatformInfo implements Info { platformData(): PlatformData { return { - name: 'Cloudflare worker', + name: 'Cloudflare', }; } From 8b84131f33e97069e33c94c6d59f6d86888a0b8d Mon Sep 17 00:00:00 2001 From: Yusinto Ngadiman Date: Fri, 28 Apr 2023 03:20:33 -0700 Subject: [PATCH 13/13] fix: remove esModuleInterop and fix unit tests. move vercel package to dep. minor package.json changes. --- packages/sdk/vercel/package.json | 7 +++++-- packages/sdk/vercel/src/createPlatformInfo.test.ts | 6 +++--- packages/sdk/vercel/src/createPlatformInfo.ts | 6 +++--- packages/sdk/vercel/tsconfig.json | 3 +-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/sdk/vercel/package.json b/packages/sdk/vercel/package.json index 70472fb9d7..71bfb2bd70 100644 --- a/packages/sdk/vercel/package.json +++ b/packages/sdk/vercel/package.json @@ -20,21 +20,24 @@ "import": "./dist/esm/src/index.js" }, "main": "./dist/cjs/src/index.js", + "types": "./dist/cjs/src/index.d.ts", "files": [ "dist" ], "scripts": { "doc": "../../../scripts/build-doc.sh .", "build": "../../../scripts/build-package.sh", + "clean": "rimraf dist", "tsw": "yarn tsc --watch", "start": "rimraf dist && yarn tsw", "lint": "eslint . --ext .ts", "prettier": "prettier --write '**/*.@(js|ts|tsx|json|css)'", - "test": "jest --ci --runInBand", + "test": "NODE_OPTIONS=\"--experimental-vm-modules --no-warnings\" jest --ci --runInBand", "check": "yarn prettier && yarn lint && yarn build && yarn test && yarn doc" }, "dependencies": { "@launchdarkly/js-server-sdk-common-edge": "1.0.1", + "@vercel/edge-config": "^0.1.8", "crypto-js": "^4.1.1" }, "devDependencies": { @@ -42,7 +45,6 @@ "@types/jest": "^29.5.0", "@typescript-eslint/eslint-plugin": "^5.57.0", "@typescript-eslint/parser": "^5.57.0", - "@vercel/edge-config": "^0.1.7", "eslint": "^8.37.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^17.0.0", @@ -52,6 +54,7 @@ "jest": "^29.5.0", "launchdarkly-js-test-helpers": "^2.2.0", "prettier": "^2.8.7", + "rimraf": "^5.0.0", "ts-jest": "^29.1.0", "typedoc": "0.23.26", "typescript": "^5.0.3" diff --git a/packages/sdk/vercel/src/createPlatformInfo.test.ts b/packages/sdk/vercel/src/createPlatformInfo.test.ts index abebf04c32..e0b6d67f5d 100644 --- a/packages/sdk/vercel/src/createPlatformInfo.test.ts +++ b/packages/sdk/vercel/src/createPlatformInfo.test.ts @@ -1,6 +1,6 @@ import createPlatformInfo from './createPlatformInfo'; -import packageJson from '../package.json'; +import { name, version } from '../package.json'; describe('Vercel Platform Info', () => { it('platformData shows correct information', () => { @@ -15,8 +15,8 @@ describe('Vercel Platform Info', () => { const platformData = createPlatformInfo(); expect(platformData.sdkData()).toEqual({ - name: packageJson.name, - version: packageJson.version, + name, + version, }); }); }); diff --git a/packages/sdk/vercel/src/createPlatformInfo.ts b/packages/sdk/vercel/src/createPlatformInfo.ts index 71d3f51a3a..4bcb2880ef 100644 --- a/packages/sdk/vercel/src/createPlatformInfo.ts +++ b/packages/sdk/vercel/src/createPlatformInfo.ts @@ -1,6 +1,6 @@ import type { Info, PlatformData, SdkData } from '@launchdarkly/js-server-sdk-common-edge'; -import packageJson from '../package.json'; +import { name, version } from '../package.json'; class VercelPlatformInfo implements Info { platformData(): PlatformData { @@ -11,8 +11,8 @@ class VercelPlatformInfo implements Info { sdkData(): SdkData { return { - name: packageJson.name, - version: packageJson.version, + name, + version, }; } } diff --git a/packages/sdk/vercel/tsconfig.json b/packages/sdk/vercel/tsconfig.json index 2448b1c9ac..d52294d88d 100644 --- a/packages/sdk/vercel/tsconfig.json +++ b/packages/sdk/vercel/tsconfig.json @@ -16,8 +16,7 @@ "stripInternal": true, "moduleResolution": "node", "types": ["jest", "node"], - "skipLibCheck": true, - "esModuleInterop": true, + "skipLibCheck": true }, "exclude": ["**/*.test.ts", "dist", "node_modules", "__tests__", "example"] }