'use strict'; const assert = require('node:assert/strict'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const test = require('node:test'); const { createMediaCacheService } = require('../../src/services/media-cache-service'); function fixture() { const parent = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-media-cache-')); let clock = 1_800_000_000_000; const service = createMediaCacheService({ root: path.join(parent, 'cache'), indexPath: path.join(parent, 'index.json'), now: () => clock, defaultTtlMs: 100 }); return { parent, service, tick: value => { clock += value; }, close: () => fs.rmSync(parent, { recursive: true, force: true }) }; } const remote = locator => ({ type: 'remote', provider: 'ps5-ftp', locator }); test('cache inventory preserves distinct provenance and exposes size/count/age metrics', async () => { const f = fixture(); try { await f.service.ensureCached({ key: 'remote:a', relativePath: 'a.jpg', origin: remote('/user/av_contents/photo/a.jpg'), fetchTo: target => fs.writeFileSync(target, 'cached') }); fs.writeFileSync(path.join(f.parent, 'cache', 'generated.jpg'), 'frame'); f.service.registerManaged({ key: 'generated:a', relativePath: 'generated.jpg', kind: 'generated', origin: { type: 'generated', provider: 'ffmpeg-frame', locator: '/user/av_contents/video/a.webm' } }); f.service.registerImported({ key: 'import:a', bytes: 99, origin: { type: 'imported', provider: 'managed-import', locator: 'manifest:item-a' } }); f.tick(25); const status = f.service.status(); assert.equal(status.metrics.count, 3); assert.equal(status.metrics.byKind.cached.count, 1); assert.equal(status.metrics.byKind.generated.count, 1); assert.equal(status.metrics.byKind.imported.count, 1); assert.equal(status.metrics.byOrigin.remote.count, 1); assert.equal(status.metrics.oldestAgeMs, 25); assert.equal(status.entries.find(entry => entry.kind === 'imported').pinned, true); } finally { f.close(); } }); test('TTL/LRU retention and granular clear preserve pinned cache and imported originals', async () => { const f = fixture(); try { await f.service.ensureCached({ key: 'old', relativePath: 'old.jpg', origin: remote('/old.jpg'), ttlMs: 10, fetchTo: target => fs.writeFileSync(target, 'old') }); const pinned = await f.service.ensureCached({ key: 'pinned', relativePath: 'pinned.jpg', origin: remote('/pinned.jpg'), ttlMs: 10, fetchTo: target => fs.writeFileSync(target, 'pinned') }); f.service.setPinned(pinned.id, true); f.service.registerImported({ key: 'original', bytes: 500, origin: { type: 'imported', provider: 'managed-import', locator: 'portable:original' } }); f.tick(20); const expired = f.service.clear({ expiredOnly: true, originType: 'remote' }); assert.equal(expired.removed, 1); assert.equal(fs.existsSync(path.join(f.parent, 'cache', 'old.jpg')), false); assert.equal(fs.existsSync(path.join(f.parent, 'cache', 'pinned.jpg')), true); const all = f.service.clear({}); assert.equal(all.removed, 0); assert.deepEqual(f.service.status().entries.map(entry => entry.kind).sort(), ['cached', 'imported']); } finally { f.close(); } }); test('interrupted fetch leaves no published or partial asset and keeps prior index intact', async () => { const f = fixture(); try { await f.service.ensureCached({ key: 'good', relativePath: 'good.jpg', origin: remote('/good.jpg'), fetchTo: target => fs.writeFileSync(target, 'good') }); await assert.rejects( () => f.service.ensureCached({ key: 'broken', relativePath: 'broken.jpg', origin: remote('/broken.jpg'), fetchTo: target => { fs.writeFileSync(target, 'partial'); throw new Error('connection lost'); } }), /connection lost/ ); assert.equal(fs.existsSync(path.join(f.parent, 'cache', 'broken.jpg')), false); assert.equal(fs.existsSync(path.join(f.parent, 'cache', 'good.jpg')), true); assert.deepEqual(f.service.status().entries.map(entry => entry.origin.locator), ['/good.jpg']); assert.equal(fs.readdirSync(path.join(f.parent, 'cache')).some(name => name.endsWith('.partial')), false); } finally { f.close(); } }); test('managed paths cannot escape the cache root or use symlinks', () => { const f = fixture(); try { assert.throws(() => f.service.registerManaged({ key: 'escape', relativePath: '../outside.jpg', origin: remote('/outside.jpg') }), /inside the managed root/); } finally { f.close(); } });