Skip to content
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 @@ -104,27 +104,27 @@ const changesTransferNone = {

describe('createPayloadListenerFDv2', () => {
let dataSourceUpdates: LDTransactionalDataSourceUpdates;
let basisReceived: jest.Mock;
let initializedCallback: jest.Mock;

beforeEach(() => {
dataSourceUpdates = {
init: jest.fn(),
upsert: jest.fn(),
applyChanges: jest.fn(),
};
basisReceived = jest.fn();
initializedCallback = jest.fn();
});

afterEach(() => {
jest.resetAllMocks();
});

test('data source updates called with basis true', async () => {
const listener = createPayloadListener(dataSourceUpdates, logger, basisReceived);
const listener = createPayloadListener(dataSourceUpdates, logger, initializedCallback);
listener(fullTransferPayload);

expect(logger.debug).toBeCalledWith(expect.stringMatching(/initializing/i));
expect(dataSourceUpdates.applyChanges).toBeCalledWith(
expect(logger.debug).toHaveBeenCalledWith(expect.stringMatching(/initializing/i));
expect(dataSourceUpdates.applyChanges).toHaveBeenCalledWith(
true,
{
features: {
Expand All @@ -134,17 +134,17 @@ describe('createPayloadListenerFDv2', () => {
segkey: { key: 'segkey', version: 2 },
},
},
basisReceived,
expect.any(Function),
{ environmentId: 'envId' },
'initial',
);
});

test('data source updates called with basis false', async () => {
const listener = createPayloadListener(dataSourceUpdates, logger, basisReceived);
const listener = createPayloadListener(dataSourceUpdates, logger, initializedCallback);
listener(changesTransferPayload);

expect(logger.debug).toBeCalledWith(expect.stringMatching(/updating/i));
expect(logger.debug).toHaveBeenCalledWith(expect.stringMatching(/updating/i));
expect(dataSourceUpdates.applyChanges).toHaveBeenCalledTimes(1);
expect(dataSourceUpdates.applyChanges).toHaveBeenNthCalledWith(
1,
Expand All @@ -168,17 +168,79 @@ describe('createPayloadListenerFDv2', () => {
},
},
},
basisReceived,
expect.any(Function),
{ environmentId: 'envId' },
'changes',
);
});

test('data source updates not called when basis is false and changes are empty', async () => {
const listener = createPayloadListener(dataSourceUpdates, logger, basisReceived);
const listener = createPayloadListener(dataSourceUpdates, logger, initializedCallback);
listener(changesTransferNone);

expect(logger.debug).toBeCalledWith(expect.stringMatching(/ignoring/i));
expect(dataSourceUpdates.applyChanges).toHaveBeenCalledTimes(0);
});

test('calls initializedCallback when state is non-empty (initial)', () => {
const listener = createPayloadListener(dataSourceUpdates, logger, initializedCallback);
let capturedCallback: (() => void) | undefined;

dataSourceUpdates.applyChanges = jest.fn((_basis, _data, callback) => {
capturedCallback = callback;
});

listener(fullTransferPayload);

expect(capturedCallback).toBeDefined();
expect(initializedCallback).not.toHaveBeenCalled();

// Simulate applyChanges calling the callback
capturedCallback?.();

expect(initializedCallback).toHaveBeenCalledTimes(1);
});

test('does not call initializedCallback when state is empty (file data initializer)', () => {
const fileDataPayload = {
initMetadata: {
environmentId: 'envId',
},
payload: {
id: 'payloadID',
version: 99,
state: '',
basis: true,
updates: [
{
kind: 'flag',
key: 'flagkey',
version: 1,
object: {
key: 'flagkey',
version: 1,
},
},
],
},
};

const listener = createPayloadListener(dataSourceUpdates, logger, initializedCallback);
let capturedCallback: (() => void) | undefined;

dataSourceUpdates.applyChanges = jest.fn((_basis, _data, callback) => {
capturedCallback = callback;
});

listener(fileDataPayload);

expect(capturedCallback).toBeDefined();
expect(initializedCallback).not.toHaveBeenCalled();

// Simulate applyChanges calling the callback
capturedCallback?.();

// Should still not be called because state is empty
expect(initializedCallback).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,30 @@ export type SynchronizerDataSource =
| StreamingDataSourceConfiguration;

/**
* This data source will allow developers to define their own composite data source
* @experimental
* This data source will allow developers to define their own composite data source.
* This is a free-form option and is not subject to any backwards compatibility guarantees or semantic
* versioning.
*
* The following example is roughly equivilent to using the {@link StandardDataSourceOptions} with the default values.
* @example
* ```typescript
* dataSource: {
* dataSourceOptionsType: 'custom',
* initializers: [
* {
* type: 'polling'
* },
* ],
* synchronizers: [
* {
* type: 'streaming',
* },
* {
* type: 'polling',
* }
* ],
* }
*/
export interface CustomDataSourceOptions {
dataSourceOptionsType: 'custom';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const createPayloadListener =
(
dataSourceUpdates: LDTransactionalDataSourceUpdates,
logger?: LDLogger,
basisReceived: VoidFunction = () => {},
initializedCallback: VoidFunction = () => {},
) =>
(dataContainer: DataCallbackContainer) => {
const { initMetadata, payload } = dataContainer;
Expand Down Expand Up @@ -68,7 +68,14 @@ export const createPayloadListener =
dataSourceUpdates.applyChanges(
payload.basis,
converted,
basisReceived,
() => {
if (payload.state !== '') {
// NOTE: The only condition that we will consider a valid basis
// is when there is a valid selector. Currently, the only data source that does not have a
// valid selector is the file data initializer, which will have a blank selector.
initializedCallback();
}
},
initMetadata,
payload.state,
);
Expand Down