Files
newznab-tmux/tests/Feature/FailedReleasesControllerSecurityTest.php
T
2026-06-12 10:11:15 +02:00

133 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature;
use App\Facades\Search;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase;
class FailedReleasesControllerSecurityTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
config([
'app.key' => 'base64:'.base64_encode(random_bytes(32)),
'cache.default' => 'array',
'database.default' => 'sqlite',
'database.connections.sqlite.database' => ':memory:',
'session.driver' => 'array',
]);
DB::purge();
DB::reconnect();
$this->createSchema();
$this->seedSettings();
}
public function test_failed_callback_rejects_userid_without_session_or_api_token(): void
{
$this->createUser(1, 'rss-token');
Search::shouldReceive('searchReleases')->never();
$this->get(route('failed', [
'guid' => 'failed-guid',
'userid' => 1,
]))->assertStatus(401)
->assertHeader('X-DNZB-RCode', 401);
}
public function test_failed_callback_uses_api_token_user_and_preserves_token_for_alternate_download(): void
{
$this->createUser(1, 'rss-token');
$this->createUser(999, 'other-token');
$this->createRelease(10, 'failed-guid', 'Show.Name.1080p.WEB-DL-GROUP', 5000, '2026-06-11 00:00:00');
$this->createRelease(20, 'alternate-guid', 'Show.Name.1080p.PROPER-GROUP', 5000, '2026-06-12 00:00:00');
Search::shouldReceive('searchReleases')
->once()
->with('Show.Name.1080p', 10)
->andReturn([20]);
$response = $this->get(route('failed', [
'guid' => 'failed-guid',
'userid' => 999,
'api_token' => 'rss-token',
]));
$response->assertOk();
$this->assertSame(
url('/getnzb').'?id=alternate-guid&r=rss-token',
$response->headers->get('Location'),
);
$this->assertDatabaseHas('dnzb_failures', [
'release_id' => 10,
'users_id' => 1,
'failed' => 1,
]);
$this->assertDatabaseMissing('dnzb_failures', [
'release_id' => 10,
'users_id' => 999,
]);
}
private function createSchema(): void
{
Schema::create('settings', function (Blueprint $table): void {
$table->string('name')->primary();
$table->text('value')->nullable();
});
Schema::create('users', function (Blueprint $table): void {
$table->increments('id');
$table->string('username');
$table->string('email')->unique();
$table->string('password');
$table->unsignedInteger('roles_id')->default(1);
$table->string('api_token')->nullable();
$table->boolean('verified')->default(true);
$table->timestamp('email_verified_at')->nullable();
$table->rememberToken();
$table->timestamps();
$table->softDeletes();
});
Schema::create('releases', function (Blueprint $table): void {
$table->increments('id');
$table->string('guid')->unique();
$table->string('searchname');
$table->unsignedInteger('categories_id');
$table->timestamp('postdate')->nullable();
});
Schema::create('dnzb_failures', function (Blueprint $table): void {
$table->increments('id');
$table->unsignedInteger('release_id');
$table->unsignedInteger('users_id');
$table->boolean('failed')->default(true);
});
}
private function seedSettings(): void
{
DB::table('settings')->insert([
['name' => 'innerfileblacklist', 'value' => ''],
['name' => 'categorizeforeign', 'value' => '0'],
['name' => 'catwebdl', 'value' => '0'],
]);
}
private function createUser(int $id, string $apiToken): void
{
DB::table('users')->insert([
'id' => $id,
'username' => 'user'.$id,
'email' => 'user'.$id.'@example.test',
'password' => 'unused',
'roles_id' => 1,
'api_token' => $apiToken,
'verified' => true,
'email_verified_at' => now(),
'created_at' => now(),
'updated_at' => now(),
]);
}
private function createRelease(int $id, string $guid, string $searchName, int $categoryId, string $postDate): void
{
DB::table('releases')->insert([
'id' => $id,
'guid' => $guid,
'searchname' => $searchName,
'categories_id' => $categoryId,
'postdate' => $postDate,
]);
}
}