command) { return; } $release = new \ReflectionMethod($this->command, 'release'); $release->setAccessible(true); $release->invoke($this->command); } protected function setUp(): void { $this->xmlSource = $this->createStub(FilesystemOperator::class); $this->xmlExport = $this->createStub(FilesystemOperator::class); $this->cache = $this->createMock(TagAwareCacheInterface::class); $this->logger = $this->createStub(LoggerInterface::class); $this->travelLoader = $this->createMock(TravelLoader::class); $this->travelDataService = $this->createMock(TravelDataProvider::class); $this->travelSnapshotService = $this->createMock(TravelSnapshotManager::class); } private function command(): BpnXmlSyncCommand { return $this->command ??= new BpnXmlSyncCommand( new BpnXmlSyncManager( $this->xmlSource, $this->xmlExport, $this->cache, $this->logger, ), new BpnXmlSnapshotRefreshManager( $this->travelLoader, $this->travelDataService, $this->travelSnapshotService, $this->logger, ), $this->logger, ); } public function testSnapshotSyncIsTriggeredAfterSuccessfulFileDownload(): void { $newerTimestamp = "24.03.2026 12:00:00\nExport\n3 Dateien\n"; $olderTimestamp = "24.03.2026 10:00:00\nExport\n3 Dateien\n"; $this->xmlSource->method('read')->willReturn($newerTimestamp); $this->xmlExport->method('fileExists')->willReturn(true); $this->xmlExport->method('read')->willReturn($olderTimestamp); $this->xmlSource->method('listContents')->willReturn(new DirectoryListing([])); $this->xmlExport->method('listContents')->willReturn(new DirectoryListing([])); $fileMap = [ 101 => ['hotels' => ['H1' => null, 'H2' => null]], 102 => ['hotels' => ['H3' => null]], ]; $this->travelLoader->expects($this->once()) ->method('generateFilesMap') ->willReturn($fileMap); $this->travelDataService->expects($this->once()) ->method('syncSnapshotsFromXml') ->with($fileMap, $this->isInstanceOf(\Closure::class)) ->willReturn(['processed' => 2, 'failed' => 0]); $this->travelSnapshotService->expects($this->once()) ->method('purgeOrphanedFutureSnapshots') ->with([101, 102]) ->willReturn(0); $this->cache->expects($this->once()) ->method('invalidateTags') ->with(['xml-sync']); $tester = new CommandTester($this->command()); $tester->execute([]); $this->assertSame(Command::SUCCESS, $tester->getStatusCode()); } public function testSnapshotSyncIsSkippedWhenLocalDataIsUpToDate(): void { $sameTimestamp = "24.03.2026 10:00:00\nExport\n3 Dateien\n"; $this->xmlSource->method('read')->willReturn($sameTimestamp); $this->xmlExport->method('fileExists')->willReturn(true); $this->xmlExport->method('read')->willReturn($sameTimestamp); $this->xmlSource->method('listContents')->willReturn(new DirectoryListing([])); $this->xmlExport->method('listContents')->willReturn(new DirectoryListing([])); $this->travelLoader->expects($this->never())->method('generateFilesMap'); $this->travelDataService->expects($this->never())->method('syncSnapshotsFromXml'); $this->travelSnapshotService->expects($this->never())->method('purgeOrphanedFutureSnapshots'); $this->cache->expects($this->never())->method('invalidateTags'); $tester = new CommandTester($this->command()); $tester->execute([]); $this->assertSame(Command::SUCCESS, $tester->getStatusCode()); } public function testSnapshotSyncIsSkippedAndWarningLoggedWhenFileMapGenerationFails(): void { $this->logger = $this->createMock(LoggerInterface::class); $newerTimestamp = "24.03.2026 12:00:00\nExport\n3 Dateien\n"; $olderTimestamp = "24.03.2026 10:00:00\nExport\n3 Dateien\n"; $this->xmlSource->method('read')->willReturn($newerTimestamp); $this->xmlExport->method('fileExists')->willReturn(true); $this->xmlExport->method('read')->willReturn($olderTimestamp); $this->xmlSource->method('listContents')->willReturn(new DirectoryListing([])); $this->xmlExport->method('listContents')->willReturn(new DirectoryListing([])); $this->travelLoader->expects($this->once()) ->method('generateFilesMap') ->willThrowException(new \RuntimeException('Storage unavailable')); $this->logger->expects($this->atLeastOnce()) ->method('warning') ->with( $this->stringContains('snapshot refresh skipped'), $this->anything(), ); $this->travelDataService->expects($this->never())->method('syncSnapshotsFromXml'); $this->travelSnapshotService->expects($this->never())->method('purgeOrphanedFutureSnapshots'); $this->cache->expects($this->once())->method('invalidateTags')->with(['xml-sync']); $tester = new CommandTester($this->command()); $tester->execute([]); $this->assertSame(Command::SUCCESS, $tester->getStatusCode()); } }