Files
usenet-indexer/tests/functional/par2_name_merge.spec.ts
T
Daan MeijerandClaude Opus 5.5 fb6b00f230
Test and deploy / test (push) Successful in 50s
Test and deploy / edge (push) Successful in 1m53s
Test and deploy / deploy (push) Successful in 57s
Name pass: only holders near the newest; merges drop duplicate candidates
The name pass put every holder of a set into its merge: a par2 posted
again years later ("Auto Bild Klassik Februar 02-2014", held by a 2014
and a 2026 release) would have joined the repost to the old upload. Now
the newest holder anchors it and only holders in its group within 12
hours take part. Every round, three sets failed on the probe candidates'
unique key: a member held a candidate of an article the target had.
merge() now drops such a candidate (the target's, or a lower member's,
stays), as it already does for par2 sets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:59:09 +02:00

89 lines
5.7 KiB
TypeScript

import { test } from '@japa/runner'
import db from '@adonisjs/lucid/services/db'
import testUtils from '@adonisjs/core/services/test_utils'
import BatchStore from '#services/batch_store'
import { Par2Merger, fileKey } from '#services/par2_merger'
import { RangeIndexer } from '#lib/edge/index'
import { DEFAULT_RULES } from '#lib/rules/index'
import type { Overview } from '#lib/nntp/index'
const GROUP = 'alt.binaries.test'
/** One file without a counter, from `poster`, posted `minutes` after the first. */
function file(name: string, at: number, minutes: number, poster = 'up <up@load.er>'): Overview[] {
const date = new Date(Date.parse('2026-10-20T10:00:00Z') + minutes * 60_000).toUTCString().replace('GMT', '+0000')
return [[at, { 'subject': `"${name}" yEnc (1/1)`, 'from': poster, 'date': date, 'message-id': `<${at}@test>`, ':bytes': '700000', 'xref': `news.example ${GROUP}:${at}` }] as Overview]
}
let stores = 0
async function store(overviews: Overview[]) {
stores++
const g = (await db.from('groups').where('name', GROUP).first()) ?? (await db.table('groups').insert({ name: GROUP, priority: 1 }).returning('*'))[0]
const [n] = await db.table('nodes').insert({ name: `n${stores}` }).returning('id')
const [r] = await db.table('ranges').insert({ group_id: g.id, start_id: stores * 100_000, end_id: stores * 100_000 + 99_999, next_id: stores * 100_000, status: 'leased', node_id: n.id }).returning('id')
const indexer = new RangeIndexer({ group: GROUP, start: stores * 100_000, end: stores * 100_000 + 99_999, rules: { ...DEFAULT_RULES, samplePercent: 0, droppedSignatures: [], priorities: { [GROUP]: 1 } } })
indexer.add(overviews, stores * 100_000 + 100_000)
indexer.finish()
await BatchStore.store(Number(r.id), Number(n.id), indexer.take())
}
test.group('Par2Merger / by file names', (group) => {
group.each.setup(() => testUtils.db().withGlobalTransaction())
test('a name as compared: no case, spaces or punctuation, UTF-8 read as Latin-1 read back', ({ assert }) => {
assert.equal(fileKey('Some/Dir/Ein.dunkles.Vermächtnis - 1080p.MKV'), fileKey('Ein dunkles Vermächtnis 1080p.mkv'))
assert.notEqual(fileKey('Track One.flac'), fileKey('Track Two.flac'))
})
test("a set's poster's releases nearby whose files it all names join its release; others stay", async ({ assert }) => {
await store([
// The album's index files, its tracks apart (no counter: a release each), and the same poster's next album.
...file('Some.Album-WEB-2026.par2', 100_010, 0),
...file('Some.Album-WEB-2026.nfo', 100_011, 0),
...file('Track One.flac', 100_020, 1),
...file('Track Two.flac', 100_030, 2),
...file('Ein.dunkles.Vermächtnis.mkv', 100_040, 3),
...file('Another Song.flac', 100_050, 4),
// A track the set names, but posted by someone else, and one a day later.
...file('Track One.flac', 100_060, 5, 'other <o@x>'),
])
await store(file('Track Two.flac', 200_070, 24 * 60 + 10))
const ids = Object.fromEntries((await db.from('releases').select('id', 'name', 'poster', 'posted_at')).map((r) => [`${r.name}${r.poster.startsWith('other') ? ' (other)' : ''}${new Date(r.posted_at).getUTCDate() === 21 ? ' (later)' : ''}`, Number(r.id)]))
const index = ids['Some.Album-WEB-2026']
const setId = 'a'.repeat(32)
await db.table('par2_files').multiInsert(
['Track One.flac', 'Track Two.flac', 'Ein.dunkles.Vermächtnis.mkv'].map((name, i) => ({ set_id: setId, file_id: String(i).repeat(32), hash16k: 'f'.repeat(32), length: 600_000, name }))
)
await db.table('par2_sets').insert({ set_id: setId, release_id: index, files: 3 })
assert.equal(await new Par2Merger().mergeByName(setId), 3)
const left = (await db.from('releases').orderBy('name').select('name', 'files', 'poster')).map((r) => `${r.name} ${r.files}${r.poster.startsWith('other') ? ' (other)' : ''}`)
assert.includeMembers(left, ['Another Song 1', 'Track One 1 (other)', 'Track Two 1'])
assert.lengthOf(left, 4)
const merged = await db.from('release_merges').where('via', 'name').count('* as n')
assert.equal(Number(merged[0].n), 3)
// Nothing more to find.
assert.equal(await new Par2Merger().mergeByName(setId), 0)
})
test('not a holder years off (a repost of the par2); a member with an article the target has merges without it', async ({ assert }) => {
await store([...file('Some.Album-WEB-2026.par2', 100_010, 0), ...file('Track One.flac', 100_020, 1)])
// The same par2 posted again a year later, in a range of its own.
await store(file('Some.Album-WEB-2026.par2', 200_010, 365 * 24 * 60))
const releases = await db.from('releases').orderBy('posted_at').select('id', 'name', 'posted_at')
const [index, track, repost] = releases.map((r) => Number(r.id))
const setId = 'b'.repeat(32)
await db.table('par2_files').insert({ set_id: setId, file_id: '1'.repeat(32), hash16k: 'f'.repeat(32), length: 600_000, name: 'Track One.flac' })
await db.table('par2_sets').multiInsert([index, repost].map((id) => ({ set_id: setId, release_id: id, files: 1 })))
// The repost is the newest holder: nothing near it to join.
assert.equal(await new Par2Merger().mergeByName(setId), 0)
await db.from('par2_sets').where('release_id', repost).delete()
// Both releases hold a candidate of the same article.
await db.table('probe_candidates').multiInsert([index, track].map((id) => ({ release_id: id, kind: 'par2', message_id: '<same@x>', file: 'x.par2' })))
assert.equal(await new Par2Merger().mergeByName(setId), 1)
assert.lengthOf(await db.from('probe_candidates').where('message_id', '<same@x>'), 1)
assert.lengthOf(await db.from('releases').whereIn('id', [index, track]), 1)
assert.lengthOf(await db.from('releases').where('id', repost), 1)
})
})