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 { RangeIndexer } from '#lib/edge/index'
import { parseNzb } from '#lib/nzb/index'
import { DEFAULT_RULES } from '#lib/rules/index'
import type { Overview } from '#lib/nntp/index'
const GROUP = 'alt.binaries.test'
function file(name: string, counter: string, parts: number, at: number): Overview[] {
return Array.from({ length: parts }, (_, i) => [at + i, {
'subject': `${counter} - "${name}" yEnc (${i + 1}/${parts})`, 'from': 'p
', 'date': 'Mon, 05 Oct 2026 10:00:00 +0000',
'message-id': `<${name}-${i + 1}@test>`, ':bytes': String(700000 + i), 'xref': `news.example ${GROUP}:${at + i}`,
}] as Overview)
}
test.group('Releases API', (group) => {
group.each.setup(() => testUtils.db().withGlobalTransaction())
test('finds a release by the words of its name and builds its NZB from the chunk store', async ({ client, assert }) => {
const [g] = await db.table('groups').insert({ name: GROUP, priority: 1 }).returning('id')
const [n] = await db.table('nodes').insert({ name: 'api-node' }).returning('id')
const [r] = await db.table('ranges').insert({ group_id: g.id, start_id: 1, end_id: 1000, next_id: 1, status: 'leased', node_id: n.id }).returning('id')
const indexer = new RangeIndexer({ group: GROUP, start: 1, end: 1000, rules: { ...DEFAULT_RULES, samplePercent: 0, droppedSignatures: [], priorities: { [GROUP]: 1 } } })
indexer.add([...file('Some.Show.S01E01.1080p.WEB.part1.rar', 'Some.Show.S01E01 [1/2]', 3, 100), ...file('Some.Show.S01E01.1080p.WEB.par2', 'Some.Show.S01E01 [2/2]', 1, 200)], 1001)
indexer.finish()
await BatchStore.store(Number(r.id), Number(n.id), indexer.take())
// Open, as voyager 1's catalog (the web interface uses it).
const found = await client.get('/api/v1/releases?q=show s01e01')
found.assertStatus(200)
const releases = (found.body() as { releases: { id: number; name: string }[] }).releases
assert.lengthOf(releases, 1)
const nzb = await client.get(`/api/v1/releases/${releases[0].id}/nzb`)
nzb.assertStatus(200)
assert.include(nzb.header('content-type'), 'application/x-nzb')
const parsed = parseNzb(nzb.text())!
assert.equal(parsed.meta.name, releases[0].name)
assert.deepEqual(parsed.files.map((f) => f.segments.length), [1, 3])
assert.equal(parsed.files[1].segments[2].id, 'Some.Show.S01E01.1080p.WEB.part1.rar-3@test')
})
test('details with files, categories, stats and the Newznab API', async ({ client, assert }) => {
const [g] = await db.table('groups').insert({ name: GROUP, priority: 1 }).returning('id')
const [n] = await db.table('nodes').insert({ name: 'api-node' }).returning('id')
const [r] = await db.table('ranges').insert({ group_id: g.id, start_id: 1, end_id: 1000, next_id: 1, status: 'leased', node_id: n.id }).returning('id')
const indexer = new RangeIndexer({ group: GROUP, start: 1, end: 1000, rules: { ...DEFAULT_RULES, samplePercent: 0, droppedSignatures: [], priorities: { [GROUP]: 1 } } })
indexer.add([...file('Some.Show.S01E01.1080p.WEB.mkv', 'Some.Show.S01E01.1080p.WEB [1/1]', 2, 100), ...file('Other.Movie.2024.2160p.BluRay.x265.mkv', 'Other.Movie.2024.2160p.BluRay.x265 [1/1]', 1, 300)], 1001)
indexer.finish()
await BatchStore.store(Number(r.id), Number(n.id), indexer.take())
// Web API: a category (TV, expanded to its subcategories) and a release's files.
const tv = (await client.get('/api/v1/releases?cat=5000')).body() as { releases: { id: number; name: string; category: number }[] }
assert.deepEqual(tv.releases.map((x) => [x.name, x.category]), [['Some Show S01E01 1080p WEB', 5040]])
const detail = (await client.get(`/api/v1/releases/${tv.releases[0].id}`)).body() as { files: { name: string; parts: number; size: number }[] }
assert.deepEqual(detail.files, [{ ...detail.files[0], name: 'Some.Show.S01E01.1080p.WEB.mkv', parts: 2, size: 1400001 }])
;(await client.get('/api/v1/releases/999999')).assertStatus(404)
// Complete as soon as the counter's files are all found (settling makes it final).
const complete = (await client.get('/api/v1/releases?complete=1')).body() as { releases: unknown[] }
assert.lengthOf(complete.releases, 2)
const stats = (await client.get('/api/v1/stats')).body() as { releases: number; ranges: { total: number }; history: { total: number } }
assert.equal(stats.releases, 2)
assert.equal(stats.ranges.total, 1)
assert.equal(stats.history.total, 1000)
// Newznab: caps, a TV search with season and episode, the NZB; the key when one is set.
const caps = await client.get('/api?t=caps')
assert.include(caps.text(), '')
const search = await client.get('/api?t=tvsearch&q=some show&season=1&ep=1')
assert.include(search.text(), 'Some Show S01E01 1080p WEB')
assert.include(search.text(), '')
assert.notInclude(search.text(), 'Other Movie')
const movie = await client.get('/api?t=movie&cat=2045')
assert.include(movie.text(), 'Other Movie 2024 2160p BluRay x265')
const get = await client.get(`/api?t=get&id=${tv.releases[0].id}`)
assert.include(get.header('content-type'), 'application/x-nzb')
assert.equal(parseNzb(get.text())!.files.length, 1)
assert.include((await client.get('/api?t=get&id=999999')).text(), 'code="300"')
assert.include((await client.get('/api?t=nope')).text(), 'code="202"')
})
test("the prober's findings: inner names in search and Newznab titles, passworded filtered and marked", async ({ client, assert }) => {
const [g] = await db.table('groups').insert({ name: GROUP, priority: 1 }).returning('id')
const [n] = await db.table('nodes').insert({ name: 'api-node' }).returning('id')
const [r] = await db.table('ranges').insert({ group_id: g.id, start_id: 1, end_id: 1000, next_id: 1, status: 'leased', node_id: n.id }).returning('id')
const indexer = new RangeIndexer({ group: GROUP, start: 1, end: 1000, rules: { ...DEFAULT_RULES, samplePercent: 0, droppedSignatures: [], priorities: { [GROUP]: 1 } } })
indexer.add([...file('Some.Show.S01E01.1080p.WEB.mkv', 'Some.Show.S01E01.1080p.WEB [1/1]', 2, 100), ...file('Other.Movie.2024.2160p.BluRay.x265.mkv', 'Other.Movie.2024.2160p.BluRay.x265 [1/1]', 1, 300)], 1001)
indexer.finish()
await BatchStore.store(Number(r.id), Number(n.id), indexer.take())
const show = await db.from('releases').where('name', 'ilike', '%show%').firstOrFail()
const movie = await db.from('releases').where('name', 'ilike', '%movie%').firstOrFail()
// As the prober leaves them: the movie's archive is encrypted, the show holds a differently named file.
await db.from('releases').where('id', movie.id).update({ passworded: true, inspected_at: new Date() })
await db.from('releases').where('id', show.id).update({ passworded: false, inner_name: 'Hidden.Gem.2023.mkv', readable: false, inspected_at: new Date() })
// The name inside, searchable (release_texts, kind file: lib/release/text_kind.ts).
await db.table('release_texts').insert({ release_id: show.id, kind: 2, text: 'Hidden.Gem.2023.mkv' })
await db.table('release_inspections').insert({ release_id: show.id, candidate_id: 1, kind: 'archive', result: JSON.stringify({ yencName: 'x.rar', size: 1, type: 'rar5', names: ['Hidden.Gem.2023.mkv'], entries: 1, passworded: false }) })
const byInner = (await client.get('/api/v1/releases?q=hidden gem')).body() as { releases: { id: number; innerName: string }[] }
assert.deepEqual(byInner.releases.map((x) => [x.id, x.innerName]), [[Number(show.id), 'Hidden.Gem.2023.mkv']])
// Readable by its inner name.
const readable = (await client.get('/api/v1/releases?readable=1')).body() as { releases: { id: number }[] }
assert.lengthOf(readable.releases, 2)
const unlocked = (await client.get('/api/v1/releases?unlocked=1')).body() as { releases: { id: number; passworded: boolean }[] }
assert.deepEqual(unlocked.releases.map((x) => x.id), [Number(show.id)])
const detail = (await client.get(`/api/v1/releases/${show.id}`)).body() as any
assert.equal(detail.innerName, 'Hidden.Gem.2023.mkv')
assert.deepEqual(detail.inspections.map((i: any) => [i.kind, i.names]), [['archive', ['Hidden.Gem.2023.mkv']]])
const key = process.env.NEWZNAB_API_KEY
const feed = (await client.get(`/api?t=search&q=&apikey=${key ?? ''}`)).text()
assert.include(feed, 'Hidden.Gem.2023')
assert.match(feed, /Other Movie 2024[^]*?name="password" value="1"/)
})
test('a known password: in the NZB (the most trusted source first), the details and the list', async ({ client, assert }) => {
const [g] = await db.table('groups').insert({ name: GROUP, priority: 1 }).returning('id')
const [n] = await db.table('nodes').insert({ name: 'api-node' }).returning('id')
const [r] = await db.table('ranges').insert({ group_id: g.id, start_id: 1, end_id: 1000, next_id: 1, status: 'leased', node_id: n.id }).returning('id')
const indexer = new RangeIndexer({ group: GROUP, start: 1, end: 1000, rules: { ...DEFAULT_RULES, samplePercent: 0, droppedSignatures: [], priorities: { [GROUP]: 1 } } })
indexer.add(file('Locked.Movie.2024.1080p.BluRay.x264.part1.rar', 'Locked.Movie [1/1]', 2, 100), 1001)
indexer.finish()
await BatchStore.store(Number(r.id), Number(n.id), indexer.take())
const movie = await db.from('releases').firstOrFail()
await db.from('releases').where('id', movie.id).update({ passworded: true })
await db.table('release_passwords').multiInsert([
{ release_id: movie.id, password: 'from-text', source: 'spot-text' },
{ release_id: movie.id, password: 'from-nzb', source: 'nzb' },
])
const nzb = parseNzb((await client.get(`/api/v1/releases/${movie.id}/nzb`)).text())!
assert.equal(nzb.meta.password, 'from-nzb')
const detail = (await client.get(`/api/v1/releases/${movie.id}`)).body() as any
assert.deepEqual(detail.passwords, [{ password: 'from-nzb', source: 'nzb' }, { password: 'from-text', source: 'spot-text' }])
const list = (await client.get('/api/v1/releases?q=locked movie')).body() as { releases: { passwordKnown: boolean }[] }
assert.isTrue(list.releases[0].passwordKnown)
})
})