Skip to content

Commit 4aa31fe

Browse files
Merge pull request #31 from TanvikaOjha/fix/cacheGet
fix: safely handle corrupt json cache entries in cacheGet
2 parents dcdee5d + bb4785a commit 4aa31fe

2 files changed

Lines changed: 59 additions & 1 deletion

File tree

src/__tests__/redis.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { cacheGet } from './redis';
2+
import { redis } from './redis'; // or your Redis client module
3+
4+
describe('cacheGet', () => {
5+
const testKey = 'test-corrupt-entry';
6+
const fullKey = `cache:${testKey}`;
7+
8+
afterEach(async () => {
9+
await redis.del(fullKey);
10+
jest.restoreAllMocks();
11+
});
12+
13+
it('should return parsed data for a valid cache entry', async () => {
14+
const data = { id: 123, name: 'Alice' };
15+
await redis.set(fullKey, JSON.stringify(data));
16+
17+
const result = await cacheGet<{ id: number; name: string }>(testKey);
18+
expect(result).toEqual(data);
19+
});
20+
21+
it('should handle malformed JSON safely by logging, deleting the key, and returning null', async () => {
22+
// 1. Seed a corrupted JSON entry in Redis
23+
const corruptedValue = '{ "id": 123, "name": invalid_json ';
24+
await redis.set(fullKey, corruptedValue);
25+
26+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
27+
28+
// 2. Call cacheGet — should NOT throw
29+
const result = await cacheGet(testKey);
30+
31+
// 3. Verify acceptance criteria
32+
expect(result).toBeNull();
33+
expect(warnSpy).toHaveBeenCalledWith(
34+
expect.stringContaining(`Failed to parse cached JSON for key "${fullKey}"`),
35+
expect.any(Error)
36+
);
37+
38+
// 4. Verify the corrupted key was evicted from Redis
39+
const remainingKey = await redis.get(fullKey);
40+
expect(remainingKey).toBeNull();
41+
});
42+
});

src/redis.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,25 @@ export async function cacheSet(key: string, value: unknown, ttlSeconds: number):
7575
* Retrieve a cached JSON value. Returns null on miss.
7676
*/
7777
export async function cacheGet<T>(key: string): Promise<T | null> {
78+
const fullKey = `cache:${key}`;
7879
const raw = await redis.get(`cache:${key}`);
7980
if (!raw) return null;
80-
return JSON.parse(raw) as T;
81+
82+
try{
83+
return JSON.parse(raw) as T;
84+
}
85+
catch(error) {
86+
console.warn(`[Cache Error] Failed to parse cached JSON for key "${fullKey}". Evicting corrupted key.`, error)
87+
88+
};
89+
90+
// Deleting the corrupted key so subsequent requests repopulate it safely
91+
try {
92+
await redis.del(fullKey);
93+
} catch (delError) {
94+
console.error(`[Cache Error] Failed to delete corrupted key "${fullKey}":`, delError);
95+
}
96+
return null;
8197
}
8298

8399
/**

0 commit comments

Comments
 (0)