Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
4b51907
Lint
kinyoklion Jul 29, 2022
f208ff4
Write unit tests for file data source
kinyoklion Aug 1, 2022
ca829ca
Remove docs folder.
kinyoklion Aug 1, 2022
67fdde3
Merge main.
kinyoklion Aug 1, 2022
f0a5080
Merge branch 'main' into rlamb/sc-154357/file-data-source
kinyoklion Aug 1, 2022
8daa0bb
Start adding top level client tests.
kinyoklion Aug 3, 2022
7835b32
Progress on tests.
kinyoklion Aug 4, 2022
1b96fad
Start implementation of LDClientContext.
kinyoklion Aug 8, 2022
171ebd4
Merge branch 'rlamb/sc-154357/file-data-source' into rlamb/sc-162616/…
kinyoklion Aug 8, 2022
18590c4
Change file data source factory to be LDClientContext based.
kinyoklion Aug 8, 2022
bbc4b9f
Fix open handles in tests.
kinyoklion Aug 8, 2022
5986d0b
Update organization to allow for LDDataSourceUpdates to be used with …
kinyoklion Aug 15, 2022
d8b9565
First pass DataSourceUpdates.
kinyoklion Aug 15, 2022
5bfc3da
Add DataSourceUpdates tests.
kinyoklion Aug 15, 2022
b4b69ce
Add client level individual eval tests.
kinyoklion Aug 15, 2022
01ca674
Progress on events.
kinyoklion Aug 16, 2022
32e6eba
Remove upsert clone because we need to retain prototypes.
kinyoklion Aug 16, 2022
8c5ee0b
Add more events tests.
kinyoklion Aug 16, 2022
516df56
Start big segments tests.
kinyoklion Aug 16, 2022
d427d1c
Finish big segments test. Linting.
kinyoklion Aug 16, 2022
2164dc3
Ensure we close the big segments manager. Updated tests to prevent op…
kinyoklion Aug 16, 2022
57c6a9f
Remove commented jest spy.
kinyoklion Aug 16, 2022
c08491b
Linting cleanup.
kinyoklion Aug 16, 2022
76ec9a3
File data source tests that use the node filesystem implementation.
kinyoklion Aug 16, 2022
de3313b
Add test for node level big segments. Most things are covered by lowe…
kinyoklion Aug 17, 2022
4d23a6d
Fix TLS parsing.
kinyoklion Aug 17, 2022
7c38752
Add TLS tests.
kinyoklion Aug 17, 2022
f929aee
Lint
kinyoklion Aug 17, 2022
8618392
Move tests to match directory structure. Add tests for the header wra…
kinyoklion Aug 17, 2022
3f09fbc
Merge main.
kinyoklion Aug 17, 2022
ea0d8a2
Fixes issues from end to end testing.
kinyoklion Aug 17, 2022
8f25e12
merge main
kinyoklion Aug 17, 2022
454311b
Merge fixes branch.
kinyoklion Aug 17, 2022
eeb064e
Remove the logging destination from node level tests.
kinyoklion Aug 17, 2022
94a25e2
Merge main,
kinyoklion Aug 17, 2022
22661fe
Diagnostic events error handling.
kinyoklion Aug 17, 2022
39a0eea
Merge branch 'rlamb/diagnostic-event-error-handling' into rlamb/fixes…
kinyoklion Aug 17, 2022
f199f0a
Merge branch 'rlamb/fixes-from-testing' into rlamb/sc-162616/implemen…
kinyoklion Aug 17, 2022
53707d4
Add missing describe name.
kinyoklion Aug 17, 2022
745ea4b
Linting
kinyoklion Aug 17, 2022
c005500
Change callbacks to be an object for LDClientImpl.
kinyoklion Aug 23, 2022
9a2b10e
Merge
kinyoklion Aug 23, 2022
86ad7d3
Linting
kinyoklion Aug 23, 2022
2720d7a
Merge main.
kinyoklion Aug 23, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ dist/
node_modules/
**/*.tsbuildinfo
coverage/
tmp/
docs/
153 changes: 153 additions & 0 deletions platform-node/__tests__/LDClientNode.bigSegments.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import {
integrations, interfaces, LDBigSegmentsOptions, LDLogger,
} from '@launchdarkly/js-server-sdk-common';
import { basicLogger, LDClientImpl } from '../src';
import { LDClient } from '../src/api/LDClient';

describe('given test data with big segments', () => {
// To use the public interfaces to create a client which doesn't use the
// network. (Versus being offline, or a null update processor.)
let td: integrations.TestData;
let logger: LDLogger;

beforeEach(() => {
td = new integrations.TestData();
logger = basicLogger({
destination: () => {},
});
});

describe('given a healthy big segment store', () => {
let client: LDClient;
const bigSegmentsConfig: LDBigSegmentsOptions = {
statusPollInterval: 0.1,
store(): interfaces.BigSegmentStore {
return {
getMetadata: async () => ({ lastUpToDate: Date.now() }),
getUserMembership: async () => undefined,
close: () => { },
};
},
};

beforeEach(() => {
client = new LDClientImpl('sdk-key', {
updateProcessor: td.getFactory(),
sendEvents: false,
bigSegments: bigSegmentsConfig,
});
});

it('can get status', () => {
const status = client.bigSegmentStoreStatusProvider.getStatus();
expect(status).toBeUndefined();
});

it('can require status', async () => {
const status = await client.bigSegmentStoreStatusProvider.requireStatus();
expect(status.available).toEqual(true);
expect(status.stale).toEqual(false);
});

it('Can listen to the event emitter for the status', (done) => {
client.bigSegmentStoreStatusProvider.on('change', (status: interfaces.BigSegmentStoreStatus) => {
expect(status.stale).toEqual(false);
expect(status.available).toEqual(true);

const status2 = client.bigSegmentStoreStatusProvider.getStatus();
expect(status2!.stale).toEqual(false);
expect(status2!.available).toEqual(true);
done();
});
});

afterEach(() => {
client.close();
});
});

describe('given a stale store', () => {
let client: LDClient;
const bigSegmentsConfig: LDBigSegmentsOptions = {
store(): interfaces.BigSegmentStore {
return {
getMetadata: async () => ({ lastUpToDate: 1000 }),
getUserMembership: async () => undefined,
close: () => { },
};
},
};

beforeEach(async () => {
client = new LDClientImpl('sdk-key', {
updateProcessor: td.getFactory(),
sendEvents: false,
bigSegments: bigSegmentsConfig,
});

await client.waitForInitialization();
});

it('can require status', async () => {
const status = await client.bigSegmentStoreStatusProvider.requireStatus();
expect(status.available).toEqual(true);
expect(status.stale).toEqual(true);
});

afterEach(() => {
client.close();
});
});

describe('given a store that can produce an error', () => {
let client: LDClient;
let error: boolean;
const bigSegmentsConfig: LDBigSegmentsOptions = {
statusPollInterval: 0.1,
store(): interfaces.BigSegmentStore {
return {
getMetadata: async () => {
if (error) {
throw new Error('sorry');
}
return { lastUpToDate: Date.now() };
},
getUserMembership: async () => undefined,
close: () => { },
};
},
};

beforeEach(async () => {
error = false;
client = new LDClientImpl('sdk-key', {
updateProcessor: td.getFactory(),
sendEvents: false,
bigSegments: bigSegmentsConfig,
logger,
});

await client.waitForInitialization();
});

it('Can observe the status change', (done) => {
let message = 0;
client.bigSegmentStoreStatusProvider.on('change', (status: interfaces.BigSegmentStoreStatus) => {
if (message === 0) {
expect(status.stale).toEqual(false);
expect(status.available).toEqual(true);
error = true;
message += 1;
} else {
expect(status.stale).toEqual(false);
expect(status.available).toEqual(false);
done();
}
});
});

afterEach(() => {
client.close();
});
});
});
164 changes: 164 additions & 0 deletions platform-node/__tests__/LDClientNode.fileDataSource.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { integrations } from '@launchdarkly/js-server-sdk-common';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the file data source testing is at the common level, but here we make sure it works with the actual node filesystem APIs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's just the I/O implementation that's distinctive here, I feel like you might be able to make the tests a bit simpler by omitting things like the segment. I mean, the fact that there's a flag referencing a segment is a detail of the data that's in the file, which should be orthogonal to how we read the file or detected changes to the file.

import * as fs from 'node:fs';
import LDClientNode from '../src/LDClientNode';

const flag1Key = 'flag1';
const flag2Key = 'flag2';
const flag2Value = 'value2';
const segment1Key = 'seg1';

const flag1 = {
key: flag1Key,
on: true,
rules: [
{ clauses: [{ op: 'segmentMatch', values: [segment1Key] }], variation: 1 },
],
fallthrough: {
variation: 2,
},
variations: ['fall', 'off', 'on'],
};

const segment1 = {
key: segment1Key,
included: ['user1'],
};

const flagOnlyJson = `
{
"flags": {
"${flag1Key}": ${JSON.stringify(flag1)}
}
}`;

const segmentOnlyJson = `
{
"segments": {
"${segment1Key}": ${JSON.stringify(segment1)}
}
}`;

const allPropertiesJson = `
{
"flags": {
"${flag1Key}": ${JSON.stringify(flag1)}
},
"flagValues": {
"${flag2Key}": "${flag2Value}"
},
"segments": {
"${segment1Key}": ${JSON.stringify(segment1)}
}
}`;

const tmpFiles: string[] = [];

function makeTempFile(content: string): string {
const fileName = (Math.random() + 1).toString(36).substring(7);
if (!fs.existsSync('./tmp')) {
fs.mkdirSync('./tmp');
}
const fullPath = `./tmp/${fileName}`;
fs.writeFileSync(fullPath, content);
tmpFiles.push(fullPath);
return fullPath;
}

function replaceFileContent(filePath: string, content: string) {
fs.writeFileSync(filePath, content);
}

describe('When using a file data source', () => {
afterAll(() => {
tmpFiles.forEach((filePath) => {
fs.unlinkSync(filePath);
});
});

it('loads flags on start from JSON', async () => {
const path = makeTempFile(allPropertiesJson);
const fds = new integrations.FileDataSourceFactory({
paths: [path],
});

const client = new LDClientNode('sdk-key', {
updateProcessor: fds.getFactory(),
sendEvents: false,
});

await client.waitForInitialization();

const f1Var = await client.variation(flag1Key, { key: 'user1' }, 'default');
expect(f1Var).toEqual('off');
const f1VarNoSeg = await client.variation(flag1Key, { key: 'user2' }, 'default');
expect(f1VarNoSeg).toEqual('on');
const f2Var = await client.variation(flag2Key, { key: 'user1' }, 'default');
expect(f2Var).toEqual('value2');

client.close();
});

it('it can load multiple files', async () => {
const path1 = makeTempFile(flagOnlyJson);
const path2 = makeTempFile(segmentOnlyJson);
const fds = new integrations.FileDataSourceFactory({
paths: [path1, path2],
});

const client = new LDClientNode('sdk-key', {
updateProcessor: fds.getFactory(),
sendEvents: false,
});

await client.waitForInitialization();

const f1Var = await client.variation(flag1Key, { key: 'user1' }, 'default');
expect(f1Var).toEqual('off');
const f1VarNoSeg = await client.variation(flag1Key, { key: 'user2' }, 'default');
expect(f1VarNoSeg).toEqual('on');

client.close();
});

it('reloads the file if the content changes', async () => {
const path = makeTempFile(allPropertiesJson);
const fds = new integrations.FileDataSourceFactory({
paths: [path],
autoUpdate: true,
});

const client = new LDClientNode('sdk-key', {
updateProcessor: fds.getFactory(),
sendEvents: false,
});

await client.waitForInitialization();

const f1Var = await client.variation(flag1Key, { key: 'user1' }, 'default');
expect(f1Var).toEqual('off');
const f1VarNoSeg = await client.variation(flag1Key, { key: 'user2' }, 'default');
expect(f1VarNoSeg).toEqual('on');
const f2Var = await client.variation(flag2Key, { key: 'user1' }, 'default');
expect(f2Var).toEqual('value2');

replaceFileContent(path, flagOnlyJson);

await new Promise<void>((resolve) => {
client.once('update', () => {
// After the file reloads we get changes, so we know we can move onto
// evaluation.
resolve();
});
replaceFileContent(path, flagOnlyJson);
});

const f1VarB = await client.variation(flag1Key, { key: 'user1' }, 'default');
expect(f1VarB).toEqual('on'); // Segment doesn't exist anymore.
const f1VarNoSegB = await client.variation(flag1Key, { key: 'user2' }, 'default');
expect(f1VarNoSegB).toEqual('on');
const f2VarB = await client.variation(flag2Key, { key: 'user1' }, 'default');
expect(f2VarB).toEqual('default');

client.close();
});
});
Loading