Skip to content

chore: adds LDTransactionalFeatureStore, LDTransactionalDataSourceUpdates, and FDv2 DataSource impls. #833

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { AsyncQueue } from 'launchdarkly-js-test-helpers';

import { internal } from '@launchdarkly/js-sdk-common';

import { LDFeatureStore } from '../../src/api/subsystems';
import { LDTransactionalFeatureStore } from '../../src/api/subsystems';
import promisify from '../../src/async/promisify';
import DataSourceUpdates from '../../src/data_sources/DataSourceUpdates';
import DataSourceUpdates from '../../src/data_sources/TransactionalDataSourceUpdates';
import InMemoryFeatureStore from '../../src/store/InMemoryFeatureStore';
import VersionedDataKinds from '../../src/store/VersionedDataKinds';

Expand All @@ -13,21 +13,28 @@ type InitMetadata = internal.InitMetadata;
it('passes initialization metadata to underlying feature store', () => {
const metadata: InitMetadata = { environmentId: '12345' };
const store = new InMemoryFeatureStore();
store.init = jest.fn();
store.applyChanges = jest.fn();
const updates = new DataSourceUpdates(
store,
() => false,
() => {},
);
updates.init({}, () => {}, metadata);
expect(store.init).toHaveBeenCalledTimes(1);
expect(store.init).toHaveBeenNthCalledWith(1, expect.any(Object), expect.any(Function), metadata);
expect(store.applyChanges).toHaveBeenCalledTimes(1);
expect(store.applyChanges).toHaveBeenNthCalledWith(
1,
true,
expect.any(Object),
expect.any(Function),
metadata,
undefined,
);
});

