Skip to content

Commit 824e9d6

Browse files
committed
update
1 parent 63fe0e4 commit 824e9d6

13 files changed

+301
-191
lines changed

lib/project_config/config_manager_factory.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { DatafileManagerConfig } from "./datafile_manager";
44
import { ProjectConfigManagerImpl, ProjectConfigManager } from "./project_config_manager";
55
import { PollingDatafileManager } from "./polling_datafile_manager";
66
import PersistentKeyValueCache from "../plugins/key_value_cache/persistentKeyValueCache";
7+
import { DEFAULT_UPDATE_INTERVAL, MIN_UPDATE_INTERVAL, DEFAULT_URL_TEMPLATE, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './config';
8+
import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater";
79

810
export const createStaticProjectConfigManager = (
911
datafile: string,
@@ -32,13 +34,19 @@ export type PollingConfigManagerFactoryOptions = PollingConfigManagerConfig & {
3234
export const getPollingConfigManager = (
3335
opt: PollingConfigManagerFactoryOptions
3436
): ProjectConfigManager => {
37+
const updateInterval = opt.updateInterval ?? DEFAULT_UPDATE_INTERVAL;
38+
39+
const backoff = new ExponentialBackoff(1000, updateInterval, 500);
40+
const repeater = new IntervalRepeater(updateInterval, backoff);
41+
3542
const datafileManagerConfig: DatafileManagerConfig = {
3643
sdkKey: opt.sdkKey,
3744
autoUpdate: opt.autoUpdate,
38-
updateInterval: opt.updateInterval,
45+
updateInterval: updateInterval,
3946
urlTemplate: opt.urlTemplate,
4047
datafileAccessToken: opt.datafileAccessToken,
4148
requestHandler: opt.requestHandler,
49+
repeater,
4250
};
4351

4452
const datafileManager = new PollingDatafileManager(datafileManagerConfig);

lib/project_config/datafile_manager.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import { Service } from '../service';
1717
import PersistentKeyValueCache from '../plugins/key_value_cache/persistentKeyValueCache';
1818
import { RequestHandler } from '../utils/http_request_handler/http';
1919
import { Fn, Consumer } from '../utils/type';
20-
import { Ticker } from '../utils/ticker/ticker';
20+
import { Repeater } from '../utils/repeater/repeater';
21+
import { LoggerFacade } from '../modules/logging';
2122

2223
export interface DatafileUpdate {
2324
datafile: string;
@@ -35,12 +36,12 @@ interface Managed {
3536
}
3637

3738
export interface DatafileManager extends Service {
38-
get: () => string | undefined;
39-
onUpdate: (listener: Consumer<string>) => Fn;
39+
get(): string | undefined;
40+
onUpdate(listener: Consumer<string>): Fn;
41+
setLogger(logger: LoggerFacade): void;
4042
}
4143

4244
export type DatafileManagerConfig = {
43-
ticker: Ticker;
4445
requestHandler: RequestHandler;
4546
autoUpdate?: boolean;
4647
sdkKey: string;
@@ -50,6 +51,8 @@ export type DatafileManagerConfig = {
5051
cache?: PersistentKeyValueCache;
5152
datafileAccessToken?: string;
5253
initRetry?: number;
54+
repeater: Repeater;
55+
logger?: LoggerFacade;
5356
}
5457

5558
export interface NodeDatafileManagerConfig extends DatafileManagerConfig {

lib/project_config/polling_datafile_manager.spec.ts

Lines changed: 145 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616
import { describe, beforeEach, afterEach, beforeAll, it, expect, vi, MockInstance } from 'vitest';
1717

1818
import { PollingDatafileManager} from './polling_datafile_manager';;
19-
import { getMockTicker } from '../tests/mock/mockTicker';
20-
import { getMockRequestHandler } from '../tests/mock/mockRequestHandler';
19+
import { getMockRepeater } from '../tests/mock/mockRepeater';
20+
import { getMockAbortableRequest, getMockRequestHandler } from '../tests/mock/mockRequestHandler';
2121
import { Headers, AbortableRequest, Response, RequestHandler} from '../utils/http_request_handler/http';
2222
// import { DatafileManagerConfig } from '../lib/modules/datafile-manager/datafileManager';
2323
// import { advanceTimersByTime, getTimerCount } from './testUtils';
2424
import PersistentKeyValueCache from '../../lib/plugins/key_value_cache/persistentKeyValueCache';
25-
25+
import { getMockLogger } from '../tests/mock/mockLogger';
26+
import { DEFAULT_URL_TEMPLATE, MIN_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './config';
2627

2728
const testCache: PersistentKeyValueCache = {
2829
get(key: string): Promise<string | undefined> {
@@ -49,17 +50,152 @@ const testCache: PersistentKeyValueCache = {
4950
};
5051

5152
describe('PollingDatafileManager', () => {
52-
it('starts the ticker with immediateTick on start', () => {
53-
const ticker = getMockTicker();
53+
it('should log polling interval below 30 seconds', () => {
54+
const repeater = getMockRepeater();
55+
const requestHandler = getMockRequestHandler();
56+
const logger = getMockLogger();
57+
const manager = new PollingDatafileManager({
58+
repeater,
59+
requestHandler,
60+
sdkKey: '123',
61+
logger,
62+
updateInterval: 29000,
63+
});
64+
manager.start();
65+
expect(logger.warn).toHaveBeenCalledWith(UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE);
66+
});
67+
68+
it('should not log polling interval above 30 seconds', () => {
69+
const repeater = getMockRepeater();
70+
const requestHandler = getMockRequestHandler();
71+
const logger = getMockLogger();
72+
const manager = new PollingDatafileManager({
73+
repeater,
74+
requestHandler,
75+
sdkKey: '123',
76+
logger,
77+
updateInterval: 31000,
78+
});
79+
manager.start();
80+
expect(logger.warn).not.toHaveBeenCalled();
81+
});
82+
83+
it('starts the repeater with immediateExecution on start', () => {
84+
const repeater = getMockRepeater();
5485
const requestHandler = getMockRequestHandler();
5586
const manager = new PollingDatafileManager({
87+
repeater,
88+
requestHandler,
5689
sdkKey: '123',
90+
});
91+
manager.start();
92+
expect(repeater.start).toHaveBeenCalledWith(true);
93+
});
94+
95+
it('uses cached version of datafile, resolves onRunning() and calls onUpdate handlers while datafile fetch request waits', async () => {
96+
const repeater = getMockRepeater();
97+
const requestHandler = getMockRequestHandler(); // response promise is pending
98+
const manager = new PollingDatafileManager({
99+
repeater,
100+
requestHandler,
101+
sdkKey: 'keyThatExists',
102+
cache: testCache,
103+
});
104+
105+
manager.start();
106+
repeater.execute(0);
107+
const listener = vi.fn();
108+
109+
manager.onUpdate(listener);
110+
await expect(manager.onRunning()).resolves.toBeUndefined();
111+
expect(listener).toHaveBeenCalledWith(JSON.stringify({ name: 'keyThatExists' }));
112+
});
113+
114+
it('uses cached version of datafile, resolves onRunning() and calls onUpdate handlers even if fetch request fails', async () => {
115+
const repeater = getMockRepeater();
116+
const requestHandler = getMockRequestHandler();
117+
const mockResponse = getMockAbortableRequest(Promise.reject('test error'));
118+
requestHandler.makeRequest.mockReturnValueOnce(mockResponse);
119+
120+
const manager = new PollingDatafileManager({
121+
repeater,
57122
requestHandler,
58-
ticker,
123+
sdkKey: 'keyThatExists',
124+
cache: testCache,
59125
});
126+
60127
manager.start();
61-
expect(ticker.start).toHaveBeenCalledWith(true);
128+
repeater.execute(0);
129+
130+
const listener = vi.fn();
131+
132+
manager.onUpdate(listener);
133+
await expect(manager.onRunning()).resolves.toBeUndefined();
134+
expect(requestHandler.makeRequest).toHaveBeenCalledOnce();
135+
expect(listener).toHaveBeenCalledWith(JSON.stringify({ name: 'keyThatExists' }));
62136
});
137+
138+
// it('uses cached datafile, resolves ready promise, fetches new datafile from network and triggers update event', async () => {
139+
// manager.queuedResponses.push({
140+
// statusCode: 200,
141+
// body: '{"foo": "bar"}',
142+
// headers: {},
143+
// });
144+
145+
// const updateFn = vi.fn();
146+
// manager.on('update', updateFn);
147+
// manager.start();
148+
// await manager.onReady();
149+
// expect(JSON.parse(manager.get())).toEqual({ name: 'keyThatExists' });
150+
// expect(updateFn).toBeCalledTimes(0);
151+
// await advanceTimersByTime(50);
152+
// expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' });
153+
// expect(updateFn).toBeCalledTimes(1);
154+
// });
155+
156+
// it('sets newly recieved datafile in to cache', async () => {
157+
// const cacheSetSpy = vi.spyOn(testCache, 'set');
158+
// manager.queuedResponses.push({
159+
// statusCode: 200,
160+
// body: '{"foo": "bar"}',
161+
// headers: {},
162+
// });
163+
// manager.start();
164+
// await manager.onReady();
165+
// await advanceTimersByTime(50);
166+
// expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' });
167+
// expect(cacheSetSpy.mock.calls[0][0]).toEqual('opt-datafile-keyThatExists');
168+
// expect(JSON.parse(cacheSetSpy.mock.calls[0][1])).toEqual({ foo: 'bar' });
169+
// });
170+
// });
171+
172+
// describe('when constructed with a cache implementation without an already cached datafile', () => {
173+
// beforeEach(() => {
174+
// manager = new TestDatafileManager({
175+
// sdkKey: 'keyThatDoesExists',
176+
// updateInterval: 500,
177+
// autoUpdate: true,
178+
// cache: testCache,
179+
// });
180+
// manager.simulateResponseDelay = true;
181+
// });
182+
183+
// it('does not find cached datafile, fetches new datafile from network, resolves promise and does not trigger update event', async () => {
184+
// manager.queuedResponses.push({
185+
// statusCode: 200,
186+
// body: '{"foo": "bar"}',
187+
// headers: {},
188+
// });
189+
190+
// const updateFn = vi.fn();
191+
// manager.on('update', updateFn);
192+
// manager.start();
193+
// await advanceTimersByTime(50);
194+
// await manager.onReady();
195+
// expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' });
196+
// expect(updateFn).toBeCalledTimes(0);
197+
// });
198+
// });
63199
// describe('when constructed with sdkKey and datafile and autoUpdate: true,', () => {
64200

65201

@@ -672,4 +808,5 @@ describe('PollingDatafileManager', () => {
672808
// expect(updateFn).toBeCalledTimes(0);
673809
// });
674810
// });
675-
});
811+
812+
});

0 commit comments

Comments
 (0)