mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-29 09:18:54 +00:00
193 lines
7.0 KiB
PHP
193 lines
7.0 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
use Tests\Support\TestBinariesHarness;
|
|
use Tests\TestCase;
|
|
|
|
class BinariesStoreHeadersTest extends TestCase
|
|
{
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
config(['database.default' => 'sqlite', 'database.connections.sqlite.database' => ':memory:']);
|
|
\Illuminate\Support\Facades\DB::purge();
|
|
\Illuminate\Support\Facades\DB::reconnect();
|
|
|
|
// Minimal tables.
|
|
DB::statement('CREATE TABLE settings (
|
|
section TEXT NULL,
|
|
subsection TEXT NULL,
|
|
name TEXT PRIMARY KEY,
|
|
value TEXT NULL,
|
|
hint TEXT NULL,
|
|
setting TEXT NULL
|
|
)');
|
|
// Seed the settings queried in Binaries constructor.
|
|
$defaults = [
|
|
'maxmssgs' => '20000',
|
|
'partrepair' => '1',
|
|
'newgroupscanmethod' => '0',
|
|
'newgroupmsgstoscan' => '50000',
|
|
'newgroupdaystoscan' => '3',
|
|
'maxpartrepair' => '15000',
|
|
'partrepairmaxtries' => '3',
|
|
];
|
|
foreach ($defaults as $k => $v) {
|
|
DB::table('settings')->insert(['name' => $k, 'value' => $v]);
|
|
}
|
|
|
|
DB::statement('CREATE TABLE collections (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
subject VARCHAR(255),
|
|
fromname VARCHAR(255),
|
|
date DATETIME NULL,
|
|
xref TEXT DEFAULT "",
|
|
groups_id INT,
|
|
totalfiles INT,
|
|
collectionhash VARCHAR(40) UNIQUE,
|
|
collection_regexes_id INT,
|
|
dateadded DATETIME NULL,
|
|
noise VARCHAR(64) DEFAULT ""
|
|
)');
|
|
|
|
DB::statement('CREATE TABLE binaries (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
binaryhash BLOB,
|
|
name VARCHAR(255),
|
|
collections_id INT,
|
|
totalparts INT,
|
|
currentparts INT,
|
|
filenumber INT,
|
|
partsize INT,
|
|
UNIQUE(binaryhash, collections_id)
|
|
)');
|
|
|
|
DB::statement('CREATE TABLE parts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
binaries_id INT,
|
|
number INT,
|
|
messageid VARCHAR(255),
|
|
partnumber INT,
|
|
size INT,
|
|
UNIQUE(number)
|
|
)');
|
|
|
|
DB::statement('CREATE TABLE missed_parts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
numberid INT,
|
|
groups_id INT,
|
|
attempts INT DEFAULT 0,
|
|
UNIQUE(numberid, groups_id)
|
|
)');
|
|
}
|
|
|
|
private function makeHeader(int $articleNumber, int $partNumber, int $totalParts, int $bytes = 100): array
|
|
{
|
|
$subjectBase = 'Example.File.Name';
|
|
|
|
return [
|
|
'Number' => $articleNumber,
|
|
'Subject' => $subjectBase.' ('.$partNumber.'/'.$totalParts.')',
|
|
'From' => 'poster@example.com',
|
|
'Date' => time(),
|
|
'Bytes' => $bytes,
|
|
'Message-ID' => '<msg'.$articleNumber.'@example.com>',
|
|
'Xref' => 'news.example.com group:'.$articleNumber,
|
|
'matches' => [
|
|
0 => $subjectBase.' ('.$partNumber.'/'.$totalParts.')',
|
|
1 => $subjectBase,
|
|
2 => $partNumber,
|
|
3 => $totalParts,
|
|
],
|
|
];
|
|
}
|
|
|
|
public function test_duplicate_collection_and_binary_reuse(): void
|
|
{
|
|
$harness = new TestBinariesHarness;
|
|
|
|
$headers = [
|
|
$this->makeHeader(1001, 1, 2, 150),
|
|
$this->makeHeader(1002, 2, 2, 175),
|
|
];
|
|
|
|
$harness->publicStoreHeaders($headers);
|
|
|
|
$collections = DB::table('collections')->count();
|
|
$binaries = DB::table('binaries')->count();
|
|
$parts = DB::table('parts')->count();
|
|
$binary = DB::table('binaries')->first();
|
|
|
|
$this->assertEquals(1, $collections, 'Should have reused collection');
|
|
$this->assertEquals(1, $binaries, 'Should have reused binary');
|
|
$this->assertEquals(2, $parts, 'Two part rows expected');
|
|
$this->assertEquals(325, $binary->partsize, 'Binary partsize should be sum of part sizes');
|
|
$this->assertEquals(2, $binary->currentparts, 'Binary currentparts should reflect both parts');
|
|
}
|
|
|
|
public function test_raw_message_id_stored_unmodified(): void
|
|
{
|
|
$harness = new TestBinariesHarness;
|
|
$headers = [$this->makeHeader(3001, 1, 1, 123)];
|
|
$harness->publicStoreHeaders($headers);
|
|
$stored = DB::table('parts')->where('number', 3001)->value('messageid');
|
|
$this->assertSame('<msg3001@example.com>', $stored, 'Message-ID should be stored with angle brackets intact');
|
|
}
|
|
|
|
public function test_rollback_on_parts_insert_failure_and_part_repair_queue(): void
|
|
{
|
|
$harness = new TestBinariesHarness;
|
|
$group = ['id' => 2, 'name' => 'alt.rollback'];
|
|
|
|
config(['nntmux.parts_chunk_size' => 2]); // force flush earlier
|
|
|
|
$headers = [
|
|
$this->makeHeader(2001, 1, 3, 111),
|
|
$this->makeHeader(2002, 2, 3, 222),
|
|
$this->makeHeader(2003, 3, 3, 333),
|
|
];
|
|
|
|
$harness->failPartsInsert = true; // Force flushPartsChunk failure
|
|
$harness->simulateScan($headers, $group, true);
|
|
|
|
$this->assertEquals(0, DB::table('collections')->count(), 'Collections should rollback');
|
|
$this->assertEquals(0, DB::table('binaries')->count(), 'Binaries should rollback');
|
|
$this->assertEquals(0, DB::table('parts')->count(), 'Parts should rollback');
|
|
|
|
$missed = DB::table('missed_parts')->pluck('numberid')->toArray();
|
|
sort($missed);
|
|
$this->assertEquals([2001, 2002, 2003], $missed, 'All headers should be marked missed after rollback');
|
|
}
|
|
|
|
public function test_mixed_success_then_failure_rolls_back_everything(): void
|
|
{
|
|
$harness = new TestBinariesHarness;
|
|
$group = ['id' => 3, 'name' => 'alt.mixed'];
|
|
|
|
// Chunk of 2: first flush succeeds, second flush fails
|
|
config(['nntmux.parts_chunk_size' => 2]);
|
|
|
|
$headers = [
|
|
$this->makeHeader(4001, 1, 5, 101),
|
|
$this->makeHeader(4002, 2, 5, 102), // triggers first successful flush
|
|
$this->makeHeader(4003, 3, 5, 103),
|
|
$this->makeHeader(4004, 4, 5, 104), // triggers failing flush
|
|
$this->makeHeader(4005, 5, 5, 105), // buffered but not flushed
|
|
];
|
|
|
|
$harness->failPartsInsert = true;
|
|
$harness->failAfterFlushCount = 1; // fail on second flush
|
|
$harness->simulateScan($headers, $group, true);
|
|
|
|
$this->assertEquals(0, DB::table('collections')->count(), 'Collections should rollback after mixed flush');
|
|
$this->assertEquals(0, DB::table('binaries')->count(), 'Binaries should rollback after mixed flush');
|
|
$this->assertEquals(0, DB::table('parts')->count(), 'Parts should rollback after mixed flush');
|
|
|
|
$missed = DB::table('missed_parts')->pluck('numberid')->toArray();
|
|
sort($missed);
|
|
$this->assertEquals([4001, 4002, 4003, 4004, 4005], $missed, 'All headers should be marked missed after mixed rollback');
|
|
}
|
|
}
|