describe.each([true, false])(
'given a DataSourceUpdates with in memory store and change listeners: %s',
(listen) => {
let store: LDFeatureStore;
let store: LDTransactionalFeatureStore;
let updates: DataSourceUpdates;

const queue = new AsyncQueue<string>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { subsystem } from '../../src';
import OneShotInitializerFDv2 from '../../src/data_sources/OneShotInitializerFDv2';
import Requestor from '../../src/data_sources/Requestor';
import TestLogger from '../Logger';

describe('given a one shot initializer', () => {
const requestor = {
requestAllData: jest.fn(),
};
const allEvents = {
events: [
{
event: 'server-intent',
data: { payloads: [{ code: 'xfer-full', id: 'mockId' }] },
},
{
event: 'put-object',
data: {
kind: 'flag',
key: 'flagA',
version: 123,
object: { objectFieldA: 'objectValueA' },
},
},
{
event: 'payload-transferred',
data: { state: 'mockState', version: 1 },
},
],
};
const jsonData = JSON.stringify(allEvents);

let initializer: OneShotInitializerFDv2;
const mockDataCallback = jest.fn();
const mockStatusCallback = jest.fn();
let testLogger: TestLogger;

beforeEach(() => {
testLogger = new TestLogger();
initializer = new OneShotInitializerFDv2(requestor as unknown as Requestor, testLogger);
});

afterEach(() => {
initializer.stop();
jest.restoreAllMocks();
});

it('makes no requests before being started', () => {
expect(requestor.requestAllData).not.toHaveBeenCalled();
});

it('polls immediately on start', () => {
initializer.start(mockDataCallback, mockStatusCallback);
expect(requestor.requestAllData).toHaveBeenCalledTimes(1);
expect(mockDataCallback).not.toHaveBeenCalled();
expect(mockStatusCallback).toHaveBeenNthCalledWith(1, subsystem.DataSourceState.Initializing);
});

it('calls callback on success', () => {
requestor.requestAllData = jest.fn((cb) => cb(undefined, jsonData));
initializer.start(mockDataCallback, mockStatusCallback);
expect(mockDataCallback).toHaveBeenNthCalledWith(1, true, {
basis: true,
id: `mockId`,
state: `mockState`,
updates: [
{
kind: `flag`,
key: `flagA`,
version: 123,
object: { objectFieldA: 'objectValueA' },
},
],
version: 1,
});
});
});
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { ClientContext } from '@launchdarkly/js-sdk-common';

import { LDFeatureStore } from '../../src';
import PollingProcessor from '../../src/data_sources/PollingProcessor';
import Requestor from '../../src/data_sources/Requestor';
import Configuration from '../../src/options/Configuration';
import AsyncStoreFacade from '../../src/store/AsyncStoreFacade';
import InMemoryFeatureStore from '../../src/store/InMemoryFeatureStore';
import VersionedDataKinds from '../../src/store/VersionedDataKinds';
import { createBasicPlatform } from '../createBasicPlatform';
import TestLogger, { LogLevel } from '../Logger';

describe('given an event processor', () => {
Expand All @@ -23,24 +19,19 @@ describe('given an event processor', () => {

let store: LDFeatureStore;
let storeFacade: AsyncStoreFacade;
let config: Configuration;
let processor: PollingProcessor;
let initSuccessHandler: jest.Mock;

beforeEach(() => {
store = new InMemoryFeatureStore();
storeFacade = new AsyncStoreFacade(store);
config = new Configuration({
featureStore: store,
pollInterval: longInterval,
logger: new TestLogger(),
});
initSuccessHandler = jest.fn();

processor = new PollingProcessor(
config,
requestor as unknown as Requestor,
config.featureStoreFactory(new ClientContext('', config, createBasicPlatform())),
longInterval,
store,
new TestLogger(),
initSuccessHandler,
);
});
Expand Down Expand Up @@ -99,27 +90,22 @@ describe('given a polling processor with a short poll duration', () => {
const jsonData = JSON.stringify(allData);

let store: LDFeatureStore;
let config: Configuration;
let testLogger: TestLogger;
let processor: PollingProcessor;
let initSuccessHandler: jest.Mock;
let errorHandler: jest.Mock;

beforeEach(() => {
store = new InMemoryFeatureStore();
config = new Configuration({
featureStore: store,
pollInterval: shortInterval,
logger: new TestLogger(),
});
testLogger = new TestLogger();
initSuccessHandler = jest.fn();
errorHandler = jest.fn();

// Configuration will not let us set this as low as needed for the test.
Object.defineProperty(config, 'pollInterval', { value: 0.1 });
processor = new PollingProcessor(
config,
requestor as unknown as Requestor,
config.featureStoreFactory(new ClientContext('', config, createBasicPlatform())),
shortInterval,
store,
testLogger,
initSuccessHandler,
errorHandler,
);
Expand Down Expand Up @@ -158,7 +144,6 @@ describe('given a polling processor with a short poll duration', () => {
expect(errorHandler).not.toBeCalled();
setTimeout(() => {
expect(requestor.requestAllData.mock.calls.length).toBeGreaterThanOrEqual(2);
const testLogger = config.logger as TestLogger;
expect(testLogger.getCount(LogLevel.Error)).toBe(0);
expect(testLogger.getCount(LogLevel.Warn)).toBeGreaterThan(2);
(done as jest.DoneCallback)();
Expand All @@ -176,7 +161,6 @@ describe('given a polling processor with a short poll duration', () => {

setTimeout(() => {
expect(requestor.requestAllData.mock.calls.length).toBeGreaterThanOrEqual(2);
const testLogger = config.logger as TestLogger;
expect(testLogger.getCount(LogLevel.Error)).toBeGreaterThan(2);
(done as jest.DoneCallback)();
}, 300);
Expand All @@ -199,7 +183,6 @@ describe('given a polling processor with a short poll duration', () => {

setTimeout(() => {
expect(requestor.requestAllData.mock.calls.length).toBe(1);
const testLogger = config.logger as TestLogger;
expect(testLogger.getCount(LogLevel.Error)).toBe(1);
(done as jest.DoneCallback)();
}, 300);
Expand Down
Loading