*/ private array $tempDirectories = []; protected function tearDown(): void { foreach ($this->tempDirectories as $directory) { $this->removeDirectory($directory); } $this->tempDirectories = []; } public function testSingleFileStillAnonymizesPairedResponseFile(): void { $inputDir = $this->createTempDirectory(); $outputDir = $this->createTempDirectory(); $inputFile = $inputDir.'/travel_request.xml'; $pairedInputFile = $inputDir.'/travel_response.xml'; $targetFile = $outputDir.'/travel_request.xml'; $pairedTargetFile = $outputDir.'/travel_response.xml'; file_put_contents($inputFile, 'travel-request'); file_put_contents($pairedInputFile, 'travel-response'); $calls = []; $lastIdentityCount = 0; $anonymizer = $this->createMock(BpnXmlAnonymizer::class); $anonymizer->expects(self::once()) ->method('resetState') ->willReturnCallback(static function () use (&$calls, &$lastIdentityCount): void { $calls[] = ['resetState']; $lastIdentityCount = 0; }); $anonymizer->expects(self::exactly(2)) ->method('anonymize') ->willReturnCallback(function (string $xml, bool $resetState) use (&$calls, &$lastIdentityCount): string { $calls[] = ['anonymize', trim($xml), $resetState]; $lastIdentityCount = true === $resetState ? 1 : 2; return sprintf('ANON:%s', trim($xml)); }); $anonymizer->method('getLastIdentityCount') ->willReturnCallback(function () use (&$lastIdentityCount): int { return $lastIdentityCount; }); $tester = new CommandTester(new BpnXmlAnonymizeCommand($anonymizer)); $tester->execute([ 'infile' => $inputFile, 'outfile' => $targetFile, ]); self::assertSame(Command::SUCCESS, $tester->getStatusCode()); self::assertSame('ANON:travel-request', trim((string) file_get_contents($targetFile))); self::assertSame('ANON:travel-response', trim((string) file_get_contents($pairedTargetFile))); self::assertSame([ ['resetState'], ['anonymize', 'travel-request', true], ['anonymize', 'travel-response', false], ], $calls); self::assertStringContainsString('Paired file anonymized', $tester->getDisplay()); } public function testDirectoryInputAnonymizesAllPairsIntoOutputDirectory(): void { $inputDir = $this->createTempDirectory(); $outputDir = $this->createTempDirectory().'/anonymized'; file_put_contents($inputDir.'/alpha_request.xml', 'alpha-request'); file_put_contents($inputDir.'/alpha_response.xml', 'alpha-response'); file_put_contents($inputDir.'/beta_request.xml', 'beta-request'); file_put_contents($inputDir.'/beta_response.xml', 'beta-response'); $calls = []; $lastIdentityCount = 0; $anonymizer = $this->createMock(BpnXmlAnonymizer::class); $anonymizer->expects(self::exactly(2)) ->method('resetState') ->willReturnCallback(static function () use (&$calls, &$lastIdentityCount): void { $calls[] = ['resetState']; $lastIdentityCount = 0; }); $anonymizer->expects(self::exactly(4)) ->method('anonymize') ->willReturnCallback(function (string $xml, bool $resetState) use (&$calls, &$lastIdentityCount): string { $content = trim($xml); $calls[] = ['anonymize', $content, $resetState]; if (str_contains($content, 'request')) { $lastIdentityCount = true === $resetState ? 1 : 0; } else { $lastIdentityCount = 2; } return sprintf('ANON:%s', $content); }); $anonymizer->method('getLastIdentityCount') ->willReturnCallback(function () use (&$lastIdentityCount): int { return $lastIdentityCount; }); $tester = new CommandTester(new BpnXmlAnonymizeCommand($anonymizer)); $tester->execute([ 'infile' => $inputDir, 'outfile' => $outputDir, ]); self::assertSame(Command::SUCCESS, $tester->getStatusCode()); self::assertSame('ANON:alpha-request', trim((string) file_get_contents($outputDir.'/alpha_request.xml'))); self::assertSame('ANON:alpha-response', trim((string) file_get_contents($outputDir.'/alpha_response.xml'))); self::assertSame('ANON:beta-request', trim((string) file_get_contents($outputDir.'/beta_request.xml'))); self::assertSame('ANON:beta-response', trim((string) file_get_contents($outputDir.'/beta_response.xml'))); self::assertSame([ ['resetState'], ['anonymize', 'alpha-request', true], ['anonymize', 'alpha-response', false], ['resetState'], ['anonymize', 'beta-request', true], ['anonymize', 'beta-response', false], ], $calls); self::assertStringContainsString('Processed 4 XML files', $tester->getDisplay()); self::assertStringContainsString('Anonymized 4 persons', $tester->getDisplay()); } public function testSingleFileWithoutSupportedPersonNodesWarnsInsteadOfClaimingSuccess(): void { $inputDir = $this->createTempDirectory(); $inputFile = $inputDir.'/meta.xml'; $targetFile = $inputDir.'/meta-out.xml'; file_put_contents($inputFile, 'hello'); $tester = new CommandTester(new BpnXmlAnonymizeCommand(new BpnXmlAnonymizer())); $tester->execute([ 'infile' => $inputFile, 'outfile' => $targetFile, ]); self::assertSame(Command::SUCCESS, $tester->getStatusCode()); self::assertStringContainsString('No anonymizable person data found', $tester->getDisplay()); self::assertStringNotContainsString('Anonymized 0 persons', $tester->getDisplay()); self::assertFileExists($targetFile); } private function createTempDirectory(): string { $directory = sys_get_temp_dir().'/bpn-xml-anonymize-'.bin2hex(random_bytes(6)); if (false === mkdir($directory, 0777, true) && false === is_dir($directory)) { self::fail(sprintf('Unable to create temporary directory: %s', $directory)); } $this->tempDirectories[] = $directory; return $directory; } private function removeDirectory(string $directory): void { if (false === is_dir($directory)) { return; } $items = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST, ); foreach ($items as $item) { if ($item->isDir()) { rmdir($item->getPathname()); continue; } unlink($item->getPathname()); } rmdir($directory); } }