Skip to content
Draft
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 @@ -86,6 +86,7 @@ describe('given a BrowserDataManager with mocked dependencies', () => {
sendEvents: false,
sendLDHeaders: true,
useReport: false,
usePost: false,
withReasons: true,
privateAttributes: [],
tags: new ApplicationTags({}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ describe('given an ElectronDataManager with mocked dependencies', () => {
sendEvents: false,
sendLDHeaders: true,
useReport: false,
usePost: false,
withReasons: true,
privateAttributes: [],
tags: new ApplicationTags({}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ describe('given a NodeDataManager with mocked dependencies', () => {
sendEvents: false,
sendLDHeaders: true,
useReport: false,
usePost: false,
withReasons: true,
privateAttributes: [],
tags: new ApplicationTags({}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ describe('given a MobileDataManager with mocked dependencies', () => {
sendEvents: false,
sendLDHeaders: true,
useReport: false,
usePost: false,
withReasons: true,
privateAttributes: [],
tags: new ApplicationTags({}),
Expand Down
131 changes: 131 additions & 0 deletions packages/shared/sdk-client/__tests__/LDClientImpl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -468,3 +468,134 @@ describe('sdk-client object', () => {
expect(mockEventSource.closed).toBe(true);
});
});

describe('usePost validation', () => {
it('warns and continues when dataSystem is configured with usePost but the EventSource lacks customMethod', async () => {
const platform = createBasicPlatform();
platform.requests.getEventSourceCapabilities.mockImplementation(() => ({
readTimeout: true,
headers: true,
customMethod: false,
}));
const logger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};

// Construction succeeds. usePost still applies to polling, and only streaming
// degrades to GET.
const client = new LDClientImpl(
testSdkKey,
AutoEnvAttributes.Enabled,
platform,
{ usePost: true, dataSystem: {}, sendEvents: false, logger },
makeTestDataManagerFactory(testSdkKey, platform),
);
// The warning names the GET fallback for streaming.
expect(logger.warn).toHaveBeenCalledWith(expect.stringMatching(/usePost.*GET/s));
await client.close();
});

it('does not warn when dataSystem is configured with usePost and the EventSource supports customMethod', async () => {
const platform = createBasicPlatform();
platform.requests.getEventSourceCapabilities.mockImplementation(() => ({
readTimeout: true,
headers: true,
customMethod: true,
}));
const logger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};

const client = new LDClientImpl(
testSdkKey,
AutoEnvAttributes.Enabled,
platform,
{ usePost: true, dataSystem: {}, sendEvents: false, logger },
makeTestDataManagerFactory(testSdkKey, platform),
);

expect(logger.warn).not.toHaveBeenCalledWith(expect.stringContaining('usePost'));
await client.close();
});

it('does not warn when usePost is set without dataSystem, even if the EventSource lacks customMethod (usePost only applies to FDv2)', async () => {
const platform = createBasicPlatform();
platform.requests.getEventSourceCapabilities.mockImplementation(() => ({
readTimeout: true,
headers: true,
customMethod: false,
}));
const logger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};

const client = new LDClientImpl(
testSdkKey,
AutoEnvAttributes.Enabled,
platform,
{ usePost: true, sendEvents: false, logger },
makeTestDataManagerFactory(testSdkKey, platform),
);

expect(logger.warn).not.toHaveBeenCalledWith(expect.stringContaining('usePost'));
await client.close();
});
});

describe('useReport under dataSystem', () => {
it('ignores useReport and logs a warning when dataSystem is also configured', async () => {
const platform = createBasicPlatform();
platform.requests.getEventSourceCapabilities.mockImplementation(() => ({
readTimeout: true,
headers: true,
customMethod: false,
}));
const warnLogger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};

const client = new LDClientImpl(
testSdkKey,
AutoEnvAttributes.Enabled,
platform,
{ useReport: true, dataSystem: {}, sendEvents: false, logger: warnLogger },
makeTestDataManagerFactory(testSdkKey, platform),
);

expect(warnLogger.warn).toHaveBeenCalledWith(expect.stringContaining('useReport'));
await client.close();
});

it('does not warn when useReport is set without dataSystem', async () => {
const platform = createBasicPlatform();
const warnLogger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};

const client = new LDClientImpl(
testSdkKey,
AutoEnvAttributes.Enabled,
platform,
{ useReport: true, sendEvents: false, logger: warnLogger },
makeTestDataManagerFactory(testSdkKey, platform),
);

expect(warnLogger.warn).not.toHaveBeenCalledWith(expect.stringContaining('useReport'));
await client.close();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,29 @@ describe('dataSystem validation', () => {
expect(config.dataSystem!.automaticModeSwitching).toBe(false);
});

it('forces useReport to false and warns when dataSystem is also set', () => {
const logger = {
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
};
const config = new ConfigurationImpl(
// @ts-ignore dataSystem is @internal
{ useReport: true, dataSystem: {}, logger },
{
getImplementationHooks: () => [],
credentialType: 'clientSideId',
dataSystemDefaults: {
foregroundConnectionMode: 'one-shot',
automaticModeSwitching: false,
},
},
);
expect(config.useReport).toBe(false);
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('useReport'));
});

it('validates dataSystem with user overrides applied over platform defaults', () => {
const config = new ConfigurationImpl(
// @ts-ignore dataSystem is @internal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,30 @@ it('adds withReasons query param when config.withReasons is true', async () => {
manager.close();
});

it('passes usePost to the requestor and the source factory context', async () => {
const sourceFactoryProvider = makeSourceFactoryProvider();
const manager = createFDv2DataManagerBase(
makeBaseConfig({
config: makeConfig({ usePost: true }),
sourceFactoryProvider,
}),
);

await identifyManager(manager);

expect(mockMakeFDv2Requestor).toHaveBeenCalledTimes(1);
expect(mockMakeFDv2Requestor.mock.calls[0][7]).toBe(true);

const factoryContexts = [
...(sourceFactoryProvider.createInitializerFactory as jest.Mock).mock.calls,
...(sourceFactoryProvider.createSynchronizerSlot as jest.Mock).mock.calls,
].map((call) => call[1]);
expect(factoryContexts.length).toBeGreaterThan(0);
factoryContexts.forEach((ctx) => expect(ctx.usePost).toBe(true));

manager.close();
});

it('closes data source and debounce manager on close', async () => {
const manager = createFDv2DataManagerBase(makeBaseConfig());
await identifyManager(manager);
Expand Down Expand Up @@ -1019,6 +1043,48 @@ it('appends a blocked FDv1 fallback synchronizer when fdv1Endpoints are configur
manager.close();
});

it('uses REPORT for the FDv1 fallback requestor when usePost is set', async () => {
const sourceFactoryProvider = makeSourceFactoryProvider();
const fdv1Endpoints = {
polling: jest.fn(() => ({
pathGet: jest.fn(),
pathReport: jest.fn(),
pathPost: jest.fn(),
pathPing: jest.fn(),
})),
streaming: jest.fn(() => ({
pathGet: jest.fn(),
pathReport: jest.fn(),
pathPost: jest.fn(),
pathPing: jest.fn(),
})),
};

(makeRequestor as jest.Mock).mockReturnValue({});
(createFDv1PollingSynchronizer as jest.Mock).mockReturnValue({ close: jest.fn() });

const manager = createFDv2DataManagerBase(
makeBaseConfig({
config: makeConfig({ usePost: true }),
sourceFactoryProvider,
fdv1Endpoints,
foregroundMode: 'streaming',
}),
);
await identifyManager(manager);

const dsConfig = capturedDataSourceConfigs[0];
const fdv1Slot = dsConfig.synchronizerSlots[dsConfig.synchronizerSlots.length - 1];
// Invoke the factory to trigger requestor creation.
fdv1Slot.factory.create(() => undefined);

expect(makeRequestor).toHaveBeenCalledTimes(1);
// The useReport argument of makeRequestor controls the REPORT method.
expect((makeRequestor as jest.Mock).mock.calls[0][8]).toBe(true);

manager.close();
});

it('uses per-mode fdv1Fallback pollInterval from MODE_TABLE for background mode', async () => {
const sourceFactoryProvider = makeSourceFactoryProvider();
const fdv1Endpoints = {
Expand Down
Loading
Loading