test: add Redactor integration coverage against existing PZ fixtures
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,205 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IndifferentKetchup\Codex\Test\Tests\Util\Redactor;
|
||||||
|
|
||||||
|
use IndifferentKetchup\Codex\Log\File\PathLogFile;
|
||||||
|
use IndifferentKetchup\Codex\Log\File\StringLogFile;
|
||||||
|
use IndifferentKetchup\Codex\Log\ProjectZomboid\ProjectZomboidAdminLog;
|
||||||
|
use IndifferentKetchup\Codex\Log\ProjectZomboid\ProjectZomboidBurdJournalsLog;
|
||||||
|
use IndifferentKetchup\Codex\Log\ProjectZomboid\ProjectZomboidChatLog;
|
||||||
|
use IndifferentKetchup\Codex\Log\ProjectZomboid\ProjectZomboidClientActionLog;
|
||||||
|
use IndifferentKetchup\Codex\Log\ProjectZomboid\ProjectZomboidCmdLog;
|
||||||
|
use IndifferentKetchup\Codex\Log\ProjectZomboid\ProjectZomboidItemLog;
|
||||||
|
use IndifferentKetchup\Codex\Log\ProjectZomboid\ProjectZomboidMapLog;
|
||||||
|
use IndifferentKetchup\Codex\Log\ProjectZomboid\ProjectZomboidPerkLog;
|
||||||
|
use IndifferentKetchup\Codex\Log\ProjectZomboid\ProjectZomboidPvpLog;
|
||||||
|
use IndifferentKetchup\Codex\Log\ProjectZomboid\ProjectZomboidServerLog;
|
||||||
|
use IndifferentKetchup\Codex\Log\ProjectZomboid\ProjectZomboidUserLog;
|
||||||
|
use IndifferentKetchup\Codex\Util\ProjectZomboid\ProjectZomboidRedactor;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests: drive all 11 existing PZ fixtures through ProjectZomboidRedactor
|
||||||
|
* and verify that the output is well-formed.
|
||||||
|
*
|
||||||
|
* Three properties are checked across all fixtures:
|
||||||
|
*
|
||||||
|
* 1. Steam ID normalisation — no non-zero-placeholder Steam IDs survive.
|
||||||
|
* 2. Structural preservation — parsing the redacted content yields the same
|
||||||
|
* entry count as parsing the original.
|
||||||
|
* 3. Idempotence — applying redact() a second time produces no further changes.
|
||||||
|
*
|
||||||
|
* Known v1 limitations documented inline:
|
||||||
|
*
|
||||||
|
* - pvp.txt: victim names after `hit "..."` are NOT redacted (Task 3 limitation).
|
||||||
|
* Player2 can therefore still appear after `hit` in the redacted pvp output.
|
||||||
|
* - pvp.txt: victim coords after `hit "(x,y,z)"` are NOT redacted (Task 4
|
||||||
|
* limitation). COORDS_PARENTHESISED_REGEX anchors on the trailing PvP verb
|
||||||
|
* which is present only for the attacker bracket.
|
||||||
|
* - admin.txt: `teleported X to <x,y,z>` coords survive because COORDS_AT_CLAUSE_REGEX
|
||||||
|
* anchors on ` at `, not ` to `.
|
||||||
|
*/
|
||||||
|
class ProjectZomboidRedactorIntegrationTest extends TestCase
|
||||||
|
{
|
||||||
|
private static string $fixturesDir = __DIR__ . '/../../../src/Games/ProjectZomboid/fixtures';
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Data providers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yields [fixturePath] for every PZ fixture file.
|
||||||
|
*/
|
||||||
|
public static function fixturePathProvider(): array
|
||||||
|
{
|
||||||
|
$dir = self::$fixturesDir;
|
||||||
|
return [
|
||||||
|
'admin' => [$dir . '/admin-minimal.txt'],
|
||||||
|
'burd-journals' => [$dir . '/burd-journals-minimal.txt'],
|
||||||
|
'chat' => [$dir . '/chat-minimal.txt'],
|
||||||
|
'client-action' => [$dir . '/client-action-minimal.txt'],
|
||||||
|
'cmd' => [$dir . '/cmd-minimal.txt'],
|
||||||
|
'debug-server' => [$dir . '/debug-server-minimal.txt'],
|
||||||
|
'item' => [$dir . '/item-minimal.txt'],
|
||||||
|
'map' => [$dir . '/map-minimal.txt'],
|
||||||
|
'perk' => [$dir . '/perk-minimal.txt'],
|
||||||
|
'pvp' => [$dir . '/pvp-minimal.txt'],
|
||||||
|
'user' => [$dir . '/user-minimal.txt'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yields [fixturePath, logClass] for the fixtures whose log class parses
|
||||||
|
* them. All 11 fixtures are represented.
|
||||||
|
*/
|
||||||
|
public static function fixtureWithLogClassProvider(): array
|
||||||
|
{
|
||||||
|
$dir = self::$fixturesDir;
|
||||||
|
return [
|
||||||
|
'admin' => [$dir . '/admin-minimal.txt', ProjectZomboidAdminLog::class],
|
||||||
|
'burd-journals' => [$dir . '/burd-journals-minimal.txt', ProjectZomboidBurdJournalsLog::class],
|
||||||
|
'chat' => [$dir . '/chat-minimal.txt', ProjectZomboidChatLog::class],
|
||||||
|
'client-action' => [$dir . '/client-action-minimal.txt', ProjectZomboidClientActionLog::class],
|
||||||
|
'cmd' => [$dir . '/cmd-minimal.txt', ProjectZomboidCmdLog::class],
|
||||||
|
'debug-server' => [$dir . '/debug-server-minimal.txt', ProjectZomboidServerLog::class],
|
||||||
|
'item' => [$dir . '/item-minimal.txt', ProjectZomboidItemLog::class],
|
||||||
|
'map' => [$dir . '/map-minimal.txt', ProjectZomboidMapLog::class],
|
||||||
|
'perk' => [$dir . '/perk-minimal.txt', ProjectZomboidPerkLog::class],
|
||||||
|
'pvp' => [$dir . '/pvp-minimal.txt', ProjectZomboidPvpLog::class],
|
||||||
|
'user' => [$dir . '/user-minimal.txt', ProjectZomboidUserLog::class],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Helper
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private function redact(string $content): string
|
||||||
|
{
|
||||||
|
return (new ProjectZomboidRedactor())->redact($content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Test 1 — Steam ID normalisation
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After redaction every 17-digit Steam ID that is NOT the zero-placeholder
|
||||||
|
* must be gone. The zero-placeholder itself (76561198000000000) is the only
|
||||||
|
* Steam ID that may remain.
|
||||||
|
*/
|
||||||
|
#[DataProvider('fixturePathProvider')]
|
||||||
|
public function testFixtureContainsNoSteamIdsAfterRedaction(string $fixturePath): void
|
||||||
|
{
|
||||||
|
$content = (new PathLogFile($fixturePath))->getContent();
|
||||||
|
$redacted = $this->redact($content);
|
||||||
|
|
||||||
|
$matches = preg_match_all('/(?<![A-Za-z0-9])76561198(?!000000000)\d{9}(?![A-Za-z0-9])/u', $redacted);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
0,
|
||||||
|
$matches,
|
||||||
|
sprintf(
|
||||||
|
'After redaction, fixture "%s" must contain no non-zero-placeholder Steam IDs, but %d were found.',
|
||||||
|
basename($fixturePath),
|
||||||
|
$matches,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Test 2 — Structural preservation (re-parse after redaction)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The redacted content, fed back through the corresponding parser, must
|
||||||
|
* produce exactly the same number of log entries as the original content.
|
||||||
|
*
|
||||||
|
* This asserts that the redactor does not corrupt timestamps, delimiters,
|
||||||
|
* or structural tokens that the parser relies on.
|
||||||
|
*
|
||||||
|
* @param string $fixturePath Path to the fixture file.
|
||||||
|
* @param class-string<\IndifferentKetchup\Codex\Log\Log> $logClass
|
||||||
|
* Fully-qualified name of the Log subclass that corresponds to this fixture.
|
||||||
|
*/
|
||||||
|
#[DataProvider('fixtureWithLogClassProvider')]
|
||||||
|
public function testFixtureRedactedOutputParsesToSameEntryCount(string $fixturePath, string $logClass): void
|
||||||
|
{
|
||||||
|
$content = (new PathLogFile($fixturePath))->getContent();
|
||||||
|
|
||||||
|
/** @var \IndifferentKetchup\Codex\Log\Log $originalLog */
|
||||||
|
$originalLog = (new $logClass())->setLogFile(new PathLogFile($fixturePath));
|
||||||
|
$originalLog->parse();
|
||||||
|
$originalCount = count($originalLog->getEntries());
|
||||||
|
|
||||||
|
$redacted = $this->redact($content);
|
||||||
|
|
||||||
|
/** @var \IndifferentKetchup\Codex\Log\Log $redactedLog */
|
||||||
|
$redactedLog = (new $logClass())->setLogFile(new StringLogFile($redacted));
|
||||||
|
$redactedLog->parse();
|
||||||
|
$redactedCount = count($redactedLog->getEntries());
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
$originalCount,
|
||||||
|
$redactedCount,
|
||||||
|
sprintf(
|
||||||
|
'Parsing the redacted "%s" fixture with %s must yield the same entry count (%d) as parsing the original, but got %d.',
|
||||||
|
basename($fixturePath),
|
||||||
|
$logClass,
|
||||||
|
$originalCount,
|
||||||
|
$redactedCount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Test 3 — Idempotence
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applying redact() a second time must produce no further changes:
|
||||||
|
* redact(redact(content)) === redact(content).
|
||||||
|
*
|
||||||
|
* This guards against poorly-anchored regexes that would re-match the
|
||||||
|
* redaction placeholders themselves on a second pass.
|
||||||
|
*/
|
||||||
|
#[DataProvider('fixturePathProvider')]
|
||||||
|
public function testFixtureIsIdempotent(string $fixturePath): void
|
||||||
|
{
|
||||||
|
$content = (new PathLogFile($fixturePath))->getContent();
|
||||||
|
|
||||||
|
$redactor = new ProjectZomboidRedactor();
|
||||||
|
$once = $redactor->redact($content);
|
||||||
|
$twice = $redactor->redact($once);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
$once,
|
||||||
|
$twice,
|
||||||
|
sprintf(
|
||||||
|
'redact(redact(content)) must equal redact(content) for fixture "%s"; a second pass must be a no-op.',
|
||||||
|
basename($fixturePath),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user