test: add Redactor combined and idempotence coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
146
test/tests/Util/Redactor/ProjectZomboidRedactorCombinedTest.php
Normal file
146
test/tests/Util/Redactor/ProjectZomboidRedactorCombinedTest.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace IndifferentKetchup\Codex\Test\Tests\Util\Redactor;
|
||||
|
||||
use IndifferentKetchup\Codex\Util\ProjectZomboid\ProjectZomboidRedactor;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ProjectZomboidRedactorCombinedTest extends TestCase
|
||||
{
|
||||
public function testFullScrubAllTogglesOn(): void
|
||||
{
|
||||
// Realistic multi-line input touching all three PII categories:
|
||||
// Steam IDs, player names in multiple contexts (after Steam ID, in ChatMessage,
|
||||
// after Combat:/Safety:), and coordinates in multiple shapes (at clause,
|
||||
// bracketed, parenthesised before PvP verb).
|
||||
$input = implode("\n", [
|
||||
// cmd.txt / admin.txt: Steam ID + quoted name + at-clause coords (keyword " at ")
|
||||
'[16-04-26 12:00:00.000] 76561198111111111 "Player1" added Base.Aerosolbomb at 1000,2000,0.',
|
||||
// map.txt: Steam ID + quoted name + at-clause float coords
|
||||
'[16-04-26 12:00:01.000] 76561198222222222 "Player2" added IsoObject (fence_01) at 1050.0,2050.0,0.0.',
|
||||
// chat.txt: ChatMessage author
|
||||
"[16-04-26 17:05:03.280][info] Got message:ChatMessage{chat=Local, author='AdminUser', text='hello'}.",
|
||||
// pvp.txt Combat: name + attacker parenthesised coords before "hit"
|
||||
'[16-04-26 17:14:35.128][INFO] Combat: "Player1" (1005,2005,0) hit "Player2" (1006,2005,0) weapon="Tire Iron (Worn)" damage=0.112317.',
|
||||
// pvp.txt Safety: name + parenthesised coords before "restore"
|
||||
'[16-04-26 16:17:49.731][LOG] Safety: "Player1" (1000,2000,0) restore true.',
|
||||
// ClientActionLog: bracketed Steam ID + action + name + coords bracket
|
||||
'[16-04-26 12:00:02.000] [76561198333333333][ISEnterVehicle][Player2][1020,2020,0][Van_LectroMax].',
|
||||
]);
|
||||
|
||||
$expected = implode("\n", [
|
||||
'[16-04-26 12:00:00.000] 76561198000000000 "<player>" added Base.Aerosolbomb at 0,0,0.',
|
||||
'[16-04-26 12:00:01.000] 76561198000000000 "<player>" added IsoObject (fence_01) at 0,0,0.',
|
||||
"[16-04-26 17:05:03.280][info] Got message:ChatMessage{chat=Local, author='<player>', text='hello'}.",
|
||||
'[16-04-26 17:14:35.128][INFO] Combat: "<player>" (0,0,0) hit "Player2" (1006,2005,0) weapon="Tire Iron (Worn)" damage=0.112317.',
|
||||
'[16-04-26 16:17:49.731][LOG] Safety: "<player>" (0,0,0) restore true.',
|
||||
'[16-04-26 12:00:02.000] [76561198000000000][ISEnterVehicle][Player2][0,0,0][Van_LectroMax].',
|
||||
]);
|
||||
|
||||
$output = (new ProjectZomboidRedactor())->redact($input);
|
||||
|
||||
$this->assertSame($expected, $output, 'With all three toggles on, every Steam ID, player name context, and coord shape must be replaced.');
|
||||
}
|
||||
|
||||
public function testSteamIdToggleOffLeavesSteamIdsIntact(): void
|
||||
{
|
||||
// All three PII categories present; Steam ID toggle is disabled.
|
||||
//
|
||||
// Important nuance: PLAYER_AFTER_STEAMID_REGEX anchors on the redacted placeholder
|
||||
// 76561198000000000. With redactSteamIds(false) the raw Steam ID survives, so the
|
||||
// regex does NOT fire for lines in the "after-Steam-ID" shape — those names survive
|
||||
// too. Names anchored by other contexts (ChatMessage author, Combat:/Safety:) are
|
||||
// still redacted because those regexes don't depend on the Steam ID pass.
|
||||
$input = implode("\n", [
|
||||
// after-Steam-ID shape: name will NOT be redacted because the Steam ID is raw
|
||||
'[16-04-26 12:00:00.000] 76561198111111111 "Player1" added Base.Aerosolbomb at 1000,2000,0.',
|
||||
// ChatMessage author: still redacted (anchor is independent of Steam ID pass)
|
||||
"[16-04-26 17:05:03.280][info] Got message:ChatMessage{chat=Local, author='AdminUser', text='hello'}.",
|
||||
// Combat: name + attacker coords
|
||||
'[16-04-26 17:14:35.128][INFO] Combat: "Player2" (1005,2005,0) hit "Player1" (1006,2005,0) weapon="Pipe Bomb" damage=1.0.',
|
||||
]);
|
||||
|
||||
$expected = implode("\n", [
|
||||
// Steam ID intact; "Player1" NOT redacted (anchor regex didn't fire)
|
||||
'[16-04-26 12:00:00.000] 76561198111111111 "Player1" added Base.Aerosolbomb at 0,0,0.',
|
||||
// ChatMessage name redacted; coords were an at-clause → redacted
|
||||
"[16-04-26 17:05:03.280][info] Got message:ChatMessage{chat=Local, author='<player>', text='hello'}.",
|
||||
// Combat: name + attacker coords both redacted
|
||||
'[16-04-26 17:14:35.128][INFO] Combat: "<player>" (0,0,0) hit "Player1" (1006,2005,0) weapon="Pipe Bomb" damage=1.0.',
|
||||
]);
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redact($input);
|
||||
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
$output,
|
||||
'With Steam ID toggle off: raw Steam IDs survive; PLAYER_AFTER_STEAMID_REGEX does not fire (no placeholder to anchor on) so those names also survive; ChatMessage and Combat:/Safety: names are still redacted; coords are still redacted.',
|
||||
);
|
||||
}
|
||||
|
||||
public function testPlayerNameToggleOffLeavesNamesIntact(): void
|
||||
{
|
||||
// Steam IDs and coords redact; player names survive verbatim.
|
||||
$input = implode("\n", [
|
||||
'[16-04-26 12:00:00.000] 76561198111111111 "Player1" added Base.Aerosolbomb at 1000,2000,0.',
|
||||
"[16-04-26 17:05:03.280][info] Got message:ChatMessage{chat=Local, author='Player2', text='bye'}.",
|
||||
'[16-04-26 16:17:49.731][LOG] Safety: "AdminUser" (1050,2050,0) restore true.',
|
||||
]);
|
||||
|
||||
$expected = implode("\n", [
|
||||
'[16-04-26 12:00:00.000] 76561198000000000 "Player1" added Base.Aerosolbomb at 0,0,0.',
|
||||
"[16-04-26 17:05:03.280][info] Got message:ChatMessage{chat=Local, author='Player2', text='bye'}.",
|
||||
'[16-04-26 16:17:49.731][LOG] Safety: "AdminUser" (0,0,0) restore true.',
|
||||
]);
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactPlayerNames(false)
|
||||
->redact($input);
|
||||
|
||||
$this->assertSame($expected, $output, 'With player-name toggle off, all player names must survive; Steam IDs and coords must still be redacted.');
|
||||
}
|
||||
|
||||
public function testCoordinatesToggleOffLeavesCoordsIntact(): void
|
||||
{
|
||||
// Steam IDs and player names redact; coordinates survive verbatim.
|
||||
$input = implode("\n", [
|
||||
'[16-04-26 12:00:00.000] 76561198111111111 "Player1" added Base.Aerosolbomb at 1000,2000,0.',
|
||||
'[16-04-26 12:00:01.000] [76561198222222222][ISEnterVehicle][Player2][1020,2020,0][Van_LectroMax].',
|
||||
'[16-04-26 17:14:35.128][INFO] Combat: "AdminUser" (1005,2005,0) hit "Player1" (1006,2005,0) weapon="Baseball Bat" damage=0.5.',
|
||||
]);
|
||||
|
||||
$expected = implode("\n", [
|
||||
'[16-04-26 12:00:00.000] 76561198000000000 "<player>" added Base.Aerosolbomb at 1000,2000,0.',
|
||||
'[16-04-26 12:00:01.000] [76561198000000000][ISEnterVehicle][Player2][1020,2020,0][Van_LectroMax].',
|
||||
'[16-04-26 17:14:35.128][INFO] Combat: "<player>" (1005,2005,0) hit "Player1" (1006,2005,0) weapon="Baseball Bat" damage=0.5.',
|
||||
]);
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactCoordinates(false)
|
||||
->redact($input);
|
||||
|
||||
$this->assertSame($expected, $output, 'With coordinates toggle off, all coord triplets must survive; Steam IDs and player names must still be redacted.');
|
||||
}
|
||||
|
||||
public function testAllTogglesOffReturnsInputByteForByte(): void
|
||||
{
|
||||
// Disabling every toggle must produce an output identical to the input —
|
||||
// the "passthrough" contract: opt-out means truly nothing happens.
|
||||
$input = implode("\n", [
|
||||
'[16-04-26 12:00:00.000] 76561198111111111 "Player1" added Base.Aerosolbomb at 1000,2000,0.',
|
||||
"[16-04-26 17:05:03.280][info] Got message:ChatMessage{chat=Local, author='Player2', text='hello'}.",
|
||||
'[16-04-26 17:14:35.128][INFO] Combat: "AdminUser" (1005,2005,0) hit "Player1" (1006,2005,0) weapon="Tire Iron (Worn)" damage=0.112317.',
|
||||
'[16-04-26 12:00:01.000] [76561198333333333][ISEnterVehicle][Player2][1020,2020,0][Van_LectroMax].',
|
||||
]);
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redactPlayerNames(false)
|
||||
->redactCoordinates(false)
|
||||
->redact($input);
|
||||
|
||||
$this->assertSame($input, $output, 'With all three toggles disabled, the output must be byte-for-byte identical to the input.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace IndifferentKetchup\Codex\Test\Tests\Util\Redactor;
|
||||
|
||||
use IndifferentKetchup\Codex\Util\ProjectZomboid\ProjectZomboidRedactor;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Verifies the idempotence property of ProjectZomboidRedactor::redact().
|
||||
*
|
||||
* Idempotence: redact(redact(x)) === redact(x) for all valid inputs.
|
||||
*
|
||||
* A downstream consumer might accidentally double-pipe content through the
|
||||
* Redactor. The result must be stable — a second pass must make no further
|
||||
* changes. If a regex were poorly anchored such that the post-redact placeholder
|
||||
* itself matched and was re-redacted to something different, idempotence would
|
||||
* fail. Specifically, the player-name regex PLAYER_AFTER_STEAMID_REGEX anchors
|
||||
* on 76561198000000000 — the same value the Steam ID pass writes. This test
|
||||
* suite verifies that applying redact() twice is safe: on the second pass, names
|
||||
* already written as <player> do not accidentally re-match and produce a doubly-
|
||||
* nested result like "<player>" → something else.
|
||||
*/
|
||||
class ProjectZomboidRedactorIdempotenceTest extends TestCase
|
||||
{
|
||||
public function testIdempotenceSteamIdOnly(): void
|
||||
{
|
||||
$input = implode("\n", [
|
||||
'Players: 76561198111111111, 76561198222222222, 76561198333333333 connected.',
|
||||
'[16-04-26 12:00:00.000] [76561198111111111][ISEnterVehicle][Player1][1000,2000,0][Van_LectroMax].',
|
||||
]);
|
||||
|
||||
$redactor = new ProjectZomboidRedactor();
|
||||
$redacted = $redactor->redact($input);
|
||||
$redactedAgain = $redactor->redact($redacted);
|
||||
|
||||
$this->assertSame($redacted, $redactedAgain, 'Applying redact() twice to Steam-ID-only input must produce the same result as applying it once.');
|
||||
}
|
||||
|
||||
public function testIdempotencePlayerNamesOnly(): void
|
||||
{
|
||||
// Input already has the Steam ID placeholder in place (as the Steam ID pass
|
||||
// would have written it), so PLAYER_AFTER_STEAMID_REGEX can fire. After the
|
||||
// first pass the name becomes "<player>"; the second pass must leave "<player>"
|
||||
// untouched — it is not a valid display name inside double quotes preceded
|
||||
// by the Steam ID placeholder anchor in a way that would re-match, because
|
||||
// the replacement written is: 76561198000000000 "<player>", and the regex
|
||||
// would need an unquoted player name inside quotes after the placeholder.
|
||||
// "<player>" (with the angle brackets) does satisfy [^"]+ but the second
|
||||
// pass must still produce an identical result.
|
||||
$input = implode("\n", [
|
||||
'76561198000000000 "Player1" ISLogSystem.writeLog @ 1000,2000,0.',
|
||||
"[16-04-26 17:05:03.280][info] Got message:ChatMessage{chat=Local, author='AdminUser', text='hi'}.",
|
||||
'[16-04-26 16:17:49.731][LOG] Safety: "Player2" (1000,2000,0) restore true.',
|
||||
]);
|
||||
|
||||
$redactor = (new ProjectZomboidRedactor())->redactSteamIds(false)->redactCoordinates(false);
|
||||
$redacted = $redactor->redact($input);
|
||||
$redactedAgain = $redactor->redact($redacted);
|
||||
|
||||
$this->assertSame($redacted, $redactedAgain, 'Applying redact() twice to player-name-only input must produce the same result as applying it once.');
|
||||
}
|
||||
|
||||
public function testIdempotenceCoordsOnly(): void
|
||||
{
|
||||
$input = implode("\n", [
|
||||
'[16-04-26 12:00:00.000] 76561198000000001 "Player1" added Base.Aerosolbomb at 1000,2000,0.',
|
||||
'[16-04-26 12:00:01.000] [76561198000000001][ISEnterVehicle][Player1][1020,2020,-1][Van_LectroMax].',
|
||||
'[16-04-26 17:14:35.128][INFO] Combat: "Player1" (1005,2005,0) hit "Player2" (1006,2005,0) weapon="Tire Iron (Worn)" damage=0.112317.',
|
||||
'[16-04-26 16:17:49.731][LOG] Safety: "Player1" (1000,2000,0) restore true.',
|
||||
]);
|
||||
|
||||
$redactor = (new ProjectZomboidRedactor())->redactSteamIds(false)->redactPlayerNames(false);
|
||||
$redacted = $redactor->redact($input);
|
||||
$redactedAgain = $redactor->redact($redacted);
|
||||
|
||||
$this->assertSame($redacted, $redactedAgain, 'Applying redact() twice to coords-only input must produce the same result as applying it once; the placeholder 0,0,0 must not be re-matched.');
|
||||
}
|
||||
|
||||
public function testIdempotenceAllCategories(): void
|
||||
{
|
||||
// Full input: all three PII categories in multiple lexical contexts.
|
||||
// After the first redact(), every placeholder is in place. The second
|
||||
// redact() must make no further changes.
|
||||
$input = implode("\n", [
|
||||
'[16-04-26 12:00:00.000] 76561198111111111 "Player1" added Base.Aerosolbomb at 1000,2000,0.',
|
||||
'[16-04-26 12:00:01.000] 76561198222222222 "Player2" teleported to 1050,2050,0.',
|
||||
"[16-04-26 17:05:03.280][info] Got message:ChatMessage{chat=Local, author='AdminUser', text='hello'}.",
|
||||
'[16-04-26 17:14:35.128][INFO] Combat: "Player1" (1005,2005,0) hit "Player2" (1006,2005,0) weapon="Tire Iron (Worn)" damage=0.112317.',
|
||||
'[16-04-26 16:17:49.731][LOG] Safety: "Player1" (1000,2000,0) restore true.',
|
||||
'[16-04-26 12:00:02.000] [76561198333333333][ISEnterVehicle][Player2][1020,2020,0][Van_LectroMax].',
|
||||
]);
|
||||
|
||||
$redactor = new ProjectZomboidRedactor();
|
||||
$redacted = $redactor->redact($input);
|
||||
$redactedAgain = $redactor->redact($redacted);
|
||||
|
||||
$this->assertSame($redacted, $redactedAgain, 'Applying redact() twice to input with all PII categories must produce the same result as applying it once; no placeholder must re-match on the second pass.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user