--- a/server/media-cache.test.js +++ b/server/media-cache.test.js @@ -10,6 +10,8 @@ process.env.DB_PATH = join(root, 'test.db'); const dbmod = await import('./db.js'); const { createMediaCache, validateMedia, MediaSkip, HIGH, LOW, OPT_REV } = await import('./media-cache.js'); +const { initP2pSchema } = await import('./p2p-db.js'); +const { sha256File } = await import('./hash.js'); const fx = (name) => join(root, name); const ff = (...args) => { @@ -26,6 +28,7 @@ beforeAll(async () => { await dbmod.initDb(); + await initP2pSchema(); // adds media_cache.sha256 // 10 s 640x360 H.264 + AAC — the shape of a good cached copy. ff('-f', 'lavfi', '-i', 'testsrc=size=640x360:rate=30:duration=10', '-f', 'lavfi', '-i', 'sine=frequency=440:duration=10', '-c:v', 'libx264', '-preset', 'ultrafast', '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-shortest', fx('good.mp4')); @@ -102,6 +105,7 @@ download: async (id, out) => { calls.push(id); await sleep(30); copyFileSync(fx(state.fixture), out); return out; }, transcode: { enabled: false }, freeBytes: () => 100 * 1024 ** 3, + backfillDelayMs: -1, ...opts, }); return { cache, dir, calls, state }; @@ -110,6 +114,35 @@ for (const r of await dbmod.listMedia()) await dbmod.deleteMedia(r.video_id); } +describe('content hashes (P2P cids)', () => { + test('a promoted copy stores the sha256 of its mp4 and reports it once', async () => { + await clearDb(); + const seen = []; + const { cache, dir } = makeCache({ onReady: (i) => { seen.push(i); } }); + await cache.init(); + const row = await cache.ensureCached('hashAAAAAA1', { priority: HIGH }); + const want = await sha256File(join(dir, `hashAAAAAA1.${row.gen}.mp4`)); + expect((await dbmod.getMedia('hashAAAAAA1')).sha256).toBe(want); + expect(await until(() => seen.length === 1)).toBe(true); + expect(seen[0]).toMatchObject({ id: 'hashAAAAAA1', gen: row.gen, sha256: want, vcodec: 'h264' }); + expect(seen[0].meta.title).toBe('T hashAAAAAA1'); + }); + + test('backfill hashes ready copies that have no sha256 yet', async () => { + await clearDb(); + const seen = []; + const { cache } = makeCache({ onReady: (i) => { seen.push(i); } }); + await cache.init(); + await cache.ensureCached('hashAAAAAA2', { priority: HIGH }); + await dbmod.upsertMedia('hashAAAAAA2', { sha256: null }); + seen.length = 0; + expect(await cache.backfillHashes()).toBe(1); + expect((await dbmod.getMedia('hashAAAAAA2')).sha256).toMatch(/^[0-9a-f]{64}$/); + expect(await until(() => seen.length === 1)).toBe(true); + expect(await cache.backfillHashes()).toBe(0); // nothing left + }); +}); + describe('media cache jobs', () => { test('fetches once, dedupes concurrent requests, then serves from disk', async () => { await clearDb();