123, 'releases_id' => 123, 'name' => $wrapped, 'searchname' => $wrapped, 'fromname' => 'poster@example.com', 'guid' => str_repeat('a', 40), 'groups_id' => 1, 'categories_id' => Category::OTHER_HASHED, ]); $context = new ReleaseProcessingContext($release); $updateService = $this->createMock(ReleaseUpdateService::class); $updateService->expects($this->once()) ->method('updateRelease') ->with( $release, $expected, 'NZBSPLIT wrapper', true, 'Filenames, ', true, true ); $manager = $this->makeReleaseFileManager($updateService); $renamed = $manager->processReleaseNameFromNzbContents([ ['title' => $wrapped], ], $context); $this->assertTrue($renamed); $this->assertSame($expected, $context->release->searchname); } public function test_additional_postprocessing_skips_non_nzbsplit_wrapped_titles(): void { $wrapped = "N_NZB_[1_6]_-_Woman's_Day_New_Zealand_-_Issue_45_April_27_2026.par2"; $release = new Release([ 'id' => 125, 'releases_id' => 125, 'name' => $wrapped, 'searchname' => $wrapped, 'fromname' => 'poster@example.com', 'guid' => str_repeat('b', 40), 'groups_id' => 1, 'categories_id' => Category::OTHER_HASHED, ]); $context = new ReleaseProcessingContext($release); $updateService = $this->createMock(ReleaseUpdateService::class); $updateService->expects($this->never())->method('updateRelease'); $manager = $this->makeReleaseFileManager($updateService); $renamed = $manager->processReleaseNameFromNzbContents([ ['title' => $wrapped], ], $context); $this->assertFalse($renamed); $this->assertSame($wrapped, $context->release->searchname); } public function test_additional_postprocessing_skips_low_information_nzb_split_payloads(): void { $wrapped = 'TEST__NZBSPLIT__1234567890abcdef__NZBSPLIT__setup.7z.001'; $release = new Release([ 'id' => 124, 'releases_id' => 124, 'name' => $wrapped, 'searchname' => $wrapped, 'groups_id' => 1, 'categories_id' => Category::OTHER_HASHED, ]); $context = new ReleaseProcessingContext($release); $updateService = $this->createMock(ReleaseUpdateService::class); $updateService->expects($this->never())->method('updateRelease'); $manager = $this->makeReleaseFileManager($updateService); $renamed = $manager->processReleaseNameFromNzbContents([ ['title' => $wrapped], ], $context); $this->assertFalse($renamed); $this->assertSame($wrapped, $context->release->searchname); } public function test_additional_postprocessing_does_not_rename_movie_style_obfuscated_subjects(): void { $wrapped = '[1/6] - "Yoh! Bestie 2026 1080p NF WEB-DL H 264 DDP5 1-UBWEB.par2" yEnc'; $existingName = 'Yoh!_Bestie_2026_1080p_NF_WEB-DL_H_264_DDP5_1-UBWEB'; $release = new Release([ 'id' => 126, 'releases_id' => 126, 'name' => $wrapped, 'searchname' => $existingName, 'groups_id' => 1, 'categories_id' => Category::MOVIE_HD, ]); $context = new ReleaseProcessingContext($release); $updateService = $this->createMock(ReleaseUpdateService::class); $updateService->expects($this->never())->method('updateRelease'); $manager = $this->makeReleaseFileManager($updateService); $renamed = $manager->processReleaseNameFromNzbContents([ ['title' => $wrapped], ], $context); $this->assertFalse($renamed); $this->assertSame($existingName, $context->release->searchname); } public function test_archive_name_extractor_unwraps_nzb_split_file_names(): void { $manager = $this->makeReleaseFileManager(); $extractMethod = new \ReflectionMethod($manager, 'extractReleaseNameFromFile'); $result = $extractMethod->invoke( $manager, 'MLB__NZBSPLIT__f5054661a1d468cc6a8712de8217c1c9__NZBSPLIT__MLB_2026_Cincinnati_Reds_vs_Tampa_Bay_Rays_20_04_720pEN60fps.7z.075' ); $this->assertSame('MLB.2026.Cincinnati.Reds.vs.Tampa.Bay.Rays.20.04.720pEN60fps', $result); } public function test_additional_nfo_detection_accepts_alternative_and_scene_names(): void { $manager = $this->makeReleaseFileManager(); foreach (['file_id.diz', 'readme.txt', 'info.txt', '00-scene.nfo', 'group-release.nfo'] as $filename) { $this->assertTrue($manager->isNfoFilename($filename), $filename.' should be treated as NFO data'); } } public function test_additional_nfo_normalization_converts_utf16_and_removes_utf8_bom(): void { $manager = $this->makeReleaseFileManager(); $normalize = new \ReflectionMethod($manager, 'normalizeNfoEncoding'); $utf16 = "\xFF\xFE".mb_convert_encoding('Scene NFO', 'UTF-16LE', 'UTF-8'); $this->assertSame('Scene NFO', $normalize->invoke($manager, $utf16)); $this->assertSame('Scene NFO', $normalize->invoke($manager, "\xEF\xBB\xBFScene NFO")); } private function makeReleaseFileManager(?ReleaseUpdateService $updateService = null): ReleaseFileManager { $updateService ??= $this->createMock(ReleaseUpdateService::class); return new ReleaseFileManager( $this->makeConfiguration(), $this->createMock(ReleaseImageService::class), $this->createMock(NfoService::class), $this->createMock(NzbService::class), $this->createMock(NameFixingService::class), $updateService, new FileNameCleaner ); } private function makeConfiguration(): ProcessingConfiguration { $reflection = new \ReflectionClass(ProcessingConfiguration::class); /** @var ProcessingConfiguration $config */ $config = $reflection->newInstanceWithoutConstructor(); return $config; } }