Rebuild: phase 1 modules from the current app, ordered by pipeline stage
lib/nntp (XOVER client, Xref, GROUP), lib/headers (subjects, file keys, segments from overview lines; the yEnc path stays behind for phase 2), lib/release (identity, random names, now in lib), lib/files (aggregator, volume names), lib/rules (posting tools, owner by priority, release verdicts), lib/chunks (release chunk codec). Their unit tests came along; new ones for codec, rules and volumes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
96f0ade10e
commit
d773c91ad0
26 files changed
+1486
No files matched your search
@@ -0,0 +1,18 @@
|
||||
import { test } from '@japa/runner'
|
||||
import { decodeChunk, encodeChunk, type ChunkFile } from '#lib/chunks/index'
|
||||
|
||||
test.group('chunks / codec', () => {
|
||||
test('round-trips files with their segments, part numbers when not 1..N', ({ assert }) => {
|
||||
const files: ChunkFile[] = [
|
||||
{ name: 'a.part1.rar', poster: 'p <p@x>', subject: 'A [1/2] - "a.part1.rar" yEnc', postedAt: 1_700_000_000_000, groups: ['alt.binaries.test'], segments: [[1, 'one@x', 700_000], [2, 'two@x', 1234]] },
|
||||
{ name: 'a.par2', poster: 'p <p@x>', subject: 'A [2/2] - "a.par2" yEnc', postedAt: null, groups: ['alt.binaries.test', 'alt.binaries.x'], segments: [[2, 'gap@x', 10], [5, 'gap2@x', 11]] },
|
||||
]
|
||||
const data = encodeChunk(files)
|
||||
assert.equal(data[0], 1)
|
||||
assert.deepEqual(decodeChunk(data), files)
|
||||
})
|
||||
|
||||
test('refuses an unknown version', ({ assert }) => {
|
||||
assert.throws(() => decodeChunk(Buffer.from([9, 0])), /Unknown chunk version 9/)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
import { test } from '@japa/runner'
|
||||
import { FileAggregator, type AggregatableSegment } from '#lib/files/index'
|
||||
|
||||
const seg = (fileKey: string, number: number, totalParts: number, messageId = `${fileKey}-${number}@test`): AggregatableSegment => ({
|
||||
fileKey,
|
||||
number,
|
||||
totalParts,
|
||||
messageId,
|
||||
bytes: 100,
|
||||
})
|
||||
|
||||
test.group('aggregate / FileAggregator', () => {
|
||||
test('completes a file once every part arrived, across batches and out of order', ({ assert }) => {
|
||||
const aggregator = new FileAggregator()
|
||||
assert.deepEqual(aggregator.add([seg('a', 2, 3), seg('b', 1, 1)]).map((f) => f.fileKey), ['b'])
|
||||
assert.equal(aggregator.openFiles, 1)
|
||||
const [done] = aggregator.add([seg('a', 3, 3), seg('a', 1, 3)])
|
||||
assert.equal(done.fileKey, 'a')
|
||||
assert.deepEqual(done.segments.map((s) => s.number), [1, 2, 3])
|
||||
assert.equal(aggregator.openFiles, 0)
|
||||
})
|
||||
|
||||
test('keeps the first article of a part number (a repost) and drops impossible numbers', ({ assert }) => {
|
||||
const aggregator = new FileAggregator()
|
||||
const [done] = aggregator.add([seg('a', 1, 2, 'first@test'), seg('a', 1, 2, 'repost@test'), seg('a', 5, 2), seg('a', 2, 2)])
|
||||
assert.deepEqual(done.segments.map((s) => s.messageId), ['first@test', 'a-2@test'])
|
||||
assert.deepEqual(aggregator.evict().map((s) => s.number), [5])
|
||||
})
|
||||
|
||||
test('gives up idle files as fragments, all of them at the end', ({ assert }) => {
|
||||
const aggregator = new FileAggregator()
|
||||
aggregator.add([seg('old', 1, 3)])
|
||||
aggregator.add([seg('new', 1, 3)])
|
||||
aggregator.add([seg('new', 2, 3)])
|
||||
assert.deepEqual(aggregator.evict(2).map((s) => s.fileKey), ['old'])
|
||||
assert.deepEqual(aggregator.evict(0).map((s) => s.number), [1, 2])
|
||||
assert.equal(aggregator.openFiles, 0)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,65 @@
|
||||
import { test } from '@japa/runner'
|
||||
import { fileKey, parseSubject } from '#lib/headers/index'
|
||||
import { parseXrefGroups } from '#lib/nntp/index'
|
||||
|
||||
test.group('subject / parseSubject', () => {
|
||||
test('parses file counter, filename and segment counter', ({ assert }) => {
|
||||
assert.deepEqual(parseSubject('. - [09/51] - "obrMkrDeLmbhaouwYO34.part08.rar" yEnc (56/274)'), {
|
||||
name: 'obrMkrDeLmbhaouwYO34.part08.rar',
|
||||
segment: 56,
|
||||
totalSegments: 274,
|
||||
fileIndex: 9,
|
||||
fileTotal: 51,
|
||||
base: '. - [09/51] - "obrMkrDeLmbhaouwYO34.part08.rar" yEnc',
|
||||
})
|
||||
})
|
||||
|
||||
test('handles a file size after the segment counter', ({ assert }) => {
|
||||
const subject = '[8/9] - "x.tar.zst.vol063+064.par2" yEnc (37/72) 51315352'
|
||||
const parsed = parseSubject(subject)!
|
||||
assert.equal(parsed.segment, 37)
|
||||
assert.equal(parsed.totalSegments, 72)
|
||||
assert.equal(parsed.base, '[8/9] - "x.tar.zst.vol063+064.par2" yEnc 51315352')
|
||||
})
|
||||
|
||||
test('ignores counters inside the filename', ({ assert }) => {
|
||||
const parsed = parseSubject('"Show (1/2) Special.mkv" yEnc (3/10)')!
|
||||
assert.equal(parsed.name, 'Show (1/2) Special.mkv')
|
||||
assert.equal(parsed.segment, 3)
|
||||
assert.equal(parsed.totalSegments, 10)
|
||||
})
|
||||
|
||||
test('the base is equal for all segments of one file', ({ assert }) => {
|
||||
const a = parseSubject('[1/9] - "a.tar.zst" yEnc (253/1000) 716554110')!
|
||||
const b = parseSubject('[1/9] - "a.tar.zst" yEnc (254/1000) 716554110')!
|
||||
assert.equal(a.base, b.base)
|
||||
})
|
||||
|
||||
test('returns null for obfuscated or invalid subjects', ({ assert }) => {
|
||||
assert.isNull(parseSubject('GZFDJc0hB3Y0FwwAznkk'))
|
||||
assert.isNull(parseSubject('"name.rar" yEnc'))
|
||||
assert.isNull(parseSubject('"name.rar" yEnc (0/5)'))
|
||||
assert.isNull(parseSubject('"name.rar" yEnc (6/5)'))
|
||||
assert.isNull(parseSubject('"" yEnc (1/5)'))
|
||||
})
|
||||
})
|
||||
|
||||
test.group('subject / fileKey', () => {
|
||||
test('differs per poster and per segment count, is stable otherwise', ({ assert }) => {
|
||||
const key = fileKey('poster@a', '"x.rar" yEnc', 10)
|
||||
assert.lengthOf(key, 40)
|
||||
assert.equal(key, fileKey('poster@a', '"x.rar" yEnc', 10))
|
||||
assert.notEqual(key, fileKey('poster@b', '"x.rar" yEnc', 10))
|
||||
assert.notEqual(key, fileKey('poster@a', '"x.rar" yEnc', 11))
|
||||
})
|
||||
})
|
||||
|
||||
test.group('nntp / parseXrefGroups', () => {
|
||||
test('extracts crossposted groups', ({ assert }) => {
|
||||
assert.deepEqual(
|
||||
parseXrefGroups('news.eweka.nl alt.binaries.nl:35801424104 alt.binaries.test:27275430913'),
|
||||
['alt.binaries.nl', 'alt.binaries.test']
|
||||
)
|
||||
assert.deepEqual(parseXrefGroups(undefined), [])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
import { test } from '@japa/runner'
|
||||
import { parseGroupResponse } from '#lib/nntp/index'
|
||||
|
||||
test.group('nntp / parseGroupResponse', () => {
|
||||
test('parses the reply from news.eweka.nl', ({ assert }) => {
|
||||
// Captured 2026-09-27. count == last - first + 1, so the field order is standard.
|
||||
assert.deepEqual(parseGroupResponse('211 27275164317 266599 27275430915 alt.binaries.test'), {
|
||||
count: 27275164317n,
|
||||
first: 266599n,
|
||||
last: 27275430915n,
|
||||
name: 'alt.binaries.test',
|
||||
})
|
||||
})
|
||||
|
||||
test('rejects non-211 replies', ({ assert }) => {
|
||||
assert.throws(() => parseGroupResponse('411 No such group'), /Unexpected GROUP response/)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,106 @@
|
||||
import { test } from '@japa/runner'
|
||||
import net from 'node:net'
|
||||
import { deflateSync } from 'node:zlib'
|
||||
import { XoverClient, parseOverviews } from '#lib/nntp/index'
|
||||
import { unzipSync } from 'node:zlib'
|
||||
|
||||
const LINES = [
|
||||
'101\tShow.S01E01 - "a.mkv" yEnc (1/2)\tposter <p@x>\tMon, 05 Oct 2026 10:00:00 +0000\t<one@test>\t\t700000\t5000\tXref: news.example a.b.test:101',
|
||||
'102\tShow.S01E01 - "a.mkv" yEnc (2/2)\tposter <p@x>\tMon, 05 Oct 2026 10:00:01 +0000\t<two@test>\t\t650000\t4600\tXref: news.example a.b.test:102 a.b.other:7',
|
||||
]
|
||||
|
||||
/** A tiny NNTP server: plain replies, gzip after XFEATURE, 420 for 1-1, replies split into small chunks. */
|
||||
function server(): Promise<{ port: number; close: () => void }> {
|
||||
return new Promise((resolve) => {
|
||||
const srv = net.createServer((sock) => {
|
||||
let gzip = false
|
||||
const send = (data: Buffer) => {
|
||||
for (let i = 0; i < data.length; i += 7) sock.write(data.subarray(i, i + 7))
|
||||
}
|
||||
sock.write('200 welcome\r\n')
|
||||
let pending = ''
|
||||
sock.on('data', (chunk) => {
|
||||
pending += chunk.toString()
|
||||
let at: number
|
||||
while ((at = pending.indexOf('\r\n')) !== -1) {
|
||||
const line = pending.slice(0, at)
|
||||
pending = pending.slice(at + 2)
|
||||
if (line.startsWith('AUTHINFO USER')) sock.write('381 more\r\n')
|
||||
else if (line.startsWith('AUTHINFO PASS')) sock.write('281 ok\r\n')
|
||||
else if (line === 'XFEATURE COMPRESS GZIP') (gzip = true), sock.write('290 Feature Enabled\r\n')
|
||||
else if (line.startsWith('GROUP')) sock.write('211 2 101 102 a.b.test\r\n')
|
||||
else if (line === 'XOVER 1-1') sock.write('420 No Articles Selected\r\n')
|
||||
else if (line.startsWith('XOVER')) {
|
||||
const body = Buffer.from(LINES.join('\r\n') + '\r\n')
|
||||
if (gzip) {
|
||||
sock.write('224 Overview Information Follows [COMPRESS=GZIP]\r\n')
|
||||
send(Buffer.concat([deflateSync(body), Buffer.from('.\r\n')]))
|
||||
} else {
|
||||
sock.write('224 Overview Information Follows\r\n')
|
||||
send(Buffer.concat([body, Buffer.from('.\r\n')]))
|
||||
}
|
||||
} else if (line === 'QUIT') sock.end('205 bye\r\n')
|
||||
}
|
||||
})
|
||||
})
|
||||
srv.listen(0, '127.0.0.1', () => resolve({ port: (srv.address() as net.AddressInfo).port, close: () => srv.close() }))
|
||||
})
|
||||
}
|
||||
|
||||
test.group('nntp / XoverClient', () => {
|
||||
test('parses overview lines into the fields nntp-js gives', ({ assert }) => {
|
||||
const [[number, fields]] = parseOverviews(LINES[0] + '\r\n')
|
||||
assert.equal(number, 101)
|
||||
assert.equal(fields['message-id'], '<one@test>')
|
||||
assert.equal(fields[':bytes'], '700000')
|
||||
assert.equal(fields.xref, 'news.example a.b.test:101')
|
||||
})
|
||||
|
||||
for (const compress of [false, true]) {
|
||||
test(`fetches overviews ${compress ? 'gzip-compressed' : 'plain'}, and an empty range`, async ({ assert }) => {
|
||||
const { port, close } = await server()
|
||||
const client = await XoverClient.connect({ host: '127.0.0.1', port, user: 'u', password: 'p', compress })
|
||||
try {
|
||||
assert.equal(client.compressed, compress)
|
||||
assert.deepEqual(await client.group('a.b.test'), { count: 2, low: 101, high: 102 })
|
||||
const overviews = await client.xover(101, 102)
|
||||
assert.deepEqual(overviews.map(([n]) => n), [101, 102])
|
||||
assert.equal(overviews[1][1].subject, 'Show.S01E01 - "a.mkv" yEnc (2/2)')
|
||||
assert.deepEqual(await client.xover(1, 1), [])
|
||||
assert.lengthOf(await client.xover(101, 102), 2)
|
||||
} finally {
|
||||
client.close()
|
||||
close()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
test('idle time between commands does not count against the reply timeout', async ({ assert }) => {
|
||||
const { port, close } = await server()
|
||||
const client = await XoverClient.connect({ host: '127.0.0.1', port, user: 'u', password: 'p', compress: true, timeoutMs: 200 })
|
||||
try {
|
||||
await client.group('a.b.test')
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
assert.lengthOf(await client.xover(101, 102), 2)
|
||||
} finally {
|
||||
client.close()
|
||||
close()
|
||||
}
|
||||
})
|
||||
|
||||
for (const compress of [false, true]) {
|
||||
test(`hands out overviews as one zlib stream (${compress ? 'as the server sent it' : 'deflated here'})`, async ({ assert }) => {
|
||||
const { port, close } = await server()
|
||||
const client = await XoverClient.connect({ host: '127.0.0.1', port, user: 'u', password: 'p', compress })
|
||||
try {
|
||||
await client.group('a.b.test')
|
||||
const raw = await client.xoverRaw(101, 102)
|
||||
assert.deepEqual(parseOverviews(unzipSync(raw).toString('utf8')).map(([n]) => n), [101, 102])
|
||||
assert.lengthOf(parseOverviews(unzipSync(await client.xoverRaw(1, 1)).toString('utf8')), 0)
|
||||
} finally {
|
||||
client.close()
|
||||
close()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,169 @@
|
||||
import { test } from '@japa/runner'
|
||||
import { fileStem, releaseIdentity, subjectRemainder } from '#lib/release/index'
|
||||
import { looksLikeFilename, weakFileKey } from '#lib/headers/index'
|
||||
|
||||
test.group('release / fileStem', () => {
|
||||
test('reduces all files of a set to the same stem', ({ assert }) => {
|
||||
const names = [
|
||||
'1790546808_nicovideo_jp_watch_sm26679715.tar.zst',
|
||||
'1790546808_nicovideo_jp_watch_sm26679715.tar.zst.par2',
|
||||
'1790546808_nicovideo_jp_watch_sm26679715.tar.zst.vol03+04.par2',
|
||||
]
|
||||
assert.deepEqual(new Set(names.map(fileStem)), new Set(['1790546808_nicovideo_jp_watch_sm26679715']))
|
||||
})
|
||||
|
||||
test('handles rar, split and par2 volume naming', ({ assert }) => {
|
||||
assert.equal(fileStem('lJzuoIKA3T8xjzAD3vqtw0.part036.rar'), 'lJzuoIKA3T8xjzAD3vqtw0')
|
||||
assert.equal(fileStem('Some.Movie.2024.r07'), 'Some.Movie.2024')
|
||||
assert.equal(fileStem('Some.Movie.2024.mkv.001'), 'Some.Movie.2024')
|
||||
assert.equal(fileStem('Some.Movie.2024.2160p.WEB-DL.H.265.mkv'), 'Some.Movie.2024.2160p.WEB-DL.H.265')
|
||||
assert.equal(fileStem('Some.Movie.2024.H.264.nzb'), 'Some.Movie.2024.H.264')
|
||||
assert.equal(fileStem('Some.Movie.2024.vol127-128.par2'), 'Some.Movie.2024')
|
||||
assert.equal(fileStem('Some.Movie.2024.nfo'), 'Some.Movie.2024')
|
||||
assert.equal(fileStem('00-artist-album-web-2026.m3u'), '00-artist-album-web-2026')
|
||||
assert.equal(fileStem('f6dd6b156692a52b.bin'), 'f6dd6b156692a52b')
|
||||
})
|
||||
|
||||
test('never reduces a name to nothing', ({ assert }) => {
|
||||
assert.equal(fileStem('.par2'), '.par2')
|
||||
})
|
||||
})
|
||||
|
||||
test.group('release / subjectRemainder', () => {
|
||||
test('is empty when the subject only has counters, filename and size', ({ assert }) => {
|
||||
assert.equal(subjectRemainder('[1/4] - "x.tar.zst" yEnc 15411409'), '')
|
||||
assert.equal(subjectRemainder('. - [00/88] - "60SHkTQSvn5RZGExwju2.nzb" yEnc'), '')
|
||||
})
|
||||
|
||||
test('keeps a release name written outside the quotes', ({ assert }) => {
|
||||
assert.equal(
|
||||
subjectRemainder('Some.Show.S01E02.1080p [03/40] - "abc.part02.rar" yEnc'),
|
||||
'Some Show S01E02 1080p'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test.group('release / releaseIdentity', () => {
|
||||
test('groups a par2 set with its archive by stem, per poster', ({ assert }) => {
|
||||
const base = { poster: 'p', weak: false }
|
||||
const a = releaseIdentity({ ...base, subject: '[1/4] - "x.tar.zst" yEnc 1', name: 'x.tar.zst' })!
|
||||
const b = releaseIdentity({ ...base, subject: '[3/4] - "x.tar.zst.vol01+02.par2" yEnc 2', name: 'x.tar.zst.vol01+02.par2' })!
|
||||
assert.equal(a.key, b.key)
|
||||
assert.equal(a.name, 'x')
|
||||
assert.equal(a.fileTotal, 4)
|
||||
const other = releaseIdentity({ ...base, poster: 'q', subject: '[1/4] - "x.tar.zst" yEnc 1', name: 'x.tar.zst' })!
|
||||
assert.notEqual(a.key, other.key)
|
||||
})
|
||||
|
||||
test('a par2 index posted without [i/M] counter joins its release', ({ assert }) => {
|
||||
const base = { poster: 'nicovideo_archiver', weak: false }
|
||||
const archive = releaseIdentity({ ...base, subject: '[1/6] - "x.tar.zst" yEnc 105054576', name: 'x.tar.zst' })!
|
||||
const index = releaseIdentity({ ...base, subject: '"x.tar.zst.par2" yEnc 3100', name: 'x.tar.zst.par2' })!
|
||||
assert.equal(index.key, archive.key)
|
||||
assert.equal(archive.fileTotal, 6)
|
||||
assert.isUndefined(index.fileTotal)
|
||||
})
|
||||
|
||||
test('uses the subject text when it names the release', ({ assert }) => {
|
||||
const base = { poster: 'p', weak: false }
|
||||
const a = releaseIdentity({ ...base, subject: 'My.Release [01/40] - "abc.part01.rar" yEnc', name: 'abc.part01.rar' })!
|
||||
const b = releaseIdentity({ ...base, subject: 'My.Release [40/40] - "abc.par2" yEnc', name: 'abc.par2' })!
|
||||
assert.equal(a.key, b.key)
|
||||
assert.equal(a.name, 'My Release')
|
||||
})
|
||||
|
||||
test('obfuscated volumes group by stem regardless of the random poster', ({ assert }) => {
|
||||
const a = releaseIdentity({ poster: '3Uzx7TTSFpLzz@ngPost.com', subject: '"lJzu.part033.rar" yEnc', name: 'lJzu.part033.rar', weak: true })!
|
||||
const b = releaseIdentity({ poster: 'bGLd1oaXRdrK1@ngPost.com', subject: '"lJzu.part034.rar" yEnc', name: 'lJzu.part034.rar', weak: true })!
|
||||
assert.equal(a.key, b.key)
|
||||
})
|
||||
|
||||
test('random extensionless names cannot be grouped by name', ({ assert }) => {
|
||||
const name = '204b914a977e5f891590ff1099c7a8caf1a0be537bfa5bf7f3a8b1213231'
|
||||
assert.isNull(releaseIdentity({ poster: 'x', subject: `"${name}" yEnc`, name, weak: true }))
|
||||
})
|
||||
})
|
||||
|
||||
test.group('subject / weakFileKey', () => {
|
||||
test('ignores random per-article names, keeps real filenames', ({ assert }) => {
|
||||
assert.isFalse(looksLikeFilename('204b914a977e5f891590ff1099c7a8caf1a0be537bfa5bf7f3a8b1213231'))
|
||||
assert.isTrue(looksLikeFilename('lJzuoIKA3T8xjzAD3vqtw0.part033.rar'))
|
||||
// Same file, different random names per article: same key.
|
||||
assert.equal(weakFileKey('aaaa1111', 5000, 7), weakFileKey('bbbb2222', 5000, 7))
|
||||
// Equal-sized volumes with real names: different keys.
|
||||
assert.notEqual(weakFileKey('x.part01.rar', 5000, 7), weakFileKey('x.part02.rar', 5000, 7))
|
||||
assert.notEqual(weakFileKey('aaaa1111', 5000, 7), weakFileKey('aaaa1111', 5001, 7))
|
||||
})
|
||||
})
|
||||
|
||||
test.group('release / rarReleaseIdentity', () => {
|
||||
test('groups volumes by the shared top-level directory', async ({ assert }) => {
|
||||
const { rarReleaseIdentity } = await import('#lib/release/index')
|
||||
const first = rarReleaseIdentity([
|
||||
{ name: 'LASZR_-_DYING_ALIVE-(PRO094)-WEB-2026-DIM/00-cover.jpg', size: 589382 },
|
||||
{ name: 'LASZR_-_DYING_ALIVE-(PRO094)-WEB-2026-DIM/01-track.flac', size: 30_000_000 },
|
||||
])!
|
||||
const later = rarReleaseIdentity([{ name: 'LASZR_-_DYING_ALIVE-(PRO094)-WEB-2026-DIM/03-track.flac', size: 29_000_000 }])!
|
||||
assert.equal(first.key, later.key)
|
||||
assert.equal(first.name, 'LASZR_-_DYING_ALIVE-(PRO094)-WEB-2026-DIM')
|
||||
assert.equal(first.source, 'rar')
|
||||
})
|
||||
|
||||
test('without a directory, volumes of one file share its first entry', async ({ assert }) => {
|
||||
const { rarReleaseIdentity } = await import('#lib/release/index')
|
||||
const a = rarReleaseIdentity([{ name: 'Some.Show.S01E01.1080p.mkv', size: 3_000_000_000 }])!
|
||||
const b = rarReleaseIdentity([{ name: 'Some.Show.S01E01.1080p.mkv', size: 3_000_000_000 }])!
|
||||
assert.equal(a.key, b.key)
|
||||
assert.equal(a.name, 'Some.Show.S01E01.1080p')
|
||||
assert.isNull(rarReleaseIdentity([]))
|
||||
})
|
||||
})
|
||||
|
||||
test.group('release / namesRelease', () => {
|
||||
test('random per-file tags and poster ids are not a release name', async ({ assert }) => {
|
||||
const { namesRelease, subjectRemainder, releaseIdentity } = await import('#lib/release/index')
|
||||
const subject = 'elys-de81bcabef5faf6a - [Nyh5Q07Y] "JkpNIFg7En50zU1U" yEnc'
|
||||
assert.isFalse(namesRelease(subjectRemainder(subject)))
|
||||
const a = releaseIdentity({ poster: 'elys', subject, name: 'JkpNIFg7En50zU1U', weak: false })!
|
||||
assert.equal(a.source, 'stem')
|
||||
assert.isTrue(namesRelease(subjectRemainder('Some.Show.S01E02.1080p [03/40] - "abc.part02.rar" yEnc')))
|
||||
assert.isTrue(namesRelease('My Release'))
|
||||
})
|
||||
})
|
||||
|
||||
test.group('release / post id', () => {
|
||||
test('groups random-named files of one post by poster, post id and file count', async ({ assert }) => {
|
||||
const { releaseIdentity, isPostToken } = await import('#lib/release/index')
|
||||
const base = { poster: 'jW344UtC8R2bZEegrVLz@x', weak: false }
|
||||
const a = releaseIdentity({ ...base, subject: '83110629-n [10/18] - "eiae6fulpolas2adfts6o8643n3e6j6ct8d3219aau0jc33ol9r50tohncgr46evpl0dqoqktfb7k" yEnc', name: 'eiae6fulpolas2adfts6o8643n3e6j6ct8d3219aau0jc33ol9r50tohncgr46evpl0dqoqktfb7k' })!
|
||||
const b = releaseIdentity({ ...base, subject: '83110629-n [01/18] - "2ef0vm4kb618nv17kn0qsb0j1ofh7pgqtk5a3v5lkqqf1f7g6ed0" yEnc', name: '2ef0vm4kb618nv17kn0qsb0j1ofh7pgqtk5a3v5lkqqf1f7g6ed0' })!
|
||||
assert.equal(a.key, b.key)
|
||||
assert.equal(a.source, 'post')
|
||||
assert.equal(a.fileTotal, 18)
|
||||
const other = releaseIdentity({ ...base, subject: '83110630-n [01/18] - "x" yEnc', name: 'x' })!
|
||||
assert.notEqual(a.key, other.key)
|
||||
|
||||
assert.isTrue(isPostToken('83110629 n'))
|
||||
assert.isFalse(isPostToken('PRiVATE'))
|
||||
assert.isFalse(isPostToken(''))
|
||||
// Without a counter there is nothing to bound the post: fall back to the stem.
|
||||
assert.equal(releaseIdentity({ ...base, subject: '83110629-n - "x.bin" yEnc', name: 'x.bin' })!.source, 'stem')
|
||||
})
|
||||
})
|
||||
|
||||
test.group('release / par2 names', () => {
|
||||
test('names a season pack after what its episodes share', async ({ assert }) => {
|
||||
const { par2ReleaseName } = await import('#lib/release/index')
|
||||
const eps = Array.from({ length: 20 }, (_, i) =>
|
||||
`SEAL.Team.S03E${String(i + 1).padStart(2, '0')}.1080p.AMZN.WEB-DL.AAC2.0.H.265.DUAL-nitrato.mkv`)
|
||||
assert.equal(par2ReleaseName(eps), 'SEAL.Team.S03.1080p.AMZN.WEB-DL.AAC2.0.H.265.DUAL-nitrato')
|
||||
// Movie plus sample and nfo: the movie's (longest) name.
|
||||
assert.equal(
|
||||
par2ReleaseName(['Some.Movie.2024.1080p.BluRay.x264-GRP.mkv', 'Some.Movie.2024.1080p.BluRay.x264-GRP.nfo', 'sample.mkv']),
|
||||
'Some.Movie.2024.1080p.BluRay.x264-GRP'
|
||||
)
|
||||
// Numbered parts: the shared name without the part number.
|
||||
assert.equal(par2ReleaseName(['Album.Name-2024-GRP.part01.rar', 'Album.Name-2024-GRP.part02.rar']), 'Album.Name-2024-GRP')
|
||||
assert.equal(par2ReleaseName(['only.one.file.2024.mkv']), 'only.one.file.2024')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,56 @@
|
||||
import { test } from '@japa/runner'
|
||||
import { DEFAULT_RULES, RuleSet, ownerOf, uploadTool, explored } from '#lib/rules/index'
|
||||
import { sevenZipVolume } from '#lib/files/index'
|
||||
|
||||
const rules = new RuleSet({ ...DEFAULT_RULES, samplePercent: 0, priorities: {}, droppedSignatures: ['nyuu\talt.binaries.misc'] })
|
||||
const file = (name: string, totalParts: number, firstMessageId: string, group = 'alt.binaries.ath') => ({ name, totalParts, groups: [group], firstMessageId })
|
||||
const NGPOST = 'b94268bc702040f7b811d5318f727e4f@ngPost'
|
||||
const NYUU = 'FmFwIaTpGgUiSfYcMwFlMeRo-1790637260478@nyuu'
|
||||
|
||||
test.group('rules / owner', () => {
|
||||
test('a crosspost belongs to the indexed group with the lowest priority, whatever the Xref order', ({ assert }) => {
|
||||
const priorities = { 'alt.binaries.boneless': 1, 'alt.binaries.ath': 3, 'alt.binaries.added.later': 1001 }
|
||||
assert.equal(ownerOf(['alt.binaries.ath', 'alt.binaries.boneless'], priorities), 'alt.binaries.boneless')
|
||||
assert.equal(ownerOf(['alt.binaries.added.later', 'alt.binaries.ath'], priorities), 'alt.binaries.ath')
|
||||
assert.equal(ownerOf(['alt.binaries.not.indexed'], priorities), null)
|
||||
})
|
||||
})
|
||||
|
||||
test.group('rules / verdicts', () => {
|
||||
test('a readable file keeps its whole release', ({ assert }) => {
|
||||
assert.deepEqual(rules.judge('k', [file('Hs7dKq2LmZ9xWv3RtYp1Qa.part01.rar', 50, NGPOST), file('Some.Show.S01E01.1080p.nfo', 1, NGPOST)]), { rule: null })
|
||||
})
|
||||
|
||||
test('random names: per-article with few parts, garbage by tool or signature, else kept', ({ assert }) => {
|
||||
assert.include(rules.judge('k', [file('Hs7dKq2LmZ9xWv3RtYp1Qa', 2, NYUU)]), { rule: 'per-article', key: 'nyuu' })
|
||||
assert.include(rules.judge('k', [file('Hs7dKq2LmZ9xWv3RtYp1Qa.part01.rar', 50, NGPOST)]), { rule: 'garbage', key: 'ngPost alt.binaries.ath' })
|
||||
assert.include(rules.judge('k', [file('Hs7dKq2LmZ9xWv3RtYp1Qa.part01.rar', 50, NYUU, 'alt.binaries.misc')]), { rule: 'garbage', key: 'nyuu alt.binaries.misc' })
|
||||
assert.deepEqual(rules.judge('k', [file('Hs7dKq2LmZ9xWv3RtYp1Qa.part01.rar', 50, NYUU)]), { rule: null })
|
||||
})
|
||||
})
|
||||
|
||||
test.group('rules / upload tools', () => {
|
||||
test('posting tools are told apart by their Message-IDs', ({ assert }) => {
|
||||
assert.equal(uploadTool(NYUU), 'nyuu')
|
||||
assert.equal(uploadTool(NGPOST), 'ngPost')
|
||||
assert.equal(uploadTool('part1of69.lOlWzvSHabJCBTVrs0ts@AdultPoster.local'), 'partNofM')
|
||||
assert.equal(uploadTool('DLugnv3ZrVBv8Z9I69nU_19o112@JBinUp.local'), 'JBinUp')
|
||||
assert.equal(uploadTool('t3Y4mV92YCp0McxDc5@zLZlsw.RYqes'), 'random')
|
||||
assert.equal(uploadTool(null), 'other')
|
||||
})
|
||||
|
||||
test('the sampled share is stable per key and about the percentage', ({ assert }) => {
|
||||
assert.equal(explored('some-release', 2), explored('some-release', 2))
|
||||
const kept = Array.from({ length: 10_000 }, (_, i) => explored(`release-${i}`, 2)).filter(Boolean).length
|
||||
assert.isAbove(kept, 150)
|
||||
assert.isBelow(kept, 250)
|
||||
})
|
||||
})
|
||||
|
||||
test.group('files / volumes', () => {
|
||||
test('7z volume numbers', ({ assert }) => {
|
||||
assert.equal(sevenZipVolume('x.7z.001'), 1)
|
||||
assert.equal(sevenZipVolume('x.7z'), 0)
|
||||
assert.isNull(sevenZipVolume('x.rar'))
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user