A seeding node fetches a range over one NNTP connection, parses headers with the live rules, groups them into files in memory (lib/aggregate) and stores per complete file its release, the file and a completed binary (SeedService), instead of one parts row per article. What doesn't complete within the window goes through the regular ingest path as fragments. `--dry-run` only fetches and aggregates, to measure a node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
48 lines
2.2 KiB
TypeScript
48 lines
2.2 KiB
TypeScript
import { test } from '@japa/runner'
|
|
import db from '@adonisjs/lucid/services/db'
|
|
import testUtils from '@adonisjs/core/services/test_utils'
|
|
import { segmentFromHeader, type IngestSegment } from '#services/IngestService'
|
|
import SeedService from '#services/SeedService'
|
|
import QueueService from '#services/QueueService'
|
|
import { FileAggregator } from '#lib/aggregate/index'
|
|
import { segmentsOf } from '#services/FileStore'
|
|
|
|
function header(subject: string, n: number) {
|
|
return {
|
|
'subject': subject,
|
|
'from': 'seeder <seed@example.invalid>',
|
|
'date': 'Mon, 05 Oct 2026 10:00:00 +0000',
|
|
'message-id': `<seed-${n}-${subject.length}@test>`,
|
|
':bytes': '700000',
|
|
}
|
|
}
|
|
|
|
test.group('SeedService', (group) => {
|
|
group.each.setup(() => testUtils.db().withGlobalTransaction())
|
|
group.teardown(() => QueueService.closeAll())
|
|
|
|
test('stores aggregated files with their release and a completed binary', async ({ assert }) => {
|
|
const aggregator = new FileAggregator<IngestSegment>()
|
|
const subjects = [1, 2].map((i) => `Seed.Test.S01E01.1080p - [1/2] - "Seed.Test.S01E01.1080p.part${i}.mkv" yEnc (%d/3)`)
|
|
const segments = subjects.flatMap((s) => [1, 2, 3].map((n) => segmentFromHeader(header(s.replace('%d', String(n)), n), 'alt.binaries.test', n)!))
|
|
const files = aggregator.add(segments)
|
|
assert.lengthOf(files, 2)
|
|
|
|
const result = await SeedService.store(files)
|
|
assert.include(result, { files: 2, existing: 0, releases: 1 })
|
|
|
|
const stored = await db.from('files').where('filename', 'Seed.Test.S01E01.1080p.part1.mkv').first()
|
|
assert.equal(stored.parts, 3)
|
|
assert.deepEqual(segmentsOf(stored).map((s) => s.number), [1, 2, 3])
|
|
const release = await db.from('releases').where('id', stored.release_id).first()
|
|
assert.equal(release.name, segments[0].release!.name) // named as live indexing would
|
|
const binary = await db.from('binaries').where('file_key', stored.file_key).first()
|
|
assert.equal(Number(binary.file_id), Number(stored.id))
|
|
assert.isNotNull(binary.completed_at)
|
|
|
|
// The same files again (a crosspost, a second run): nothing new.
|
|
const again = await SeedService.store(files)
|
|
assert.include(again, { files: 0, existing: 2 })
|
|
})
|
|
})
|