feat: full anonymization of buspro xml dumps
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Command;
|
||||
|
||||
use App\Command\BpnXmlAnonymizeCommand;
|
||||
use App\Service\BpnXmlAnonymizer;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
class BpnXmlAnonymizeCommandTest extends TestCase
|
||||
{
|
||||
/** @var array<int, string> */
|
||||
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, '<xml>travel-request</xml>');
|
||||
file_put_contents($pairedInputFile, '<xml>travel-response</xml>');
|
||||
|
||||
$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:<xml>travel-request</xml>', trim((string) file_get_contents($targetFile)));
|
||||
self::assertSame('ANON:<xml>travel-response</xml>', trim((string) file_get_contents($pairedTargetFile)));
|
||||
self::assertSame([
|
||||
['resetState'],
|
||||
['anonymize', '<xml>travel-request</xml>', true],
|
||||
['anonymize', '<xml>travel-response</xml>', 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', '<xml>alpha-request</xml>');
|
||||
file_put_contents($inputDir.'/alpha_response.xml', '<xml>alpha-response</xml>');
|
||||
file_put_contents($inputDir.'/beta_request.xml', '<xml>beta-request</xml>');
|
||||
file_put_contents($inputDir.'/beta_response.xml', '<xml>beta-response</xml>');
|
||||
|
||||
$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:<xml>alpha-request</xml>', trim((string) file_get_contents($outputDir.'/alpha_request.xml')));
|
||||
self::assertSame('ANON:<xml>alpha-response</xml>', trim((string) file_get_contents($outputDir.'/alpha_response.xml')));
|
||||
self::assertSame('ANON:<xml>beta-request</xml>', trim((string) file_get_contents($outputDir.'/beta_request.xml')));
|
||||
self::assertSame('ANON:<xml>beta-response</xml>', trim((string) file_get_contents($outputDir.'/beta_response.xml')));
|
||||
self::assertSame([
|
||||
['resetState'],
|
||||
['anonymize', '<xml>alpha-request</xml>', true],
|
||||
['anonymize', '<xml>alpha-response</xml>', false],
|
||||
['resetState'],
|
||||
['anonymize', '<xml>beta-request</xml>', true],
|
||||
['anonymize', '<xml>beta-response</xml>', 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, '<?xml version="1.0" encoding="UTF-8"?><root><meta>hello</meta></root>');
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,10 @@ class DbAnonymizeCommandTest extends TestCase
|
||||
$tester->execute([]);
|
||||
|
||||
self::assertSame(Command::SUCCESS, $tester->getStatusCode());
|
||||
self::assertStringContainsString('Anonymized 1 users, 2 newsletter consents, 3 newsletter opt-in requests, and 4 booking drafts.', $tester->getDisplay());
|
||||
self::assertStringContainsString(
|
||||
'Anonymized 1 users, 2 newsletter consents, 3 newsletter opt-in requests, and 4 booking drafts.',
|
||||
$this->normalizeConsoleOutput($tester->getDisplay()),
|
||||
);
|
||||
}
|
||||
|
||||
private function createCommand(DatabaseAnonymizer $anonymizer, string $environment): DbAnonymizeCommand
|
||||
@@ -52,4 +55,9 @@ class DbAnonymizeCommandTest extends TestCase
|
||||
$environment,
|
||||
);
|
||||
}
|
||||
|
||||
private function normalizeConsoleOutput(string $output): string
|
||||
{
|
||||
return trim((string) preg_replace('/\s+/', ' ', $output));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user