import { test } from '@japa/runner' import db from '@adonisjs/lucid/services/db' import testUtils from '@adonisjs/core/services/test_utils' import { Par2Pruner } from '#services/par2_pruner' test.group('Par2Pruner', (group) => { group.each.setup(() => testUtils.db().withGlobalTransaction()) test('drops the file lists of complete and judged sets, keeps the rest', async ({ assert }) => { const old = new Date(Date.now() - 3 * 86400_000) let n = 0 const release = async () => { n++ const key = `pp${String(n).padStart(6, '0')}`.padEnd(40, '0') const [row] = await db .table('releases') .insert({ release_key: key, identity_key: key, name: key, source: 'stem', groups: '[]', group_id: 1, files: 2, readable: false }) .returning('id') return Number(row.id) } let candidate = 5000 const set = async (letter: string, holder: number, files: number, hashedIn: number | null, created = old) => { const setId = letter.repeat(32) for (let i = 0; i < files; i++) { const h = `${letter}${i}`.padEnd(32, '0') await db.table('par2_files').insert({ set_id: setId, file_id: h, hash16k: h, length: 1000 + i, name: `f${i}.rar` }) if (hashedIn && i < files) { candidate++ await db.table('probe_candidates').insert({ id: candidate, release_id: hashedIn, kind: 'lone', message_id: `<${candidate}@x>`, file: 'x', status: 'done' }) await db.table('file_hashes').insert({ candidate_id: candidate, release_id: hashedIn, hash16k: h, size: 1000 + i }) } } await db.table('par2_sets').insert({ set_id: setId, release_id: holder, files, created_at: created }) return setId } const count = async (setId: string) => Number((await db.from('par2_files').where('set_id', setId).count('* as n'))[0].n) // Every file's hash in the holder: complete. const a = await release() const complete = await set('a', a, 2, a) // The files hashed in another release (not merged): kept. const b = await release() const apart = await set('b', b, 2, await release()) // Complete but recent: kept (its merges may come). const c = await release() const recent = await set('c', c, 2, c, new Date()) // Nothing hashed, its only holder dead: judged. const d = await release() const dead = await set('d', d, 2, null, new Date()) await db.table('release_verdicts').insert({ release_id: d, rule: 'dead', rule_version: 1 }) // Locked isn't junk: kept. const e = await release() const locked = await set('e', e, 2, null) await db.table('release_verdicts').insert({ release_id: e, rule: 'locked', rule_version: 1 }) // Out of budget: the complete pass's batch, not the judged pass (the next round goes on). assert.deepEqual(await new Par2Pruner().round(0), { complete: 1, judged: 0, rows: 2 }) await db.from('worker_cursors').where('name', 'par2-prune').delete() const round = await new Par2Pruner().round() assert.deepEqual(round, { complete: 0, judged: 1, rows: 2 }) assert.equal(await count(complete), 0) assert.equal(await count(apart), 2) assert.equal(await count(recent), 2) assert.equal(await count(dead), 0) assert.equal(await count(locked), 2) assert.equal((await db.from('par2_sets').count('* as n'))[0].n, '5') // Again: the cursor is past them, nothing more. assert.deepEqual(await new Par2Pruner().round(), { complete: 0, judged: 0, rows: 0 }) }) test('more sets of one created_at than a batch holds: each batch goes on past the last one', async ({ assert }) => { // One prober flush gives thousands of sets the same created_at (to the microsecond). const flush = new Date('2026-10-15T14:38:39.883Z') let candidate = 7000 for (const letter of ['p', 'q', 'r']) { const key = `pr${letter}`.padEnd(40, '0') const [row] = await db.table('releases').insert({ release_key: key, identity_key: key, name: key, source: 'stem', groups: '[]', group_id: 1, files: 1, readable: false }).returning('id') const id = Number(row.id) const setId = letter.repeat(32) const h = `${letter}0`.padEnd(32, '0') await db.table('par2_files').insert({ set_id: setId, file_id: h, hash16k: h, length: 1000, name: 'f.rar' }) candidate++ await db.table('probe_candidates').insert({ id: candidate, release_id: id, kind: 'lone', message_id: `<${candidate}@x>`, file: 'x', status: 'done' }) await db.table('file_hashes').insert({ candidate_id: candidate, release_id: id, hash16k: h, size: 1000 }) await db.rawQuery(`INSERT INTO par2_sets (set_id, release_id, files, created_at) VALUES (?, ?, 1, ?::timestamptz + interval '984 microseconds')`, [setId, id, flush]) } const round = await new Par2Pruner(1).round() assert.equal(round.complete, 3) assert.equal((await db.from('par2_files').whereIn('set_id', ['p'.repeat(32), 'q'.repeat(32), 'r'.repeat(32)]).count('* as n'))[0].n, '0') assert.equal((await db.from('worker_cursors').where('name', 'par2-prune').firstOrFail()).key, 'r'.repeat(32)) }) })