network-cache.test.ts
1 import { afterEach, beforeEach, describe, expect, it } from 'vitest'; 2 import * as fs from 'node:fs'; 3 import * as os from 'node:os'; 4 import * as path from 'node:path'; 5 import { 6 DEFAULT_TTL_MS, 7 findEntry, 8 getCachePath, 9 loadNetworkCache, 10 saveNetworkCache, 11 type CachedNetworkEntry, 12 type NetworkCacheFile, 13 } from './network-cache.js'; 14 15 function makeEntry(key: string, body: unknown = { ok: true }): CachedNetworkEntry { 16 return { key, url: `https://x.com/${key}`, method: 'GET', status: 200, size: 2, ct: 'application/json', body }; 17 } 18 19 describe('network-cache', () => { 20 let baseDir: string; 21 22 beforeEach(() => { 23 baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-netcache-')); 24 }); 25 afterEach(() => { 26 fs.rmSync(baseDir, { recursive: true, force: true }); 27 }); 28 29 it('sanitizes workspace names into safe filenames', () => { 30 const p = getCachePath('browser:default', baseDir); 31 expect(path.basename(p)).toBe('browser_default.json'); 32 }); 33 34 it('round-trips entries through save + load', () => { 35 saveNetworkCache('ws', [makeEntry('UserTweets'), makeEntry('UserByScreenName')], baseDir); 36 const res = loadNetworkCache('ws', { baseDir }); 37 expect(res.status).toBe('ok'); 38 expect(res.file?.entries).toHaveLength(2); 39 expect(res.file?.entries[0].key).toBe('UserTweets'); 40 }); 41 42 it('reports missing when cache file does not exist', () => { 43 expect(loadNetworkCache('nope', { baseDir }).status).toBe('missing'); 44 }); 45 46 it('reports expired when the cache is older than ttl', () => { 47 saveNetworkCache('ws', [makeEntry('A')], baseDir); 48 const future = Date.now() + DEFAULT_TTL_MS + 60_000; 49 const res = loadNetworkCache('ws', { baseDir, now: future }); 50 expect(res.status).toBe('expired'); 51 expect(res.file?.entries).toHaveLength(1); 52 }); 53 54 it('reports corrupt for malformed json', () => { 55 const file = getCachePath('ws', baseDir); 56 fs.mkdirSync(path.dirname(file), { recursive: true }); 57 fs.writeFileSync(file, '{not json'); 58 expect(loadNetworkCache('ws', { baseDir }).status).toBe('corrupt'); 59 }); 60 61 it('reports corrupt for wrong schema version', () => { 62 const file = getCachePath('ws', baseDir); 63 fs.mkdirSync(path.dirname(file), { recursive: true }); 64 fs.writeFileSync(file, JSON.stringify({ version: 0, entries: [] })); 65 expect(loadNetworkCache('ws', { baseDir }).status).toBe('corrupt'); 66 }); 67 68 it('findEntry returns matching entry or null', () => { 69 const file: NetworkCacheFile = { 70 version: 1, workspace: 'ws', savedAt: new Date().toISOString(), 71 entries: [makeEntry('A'), makeEntry('B')], 72 }; 73 expect(findEntry(file, 'B')?.key).toBe('B'); 74 expect(findEntry(file, 'missing')).toBeNull(); 75 }); 76 });