release_passwords (from spots: their NZB meta, title or text) was only read by the judge. The NZB now carries the most trusted one as <meta type="password">, which SABnzbd and NZBGet use to unpack; a spot's own NZB gets it added when its meta has none. The release details list all known passwords with their source, the list marks "Password known", and the web interface shows them instead of "not in the index". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
140 lines
10 KiB
TypeScript
140 lines
10 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 { 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 <p@x>', '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(), '<category id="5000" name="TV">')
|
|
const search = await client.get('/api?t=tvsearch&q=some show&season=1&ep=1')
|
|
assert.include(search.text(), '<title>Some Show S01E01 1080p WEB</title>')
|
|
assert.include(search.text(), '<newznab:attr name="category" value="5040"/>')
|
|
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() })
|
|
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, '<title>Hidden.Gem.2023</title>')
|
|
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)
|
|
})
|
|
})
|