*/ private array $originalEnvironment = []; public function createApplication() { $this->databasePath = sys_get_temp_dir().'/nntmux-nfo-import-test.sqlite'; $this->originalEnvironment = [ 'APP_ENV' => getenv('APP_ENV'), 'DB_CONNECTION' => getenv('DB_CONNECTION'), 'DB_DATABASE' => getenv('DB_DATABASE'), ]; if (file_exists($this->databasePath)) { unlink($this->databasePath); } $pdo = new PDO('sqlite:'.$this->databasePath); $pdo->exec('CREATE TABLE settings (name VARCHAR PRIMARY KEY, value TEXT NULL)'); $pdo->exec("INSERT INTO settings (name, value) VALUES ('categorizeforeign', '0'), ('catwebdl', '0'), ('innerfileblacklist', ''), ('timeoutseconds', '60')"); $this->setEnvironmentValue('APP_ENV', 'testing'); $this->setEnvironmentValue('DB_CONNECTION', 'sqlite'); $this->setEnvironmentValue('DB_DATABASE', $this->databasePath); $app = require __DIR__.'/../../bootstrap/app.php'; $app->make(Kernel::class)->bootstrap(); return $app; } protected function setUp(): void { parent::setUp(); config([ 'database.default' => 'sqlite', 'database.connections.sqlite.database' => $this->databasePath, 'nntmux.tmp_unrar_path' => sys_get_temp_dir().'/nntmux-nfo-import-tmp', ]); DB::purge(); DB::reconnect(); Schema::dropAllTables(); Schema::create('settings', function (Blueprint $table): void { $table->string('name')->primary(); $table->text('value')->nullable(); }); Schema::create('releases', function (Blueprint $table): void { $table->increments('id'); $table->string('guid')->unique(); $table->integer('nfostatus')->default(-1); }); Schema::create('release_nfos', function (Blueprint $table): void { $table->unsignedInteger('releases_id')->primary(); $table->binary('nfo')->nullable(); }); DB::table('settings')->insert([ ['name' => 'timeoutseconds', 'value' => '60'], ]); $this->uploadFolder = sys_get_temp_dir().'/nntmux-nfo-import-'.bin2hex(random_bytes(6)); (new Filesystem)->makeDirectory($this->uploadFolder, 0775, true); } protected function tearDown(): void { (new Filesystem)->deleteDirectory($this->uploadFolder); (new Filesystem)->deleteDirectory(sys_get_temp_dir().'/nntmux-nfo-import-tmp'); if (file_exists($this->databasePath)) { unlink($this->databasePath); } parent::tearDown(); foreach ($this->originalEnvironment as $key => $value) { $this->setEnvironmentValue($key, $value === false ? null : $value); } } public function test_it_replaces_an_existing_nfo_for_the_manifest_release(): void { $guid = '11111111-1111-4111-8111-111111111111'; DB::table('releases')->insert(['id' => 1, 'guid' => $guid, 'nfostatus' => -1]); DB::table('release_nfos')->insert([ 'releases_id' => 1, 'nfo' => "\x1f\x8b\x08\x00".gzcompress('old information'), ]); [$manifestPath, $nfoPath, $manifests] = $this->makeResolvedUpload($guid); $service = new NfoImportService(new Filesystem, $manifests, new NfoService); $result = $service->importManifest($manifestPath); $this->assertSame('imported', $result['status']); $stored = DB::table('release_nfos')->where('releases_id', 1)->value('nfo'); $this->assertIsString($stored); $this->assertSame('new release information', gzuncompress(substr($stored, 4))); $this->assertSame(1, DB::table('releases')->where('id', 1)->value('nfostatus')); $this->assertSame(NzbUploadManifestService::STATE_NFO_IMPORTED, $manifests->read($manifestPath)['state']); $this->assertFileExists($nfoPath); } public function test_nfo_import_log_channel_is_available(): void { $this->assertInstanceOf(LoggerInterface::class, Log::channel('nfo_import')); } public function test_it_deletes_a_successfully_imported_nfo_when_requested(): void { $guid = '22222222-2222-4222-8222-222222222222'; DB::table('releases')->insert(['id' => 1, 'guid' => $guid, 'nfostatus' => -1]); [$manifestPath, $nfoPath, $manifests] = $this->makeResolvedUpload($guid); $result = (new NfoImportService(new Filesystem, $manifests, new NfoService)) ->importManifest($manifestPath, delete: true); $this->assertSame('imported', $result['status']); $this->assertFileDoesNotExist($nfoPath); $this->assertFileExists($manifestPath); } public function test_duplicate_nzb_manifest_is_not_linked_and_can_delete_its_nfo(): void { $filesystem = new Filesystem; $manifests = new NzbUploadManifestService($filesystem); $directory = $this->uploadFolder.'/duplicate'; $filesystem->makeDirectory($directory, 0775, true); $filesystem->put($directory.'/Release.nzb', ''); $filesystem->put($directory.'/different.nfo', 'duplicate release information'); $manifestPath = $directory.'/'.NzbUploadManifestService::FILENAME; $manifests->create($directory, 'duplicate', 'Release.nzb', 'different.nfo'); $manifests->recordNzbOutcome( $directory.'/Release.nzb', NzbImportStatus::Duplicate, null, null, ); $result = (new NfoImportService($filesystem, $manifests, new NfoService)) ->importManifest($manifestPath, deleteFailed: true); $this->assertSame('skipped', $result['status']); $this->assertFileDoesNotExist($directory.'/different.nfo'); $this->assertSame(NzbUploadManifestService::STATE_NZB_DUPLICATE, $manifests->read($manifestPath)['state']); } public function test_it_rejects_a_manifest_path_that_escapes_the_upload_directory(): void { $filesystem = new Filesystem; $directory = $this->uploadFolder.'/unsafe'; $filesystem->makeDirectory($directory, 0775, true); $outsidePath = $this->uploadFolder.'/outside.nfo'; $filesystem->put($outsidePath, 'outside information'); $manifestPath = $directory.'/'.NzbUploadManifestService::FILENAME; $filesystem->put($manifestPath, json_encode([ 'version' => 1, 'upload_id' => 'unsafe', 'state' => NzbUploadManifestService::STATE_NZB_IMPORTED, 'nzb' => ['filename' => 'Release.nzb'], 'nfo' => ['filename' => '../outside.nfo'], 'release_id' => 1, 'release_guid' => 'unsafe-guid', ], JSON_THROW_ON_ERROR)); $manifests = new NzbUploadManifestService($filesystem); $result = (new NfoImportService($filesystem, $manifests, new NfoService)) ->importManifest($manifestPath, deleteFailed: true); $this->assertSame('failed', $result['status']); $this->assertFileExists($outsidePath); } /** * @return array{string,string,NzbUploadManifestService} */ private function makeResolvedUpload(string $guid): array { $filesystem = new Filesystem; $manifests = new NzbUploadManifestService($filesystem); $directory = $this->uploadFolder.'/resolved'; $filesystem->makeDirectory($directory, 0775, true); $nzbPath = $directory.'/Release.nzb'; $nfoPath = $directory.'/different.nfo'; $filesystem->put($nzbPath, ''); $filesystem->put($nfoPath, 'new release information'); $manifestPath = $directory.'/'.NzbUploadManifestService::FILENAME; $manifests->create($directory, 'resolved', 'Release.nzb', 'different.nfo'); $manifests->recordNzbOutcome($nzbPath, NzbImportStatus::Inserted, 1, $guid); return [$manifestPath, $nfoPath, $manifests]; } private function setEnvironmentValue(string $key, ?string $value): void { if ($value === null) { putenv($key); unset($_ENV[$key], $_SERVER[$key]); return; } putenv($key.'='.$value); $_ENV[$key] = $value; $_SERVER[$key] = $value; } }