Skip to content

Commit dd1a828

Browse files
test: add tests for debug override functionality
Includes expanding plugin tests to check for registration with debug override capabilities.
1 parent 74f44ed commit dd1a828

File tree

4 files changed

+433
-32
lines changed

4 files changed

+433
-32
lines changed
Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
const { initialize } = require('../index');
2+
const stubPlatform = require('./stubPlatform');
3+
const { respondJson } = require('./mockHttp');
4+
const { makeBootstrap } = require('./testUtils');
5+
6+
// Mock the logger functions
7+
const mockLogger = () => ({
8+
error: jest.fn(),
9+
warn: jest.fn(),
10+
info: jest.fn(),
11+
debug: jest.fn(),
12+
});
13+
14+
// Define a basic Plugin structure for tests
15+
const createTestPlugin = (name = 'Test Plugin') => {
16+
const plugin = {
17+
getMetadata: jest.fn().mockReturnValue({ name }),
18+
register: jest.fn(),
19+
getHooks: jest.fn().mockReturnValue([]),
20+
registerDebug: jest.fn(),
21+
};
22+
23+
return plugin;
24+
};
25+
26+
// Helper to initialize the client for tests
27+
async function withClient(initialContext, configOverrides = {}, plugins = [], testFn) {
28+
const platform = stubPlatform.defaults();
29+
const server = platform.testing.http.newServer();
30+
const logger = mockLogger();
31+
32+
// Disable streaming and event sending unless overridden
33+
const defaults = {
34+
baseUrl: server.url,
35+
streaming: false,
36+
sendEvents: false,
37+
useLdd: false,
38+
logger: logger,
39+
plugins: plugins,
40+
};
41+
const config = { ...defaults, ...configOverrides };
42+
const { client, start } = initialize('env', initialContext, config, platform);
43+
44+
server.byDefault(respondJson({}));
45+
start();
46+
47+
try {
48+
await client.waitForInitialization(10);
49+
await testFn(client, logger, platform);
50+
} finally {
51+
await client.close();
52+
server.close();
53+
}
54+
}
55+
56+
describe('LDDebugOverride', () => {
57+
describe('setOverride method', () => {
58+
it('should set override value returned by variation method', async () => {
59+
let debugOverrideInterface;
60+
const mockPlugin = createTestPlugin('test-plugin');
61+
mockPlugin.registerDebug.mockImplementation(debugOverride => {
62+
debugOverrideInterface = debugOverride;
63+
});
64+
65+
await withClient({ key: 'user-key', kind: 'user' }, {}, [mockPlugin], async client => {
66+
expect(client.variation('test-flag', 'default')).toBe('default');
67+
68+
debugOverrideInterface.setOverride('test-flag', 'override-value');
69+
expect(client.variation('test-flag', 'default')).toBe('override-value');
70+
});
71+
});
72+
73+
it('should override values taking precedence over real flag values from bootstrap', async () => {
74+
let debugOverrideInterface;
75+
const mockPlugin = createTestPlugin('test-plugin');
76+
mockPlugin.registerDebug.mockImplementation(debugOverride => {
77+
debugOverrideInterface = debugOverride;
78+
});
79+
80+
const flags = makeBootstrap({ 'existing-flag': { value: 'real-value', version: 1 } });
81+
82+
await withClient({ key: 'user-key', kind: 'user' }, { bootstrap: flags }, [mockPlugin], async client => {
83+
expect(client.variation('existing-flag', 'default')).toBe('real-value');
84+
85+
debugOverrideInterface.setOverride('existing-flag', 'override-value');
86+
expect(client.variation('existing-flag', 'default')).toBe('override-value');
87+
88+
debugOverrideInterface.removeOverride('existing-flag');
89+
expect(client.variation('existing-flag', 'default')).toBe('real-value');
90+
});
91+
});
92+
});
93+
94+
describe('removeOverride method', () => {
95+
it('should remove individual override and revert to default', async () => {
96+
let debugOverrideInterface;
97+
const mockPlugin = createTestPlugin('test-plugin');
98+
mockPlugin.registerDebug.mockImplementation(debugOverride => {
99+
debugOverrideInterface = debugOverride;
100+
});
101+
102+
await withClient({ key: 'user-key', kind: 'user' }, {}, [mockPlugin], async client => {
103+
debugOverrideInterface.setOverride('test-flag', 'override-value');
104+
expect(client.variation('test-flag', 'default')).toBe('override-value');
105+
106+
debugOverrideInterface.removeOverride('test-flag');
107+
expect(client.variation('test-flag', 'default')).toBe('default');
108+
});
109+
});
110+
111+
it('should remove only the specified override leaving others intact', async () => {
112+
let debugOverrideInterface;
113+
const mockPlugin = createTestPlugin('test-plugin');
114+
mockPlugin.registerDebug.mockImplementation(debugOverride => {
115+
debugOverrideInterface = debugOverride;
116+
});
117+
118+
await withClient({ key: 'user-key', kind: 'user' }, {}, [mockPlugin], async client => {
119+
debugOverrideInterface.setOverride('flag1', 'value1');
120+
debugOverrideInterface.setOverride('flag2', 'value2');
121+
debugOverrideInterface.setOverride('flag3', 'value3');
122+
123+
debugOverrideInterface.removeOverride('flag2');
124+
125+
expect(client.variation('flag1', 'default')).toBe('value1');
126+
expect(client.variation('flag2', 'default')).toBe('default');
127+
expect(client.variation('flag3', 'default')).toBe('value3');
128+
129+
const allOverrides = debugOverrideInterface.getAllOverrides();
130+
expect(allOverrides).toEqual({
131+
flag1: 'value1',
132+
flag3: 'value3',
133+
});
134+
});
135+
});
136+
137+
it('should handle removing non-existent override without throwing error', async () => {
138+
let debugOverrideInterface;
139+
const mockPlugin = createTestPlugin('test-plugin');
140+
mockPlugin.registerDebug.mockImplementation(debugOverride => {
141+
debugOverrideInterface = debugOverride;
142+
});
143+
144+
await withClient({ key: 'user-key', kind: 'user' }, {}, [mockPlugin], async client => {
145+
debugOverrideInterface.setOverride('existing-flag', 'value');
146+
147+
// Should not throw error
148+
expect(() => {
149+
debugOverrideInterface.removeOverride('non-existent-flag');
150+
}).not.toThrow();
151+
152+
// Existing override should remain
153+
expect(client.variation('existing-flag', 'default')).toBe('value');
154+
const allOverrides = debugOverrideInterface.getAllOverrides();
155+
expect(allOverrides).toEqual({ 'existing-flag': 'value' });
156+
});
157+
});
158+
159+
it('should be callable multiple times on same flag key safely', async () => {
160+
let debugOverrideInterface;
161+
const mockPlugin = createTestPlugin('test-plugin');
162+
mockPlugin.registerDebug.mockImplementation(debugOverride => {
163+
debugOverrideInterface = debugOverride;
164+
});
165+
166+
await withClient({ key: 'user-key', kind: 'user' }, {}, [mockPlugin], async client => {
167+
debugOverrideInterface.setOverride('test-flag', 'value');
168+
169+
debugOverrideInterface.removeOverride('test-flag');
170+
expect(client.variation('test-flag', 'default')).toBe('default');
171+
172+
// Removing again should not cause issues
173+
expect(() => {
174+
debugOverrideInterface.removeOverride('test-flag');
175+
}).not.toThrow();
176+
177+
expect(debugOverrideInterface.getAllOverrides()).toEqual({});
178+
});
179+
});
180+
});
181+
182+
describe('clearAllOverrides method', () => {
183+
it('should clear all overrides and revert all flags to their default values', async () => {
184+
let debugOverrideInterface;
185+
const mockPlugin = createTestPlugin('test-plugin');
186+
mockPlugin.registerDebug.mockImplementation(debugOverride => {
187+
debugOverrideInterface = debugOverride;
188+
});
189+
190+
await withClient({ key: 'user-key', kind: 'user' }, {}, [mockPlugin], async client => {
191+
debugOverrideInterface.setOverride('flag1', 'value1');
192+
debugOverrideInterface.setOverride('flag2', 'value2');
193+
194+
debugOverrideInterface.clearAllOverrides();
195+
expect(client.variation('flag1', 'default')).toBe('default');
196+
expect(client.variation('flag2', 'default')).toBe('default');
197+
expect(debugOverrideInterface.getAllOverrides()).toEqual({});
198+
});
199+
});
200+
201+
it('should operate safely when no overrides exist', async () => {
202+
let debugOverrideInterface;
203+
const mockPlugin = createTestPlugin('test-plugin');
204+
mockPlugin.registerDebug.mockImplementation(debugOverride => {
205+
debugOverrideInterface = debugOverride;
206+
});
207+
208+
await withClient({ key: 'user-key', kind: 'user' }, {}, [mockPlugin], async () => {
209+
// Should not throw error when no overrides exist
210+
expect(() => {
211+
debugOverrideInterface.clearAllOverrides();
212+
}).not.toThrow();
213+
214+
expect(debugOverrideInterface.getAllOverrides()).toEqual({});
215+
});
216+
});
217+
});
218+
219+
describe('getAllOverrides method', () => {
220+
it('should return all current overrides', async () => {
221+
let debugOverrideInterface;
222+
const mockPlugin = createTestPlugin('test-plugin');
223+
mockPlugin.registerDebug.mockImplementation(debugOverride => {
224+
debugOverrideInterface = debugOverride;
225+
});
226+
227+
await withClient({ key: 'user-key', kind: 'user' }, {}, [mockPlugin], async () => {
228+
debugOverrideInterface.setOverride('test-flag', 'override-value');
229+
230+
const allOverrides = debugOverrideInterface.getAllOverrides();
231+
expect(allOverrides).toEqual({ 'test-flag': 'override-value' });
232+
});
233+
});
234+
235+
it('should return empty object when no overrides have been set', async () => {
236+
let debugOverrideInterface;
237+
const mockPlugin = createTestPlugin('test-plugin');
238+
mockPlugin.registerDebug.mockImplementation(debugOverride => {
239+
debugOverrideInterface = debugOverride;
240+
});
241+
242+
await withClient({ key: 'user-key', kind: 'user' }, {}, [mockPlugin], async () => {
243+
const allOverrides = debugOverrideInterface.getAllOverrides();
244+
expect(allOverrides).toEqual({});
245+
expect(typeof allOverrides).toBe('object');
246+
expect(Array.isArray(allOverrides)).toBe(false);
247+
});
248+
});
249+
250+
it('should return immutable copy not reference to internal state', async () => {
251+
let debugOverrideInterface;
252+
const mockPlugin = createTestPlugin('test-plugin');
253+
mockPlugin.registerDebug.mockImplementation(debugOverride => {
254+
debugOverrideInterface = debugOverride;
255+
});
256+
257+
await withClient({ key: 'user-key', kind: 'user' }, {}, [mockPlugin], async client => {
258+
debugOverrideInterface.setOverride('test-flag', 'original-value');
259+
260+
const overrides1 = debugOverrideInterface.getAllOverrides();
261+
const overrides2 = debugOverrideInterface.getAllOverrides();
262+
263+
// Should be different objects
264+
expect(overrides1).not.toBe(overrides2);
265+
266+
// Modifying returned object should not affect internal state
267+
overrides1['new-flag'] = 'new-value';
268+
delete overrides1['test-flag'];
269+
270+
expect(client.variation('test-flag', 'default')).toBe('original-value');
271+
expect(client.variation('new-flag', 'default')).toBe('default');
272+
273+
const overrides3 = debugOverrideInterface.getAllOverrides();
274+
expect(overrides3).toEqual({ 'test-flag': 'original-value' });
275+
});
276+
});
277+
278+
it('should maintain consistency across different operations', async () => {
279+
let debugOverrideInterface;
280+
const mockPlugin = createTestPlugin('test-plugin');
281+
mockPlugin.registerDebug.mockImplementation(debugOverride => {
282+
debugOverrideInterface = debugOverride;
283+
});
284+
285+
await withClient({ key: 'user-key', kind: 'user' }, {}, [mockPlugin], async () => {
286+
// Test consistency through various operations
287+
expect(debugOverrideInterface.getAllOverrides()).toEqual({});
288+
289+
debugOverrideInterface.setOverride('flag1', 'value1');
290+
expect(debugOverrideInterface.getAllOverrides()).toEqual({ flag1: 'value1' });
291+
292+
debugOverrideInterface.setOverride('flag2', 'value2');
293+
expect(debugOverrideInterface.getAllOverrides()).toEqual({ flag1: 'value1', flag2: 'value2' });
294+
295+
debugOverrideInterface.removeOverride('flag1');
296+
expect(debugOverrideInterface.getAllOverrides()).toEqual({ flag2: 'value2' });
297+
298+
debugOverrideInterface.setOverride('flag2', 'updated-value2');
299+
expect(debugOverrideInterface.getAllOverrides()).toEqual({ flag2: 'updated-value2' });
300+
301+
debugOverrideInterface.clearAllOverrides();
302+
expect(debugOverrideInterface.getAllOverrides()).toEqual({});
303+
});
304+
});
305+
});
306+
307+
describe('integration with client methods', () => {
308+
it('should work correctly with variationDetail method', async () => {
309+
let debugOverrideInterface;
310+
const mockPlugin = createTestPlugin('test-plugin');
311+
mockPlugin.registerDebug.mockImplementation(debugOverride => {
312+
debugOverrideInterface = debugOverride;
313+
});
314+
315+
await withClient({ key: 'user-key', kind: 'user' }, {}, [mockPlugin], async client => {
316+
debugOverrideInterface.setOverride('test-flag', 'override-value');
317+
318+
const detail = client.variationDetail('test-flag', 'default');
319+
expect(detail.value).toBe('override-value');
320+
});
321+
});
322+
323+
it('should include overrides in allFlags method output', async () => {
324+
let debugOverrideInterface;
325+
const mockPlugin = createTestPlugin('test-plugin');
326+
mockPlugin.registerDebug.mockImplementation(debugOverride => {
327+
debugOverrideInterface = debugOverride;
328+
});
329+
330+
const flags = makeBootstrap({ 'real-flag': { value: 'real-value', version: 1 } });
331+
332+
await withClient({ key: 'user-key', kind: 'user' }, { bootstrap: flags }, [mockPlugin], async client => {
333+
debugOverrideInterface.setOverride('override-flag', 'override-value');
334+
debugOverrideInterface.setOverride('real-flag', 'overridden-real-value');
335+
336+
const allFlags = client.allFlags();
337+
expect(allFlags['real-flag']).toBe('overridden-real-value');
338+
expect(allFlags['override-flag']).toBe('override-value');
339+
});
340+
});
341+
});
342+
});

0 commit comments

Comments
 (0)