'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, ]); } }