Files
usenet-indexer/tests/unit/content.spec.ts
T
Daan MeijerandClaude Opus 5.5 be8a3d4580 Spotnet import, header backfill, staging and counter grouping, faster search
Spotnet (CONCEPT D29):
- Index free.pt fully and import spots with their descriptions, categories
  and NZBs; a spot renames its release, since Spotnet names are more
  trustworthy than names derived from subjects.
- Find passwords in spot descriptions too, and tell "needs a password we
  have" from "needs a password we don't have".

Content inspection:
- List the contents of 7z (.7z.001) archives, SFV sets, nicovideo uploads
  and other archive-like files; mark yEncBin .bin containers as encrypted.
- Take real names from yEnc headers for randomly named files, and merge
  uploads that were split across posts.

Storage (D31, D32):
- Store segment lists zstd-compressed behind the FileStore accessor.
- group:backfill indexes older headers at low priority and pauses when
  the NVMe or HDD part of the database gets too large.

Grouping (D35, D36):
- Lone header segments wait in staged_segments until a second segment of
  their file arrives, which drops the per-article junk binaries.
- Counter grouping joins random-subject uploads by poster, [i/M] counter,
  part count and day.

Search and maintenance (D33, D34):
- A trigram-indexed search_text column, smaller indexes, estimated stats.
- Passes that clean up nonsense names; weekly index maintenance; GC budgets
  per kind.
- Web UI: grey out stale results while a search runs and cancel superseded
  requests. Count only up to 1,000 matches for the page, which made filter
  clicks take seconds (Newznab keeps the 10,000 cap).

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

55 lines
3.0 KiB
TypeScript

import { test } from '@japa/runner'
import yencode from 'yencode'
import { sniffContentType } from '#lib/content/index'
import { crc32Hex, decodeSegment, YencError } from '#lib/yenc/index'
test.group('content / sniffContentType', () => {
test('recognises containers and media by magic bytes', ({ assert }) => {
const magic = (hex: string, pad = 16) => Buffer.concat([Buffer.from(hex, 'hex'), Buffer.alloc(pad)])
assert.equal(sniffContentType(Buffer.concat([Buffer.from('nzbe'), Buffer.alloc(40)])), 'nzbe')
assert.equal(sniffContentType(magic('5041523200504b54')), 'par2')
assert.equal(sniffContentType(magic('526172211a070100')), 'rar5')
assert.equal(sniffContentType(magic('526172211a0700')), 'rar4')
assert.equal(sniffContentType(magic('1a45dfa3')), 'matroska')
assert.equal(sniffContentType(magic('0000001866747970')), 'mp4')
assert.equal(sniffContentType(magic('28b52ffd')), 'zstd')
assert.equal(sniffContentType(Buffer.from('<?xml version="1.0"?>\n<!DOCTYPE nzb PUBLIC "x">')), 'nzb')
assert.isNull(sniffContentType(Buffer.from('plain text')))
assert.equal(sniffContentType(magic('ffd8ffe0')), 'jpeg')
assert.equal(sniffContentType(magic('89504e470d0a1a0a')), 'png')
assert.equal(sniffContentType(Buffer.concat([Buffer.from('ID3'), Buffer.alloc(16)])), 'mp3')
assert.equal(sniffContentType(Buffer.concat([Buffer.from('fLaC'), Buffer.alloc(16)])), 'flac')
assert.equal(sniffContentType(Buffer.from('RIFF\x00\x00\x00\x00AVI LIST', 'latin1')), 'avi')
// UDF (Blu-ray) volume recognition sequence at 32 KiB
const udf = Buffer.alloc(0x9000)
udf.write('BEA01', 0x8001, 'latin1')
udf.write('NSR03', 0x8801, 'latin1')
assert.equal(sniffContentType(udf), 'udf')
})
})
test.group('yenc / lenientSize', () => {
test("recognises yEncBin's encrypted .bin container by its header", ({ assert }) => {
// The first 48 bytes of 2141406641.bin (yEncBin@Poster.com).
const data = Buffer.from('54f399d117c492696e4dc4ef5b60c11d7f83e92dfdf23450e79370b048123da81e4c8013091b682fb22478daed2ae981', 'hex')
assert.equal(sniffContentType(data), 'yencbin')
// The second variant (1060238685.bin).
assert.equal(sniffContentType(Buffer.from('54f399e917c492696a4e9fb05b60c31d7f83e92df9f16f5086e3c3d9480d66ef1e48d915', 'hex')), 'yencbin')
assert.isNull(sniffContentType(Buffer.from('54f399d117c492696e4dc4ef5b60c11e7f83e92d', 'hex')))
})
test('decodes a part whose declared size leaves out a header, still checking the CRC', ({ assert }) => {
const data = Buffer.concat([Buffer.from('nzbe'), Buffer.alloc(16, 7), Buffer.from('ciphertext-here')])
const declared = data.length - 20
const body = Buffer.from(
`=ybegin part=1 line=128 size=${declared} name=f6dd6b156692a52b.bin\r\n=ypart begin=1 end=${declared}\r\n` +
yencode.encode(data).toString('latin1') +
`\r\n=yend size=${declared} part=1 pcrc32=${crc32Hex(data)}`,
'latin1'
)
assert.throws(() => decodeSegment(body), YencError as any)
assert.isTrue(decodeSegment(body, { lenientSize: true }).data.equals(data))
})
})