Skip to content

Commit f9bea4d

Browse files
authored
KFP-811 Improve code coverage (#18)
* KFP-811 added tests * KFP-811 added more tests
1 parent 25bb06f commit f9bea4d

File tree

5 files changed

+1529
-0
lines changed

5 files changed

+1529
-0
lines changed
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
import { decodeUrlEncodedCookie } from '../../src/context/CookieDecoder.js';
2+
import { logger } from '../../src/index.js';
3+
4+
// Mock the logger
5+
jest.mock('../../src/index.js', () => ({
6+
logger: {
7+
info: jest.fn(),
8+
debug: jest.fn(),
9+
warn: jest.fn(),
10+
error: jest.fn(),
11+
},
12+
}));
13+
14+
describe('CookieDecoder', () => {
15+
beforeEach(() => {
16+
jest.clearAllMocks();
17+
});
18+
19+
describe('decodeUrlEncodedCookie', () => {
20+
describe('valid base64 cookies', () => {
21+
it('should return already decoded base64 cookie without modification', () => {
22+
const base64Cookie = 'eyJrZXkiOiJ2YWx1ZSJ9'; // Base64 encoded {"key":"value"}
23+
24+
const result = decodeUrlEncodedCookie(base64Cookie);
25+
26+
expect(result).toBe(base64Cookie);
27+
expect(logger.info).not.toHaveBeenCalled(); // No decoding needed
28+
});
29+
30+
it('should decode URL-encoded base64 cookie once', () => {
31+
const encodedBase64 = 'eyJrZXkiOiJ2YWx1ZSJ9%3D%3D'; // URL encoded base64 with padding
32+
const expectedDecoded = 'eyJrZXkiOiJ2YWx1ZSJ9==';
33+
34+
const result = decodeUrlEncodedCookie(encodedBase64);
35+
36+
expect(result).toBe(expectedDecoded);
37+
expect(logger.info).toHaveBeenCalledWith('Applied URL decoding attempt 1');
38+
expect(logger.debug).toHaveBeenCalledWith(`Decode 1: ${expectedDecoded}`);
39+
});
40+
41+
it('should decode multiple levels of URL encoding', () => {
42+
const doubleEncoded = 'eyJrZXkiJTNBJTIydmFsdWUlMjIlN0Q%253D'; // Double URL encoded base64
43+
44+
const result = decodeUrlEncodedCookie(doubleEncoded);
45+
46+
expect(logger.info).toHaveBeenCalledWith('Applied URL decoding attempt 1');
47+
expect(logger.info).toHaveBeenCalledWith('Applied URL decoding attempt 2');
48+
expect(result).toMatch(/^[A-Za-z0-9+/]+=*$/); // Should be valid base64
49+
});
50+
51+
it('should stop at maximum decode attempts (3)', () => {
52+
// Create a string that needs multiple decodes
53+
let multipleEncoded = 'test value with spaces'; // Spaces will be encoded
54+
for (let i = 0; i < 5; i++) {
55+
multipleEncoded = encodeURIComponent(multipleEncoded);
56+
}
57+
58+
const result = decodeUrlEncodedCookie(multipleEncoded);
59+
60+
expect(logger.warn).toHaveBeenCalledWith('Reached maximum decode attempts (3), using current result');
61+
expect(logger.info).toHaveBeenCalledTimes(3); // Only 3 decode attempts
62+
});
63+
});
64+
65+
describe('valid JSON cookies (dev environment)', () => {
66+
it('should return already decoded JSON cookie without modification', () => {
67+
const jsonCookie = '{"sessionId":"abc123","userId":"user456"}';
68+
69+
const result = decodeUrlEncodedCookie(jsonCookie);
70+
71+
expect(result).toBe(jsonCookie);
72+
expect(logger.info).not.toHaveBeenCalled(); // No decoding needed
73+
});
74+
75+
it('should decode URL-encoded JSON cookie', () => {
76+
const encodedJson = '%7B%22sessionId%22%3A%22abc123%22%7D'; // {"sessionId":"abc123"}
77+
const expectedDecoded = '{"sessionId":"abc123"}';
78+
79+
const result = decodeUrlEncodedCookie(encodedJson);
80+
81+
expect(result).toBe(expectedDecoded);
82+
expect(logger.info).toHaveBeenCalledWith('Applied URL decoding attempt 1');
83+
});
84+
85+
it('should handle JSON arrays', () => {
86+
const encodedJsonArray = '%5B%22item1%22%2C%22item2%22%5D'; // ["item1","item2"]
87+
const expectedDecoded = '["item1","item2"]';
88+
89+
const result = decodeUrlEncodedCookie(encodedJsonArray);
90+
91+
expect(result).toBe(expectedDecoded);
92+
});
93+
});
94+
95+
describe('invalid cookie formats', () => {
96+
it('should return original cookie for invalid format after decoding', () => {
97+
const invalidEncoded = '%48%65%6C%6C%6F%20%57%6F%72%6C%64'; // "Hello World" - not base64 or JSON
98+
const originalCookie = invalidEncoded;
99+
100+
const result = decodeUrlEncodedCookie(invalidEncoded);
101+
102+
expect(result).toBe(originalCookie);
103+
expect(logger.warn).toHaveBeenCalledWith('Decoded cookie appears invalid, keeping original');
104+
});
105+
106+
it('should handle empty strings', () => {
107+
const result = decodeUrlEncodedCookie('');
108+
109+
expect(result).toBe('');
110+
expect(logger.warn).toHaveBeenCalledWith('Decoded cookie appears invalid, keeping original');
111+
});
112+
113+
it('should handle whitespace-only strings', () => {
114+
const whitespaceOnly = ' ';
115+
116+
const result = decodeUrlEncodedCookie(whitespaceOnly);
117+
118+
expect(result).toBe(whitespaceOnly);
119+
expect(logger.warn).toHaveBeenCalledWith('Decoded cookie appears invalid, keeping original');
120+
});
121+
122+
it('should handle null and undefined gracefully', () => {
123+
// Use strings that definitely won't match any valid patterns
124+
const result1 = decodeUrlEncodedCookie('null!@#'); // Invalid characters
125+
const result2 = decodeUrlEncodedCookie('undefined!@#'); // Invalid characters
126+
127+
// These contain invalid characters and should trigger the warning
128+
expect(result1).toBe('null!@#');
129+
expect(result2).toBe('undefined!@#');
130+
expect(logger.warn).toHaveBeenCalledWith('Decoded cookie appears invalid, keeping original');
131+
expect(logger.warn).toHaveBeenCalledTimes(2);
132+
});
133+
});
134+
135+
describe('decode error handling', () => {
136+
it('should handle malformed URI encoding gracefully', () => {
137+
// Use a string with valid hex pattern but results in invalid format
138+
const malformedUri = '%C0%80'; // Overlong encoding (invalid UTF-8)
139+
140+
const result = decodeUrlEncodedCookie(malformedUri);
141+
142+
// Will decode but result in invalid format, so returns original
143+
expect(logger.warn).toHaveBeenCalledWith('Decoded cookie appears invalid, keeping original');
144+
expect(result).toBe(malformedUri);
145+
});
146+
147+
it('should stop decoding when decode error occurs', () => {
148+
// Mock decodeURIComponent to throw an error for testing
149+
const originalDecodeURIComponent = global.decodeURIComponent;
150+
global.decodeURIComponent = jest.fn().mockImplementation((str) => {
151+
if (str.includes('%20')) {
152+
throw new Error('URI malformed');
153+
}
154+
return originalDecodeURIComponent(str);
155+
});
156+
157+
const testString = 'test%20value'; // This will trigger the mock error
158+
159+
const result = decodeUrlEncodedCookie(testString);
160+
161+
expect(logger.warn).toHaveBeenCalledWith(expect.stringMatching(/Failed to decode on attempt 1:/));
162+
expect(result).toBe(testString);
163+
164+
// Restore original function
165+
global.decodeURIComponent = originalDecodeURIComponent;
166+
});
167+
});
168+
169+
describe('edge cases', () => {
170+
it('should handle cookies that decode to same value (infinite loop protection)', () => {
171+
// Use a value that won't change when decoded
172+
const noEncoding = 'eyJrZXkiOiJ2YWx1ZSJ9'; // Valid base64, no URL encoding needed
173+
174+
const result = decodeUrlEncodedCookie(noEncoding);
175+
176+
expect(result).toBe(noEncoding);
177+
// No decode attempts should be logged since no %XX patterns exist
178+
expect(logger.info).not.toHaveBeenCalled();
179+
});
180+
181+
it('should handle simple invalid format', () => {
182+
const invalidFormat = 'not@valid#format!'; // Contains special chars not in base64 or JSON
183+
184+
const result = decodeUrlEncodedCookie(invalidFormat);
185+
186+
// Since no URL encoding needed, should validate and return original for invalid format
187+
expect(result).toBe(invalidFormat);
188+
expect(logger.warn).toHaveBeenCalledWith('Decoded cookie appears invalid, keeping original');
189+
});
190+
191+
it('should handle special characters in base64', () => {
192+
const base64WithSpecialChars = 'eyJ0ZXN0IjogImhlbGxvLXdvcmxkX3Rlc3QrMTIzPSJ9'; // Contains +, -, _, =
193+
194+
const result = decodeUrlEncodedCookie(base64WithSpecialChars);
195+
196+
expect(result).toBe(base64WithSpecialChars);
197+
});
198+
199+
it('should handle cookies with mixed URL encoding patterns', () => {
200+
// Some characters encoded, others not
201+
const mixedEncoding = 'eyJrZXki%3Ai%22dmFsdWUi%7D'; // Partial encoding
202+
203+
const result = decodeUrlEncodedCookie(mixedEncoding);
204+
205+
expect(logger.info).toHaveBeenCalledWith('Applied URL decoding attempt 1');
206+
});
207+
});
208+
209+
describe('needsDecoding internal logic', () => {
210+
it('should identify cookies that need decoding', () => {
211+
const needsDecodingCases = ['hello%20world', '%7B%22key%22%3A%22value%22%7D', 'test%3D', 'value%2B123'];
212+
213+
needsDecodingCases.forEach((cookie) => {
214+
const result = decodeUrlEncodedCookie(cookie);
215+
expect(logger.info).toHaveBeenCalledWith('Applied URL decoding attempt 1');
216+
jest.clearAllMocks(); // Clear for next iteration
217+
});
218+
});
219+
220+
it('should identify cookies that do not need decoding', () => {
221+
const noDecodingNeededCases = [
222+
'eyJrZXkiOiJ2YWx1ZSJ9', // base64
223+
'{"key":"value"}', // JSON
224+
'simplestring',
225+
'123456789',
226+
];
227+
228+
noDecodingNeededCases.forEach((cookie) => {
229+
decodeUrlEncodedCookie(cookie);
230+
expect(logger.info).not.toHaveBeenCalled(); // No decoding attempts
231+
jest.clearAllMocks(); // Clear for next iteration
232+
});
233+
});
234+
});
235+
236+
describe('isValidCookieFormat internal logic', () => {
237+
it('should validate base64 format correctly', () => {
238+
const validBase64Cases = ['eyJrZXkiOiJ2YWx1ZSJ9', 'YWJjZGVmZ2g=', 'dGVzdA==', 'MTIzNDU2Nzg5MA=='];
239+
240+
validBase64Cases.forEach((cookie) => {
241+
const result = decodeUrlEncodedCookie(cookie);
242+
expect(result).toBe(cookie); // Should return as-is
243+
expect(logger.warn).not.toHaveBeenCalledWith('Decoded cookie appears invalid, keeping original');
244+
jest.clearAllMocks();
245+
});
246+
});
247+
248+
it('should validate JSON format correctly', () => {
249+
const validJsonCases = ['{"key":"value"}', '["item1","item2"]', '{"nested":{"key":"value"}}', '[]', '{}'];
250+
251+
validJsonCases.forEach((cookie) => {
252+
const result = decodeUrlEncodedCookie(cookie);
253+
expect(result).toBe(cookie); // Should return as-is
254+
expect(logger.warn).not.toHaveBeenCalledWith('Decoded cookie appears invalid, keeping original');
255+
jest.clearAllMocks();
256+
});
257+
});
258+
});
259+
});
260+
});

0 commit comments

Comments
 (0)