feat: add player name redaction pass
Adds three lexical-context regexes (after-SteamID, ChatMessage author, Combat/Safety pvp subsystem) and wires the player-name branch in redact(). Includes six PHPUnit tests covering all three contexts plus the toggle-off and no-anchor-no-touch cases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,18 @@ class ProjectZomboidRedactor implements RedactorInterface
|
||||
/** Zeroed-out SteamID64 placeholder; syntactically valid but refers to no real account. */
|
||||
public const string STEAM_ID_REPLACEMENT = '76561198000000000';
|
||||
|
||||
/** Generic placeholder substituted for every matched player display name. */
|
||||
public const string PLAYER_NAME_REPLACEMENT = '<player>';
|
||||
|
||||
/** Matches a double-quoted player name that immediately follows the redacted Steam ID placeholder (cmd.txt / admin.txt shape); relies on the Steam ID pass having run first. */
|
||||
public const string PLAYER_AFTER_STEAMID_REGEX = '/(?<=76561198000000000) "(?<name>[^"]+)"/u';
|
||||
|
||||
/** Matches the author value inside a ChatMessage{...} envelope, using a fixed-length lookbehind on ", author='" and a lookahead on the closing "'" so only the bare name is replaced. */
|
||||
public const string PLAYER_IN_CHATMESSAGE_REGEX = '/(?<=, author=\')(?<name>[^\']+)(?=\')/u';
|
||||
|
||||
/** Matches the first double-quoted player name following a Combat: or Safety: subsystem token (pvp.txt shape); does NOT redact the second name after "hit" — deferred to v2. */
|
||||
public const string PLAYER_IN_PVP_SUBSYSTEM_REGEX = '/(?<=(?:Combat|Safety): )"(?<name>[^"]+)"/u';
|
||||
|
||||
private bool $redactSteamIds = true;
|
||||
private bool $redactPlayerNames = true;
|
||||
private bool $redactCoordinates = true;
|
||||
@@ -85,7 +97,9 @@ class ProjectZomboidRedactor implements RedactorInterface
|
||||
$content = preg_replace(self::STEAM_ID_REGEX, self::STEAM_ID_REPLACEMENT, $content);
|
||||
}
|
||||
if ($this->redactPlayerNames) {
|
||||
// Player name pass added in Task 3
|
||||
$content = preg_replace(self::PLAYER_AFTER_STEAMID_REGEX, ' "' . self::PLAYER_NAME_REPLACEMENT . '"', $content);
|
||||
$content = preg_replace(self::PLAYER_IN_CHATMESSAGE_REGEX, self::PLAYER_NAME_REPLACEMENT, $content);
|
||||
$content = preg_replace(self::PLAYER_IN_PVP_SUBSYSTEM_REGEX, '"' . self::PLAYER_NAME_REPLACEMENT . '"', $content);
|
||||
}
|
||||
if ($this->redactCoordinates) {
|
||||
// Coordinates pass added in Task 4
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace IndifferentKetchup\Codex\Test\Tests\Util\Redactor;
|
||||
|
||||
use IndifferentKetchup\Codex\Util\ProjectZomboid\ProjectZomboidRedactor;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ProjectZomboidRedactorPlayerNameTest extends TestCase
|
||||
{
|
||||
public function testRedactsPlayerNameAfterRedactedSteamId(): void
|
||||
{
|
||||
// The Steam ID pass has already run; the literal placeholder 76561198000000000
|
||||
// precedes the quoted name. The player-name pass must redact the name.
|
||||
$input = '76561198000000000 "AdminUser" admin.broadcastMessage @ 1020,2020,0.';
|
||||
$expected = '76561198000000000 "<player>" admin.broadcastMessage @ 1020,2020,0.';
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redact($input);
|
||||
|
||||
$this->assertSame($expected, $output, 'Player name following the redacted Steam ID placeholder must be replaced.');
|
||||
}
|
||||
|
||||
public function testRedactsChatMessageAuthor(): void
|
||||
{
|
||||
// The author field inside ChatMessage{...} must be replaced; the text
|
||||
// payload ('hello') is not in scope for player-name redaction and must
|
||||
// survive unchanged.
|
||||
$input = "[16-04-26 17:05:03.280][info] Got message:ChatMessage{chat=Local, author='Player1', text='hello'}.";
|
||||
$expected = "[16-04-26 17:05:03.280][info] Got message:ChatMessage{chat=Local, author='<player>', text='hello'}.";
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redact($input);
|
||||
|
||||
$this->assertSame($expected, $output, 'ChatMessage author must be replaced while the text payload remains unchanged.');
|
||||
}
|
||||
|
||||
public function testRedactsCombatNameInPvpLog(): void
|
||||
{
|
||||
// Only the FIRST quoted name (after "Combat: ") is redacted in v1.
|
||||
// The second name (after "hit") is NOT yet redacted — deferred to v2.
|
||||
// The weapon name ("Tire Iron (Worn)") must also survive unchanged.
|
||||
$input = '[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.';
|
||||
$expected = '[16-04-26 17:14:35.128][INFO] Combat: "<player>" (1005,2005,0) hit "Player2" (1006,2005,0) weapon="Tire Iron (Worn)" damage=0.112317.';
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redact($input);
|
||||
|
||||
// Player1 (after "Combat: ") is replaced; Player2 (after "hit") is NOT
|
||||
// replaced in v1 — that anchor is deferred.
|
||||
$this->assertSame($expected, $output, 'First Combat: player name must be replaced; second name and weapon must survive.');
|
||||
}
|
||||
|
||||
public function testRedactsSafetyNameInPvpLog(): void
|
||||
{
|
||||
$input = '[16-04-26 16:17:49.731][LOG] Safety: "Player1" (1000,2000,0) restore true.';
|
||||
$expected = '[16-04-26 16:17:49.731][LOG] Safety: "<player>" (1000,2000,0) restore true.';
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redact($input);
|
||||
|
||||
$this->assertSame($expected, $output, 'Player name following the Safety: token must be replaced.');
|
||||
}
|
||||
|
||||
public function testBareQuotedStringWithoutAnchorIsNotTouched(): void
|
||||
{
|
||||
// "foo" is not preceded by a redacted Steam ID, not inside ChatMessage{...},
|
||||
// and not after Combat:/Safety: — it must pass through unchanged.
|
||||
$input = 'option changed to "foo" successfully.';
|
||||
|
||||
$output = (new ProjectZomboidRedactor())->redact($input);
|
||||
|
||||
$this->assertSame($input, $output, 'A quoted string with no matching anchor must not be redacted.');
|
||||
}
|
||||
|
||||
public function testToggleOffLeavesNamesIntact(): void
|
||||
{
|
||||
$input = '76561198000000000 "Player1" ISLogSystem.writeLog @ 1000,2000,0.';
|
||||
|
||||
$output = (new ProjectZomboidRedactor())
|
||||
->redactSteamIds(false)
|
||||
->redactPlayerNames(false)
|
||||
->redact($input);
|
||||
|
||||
$this->assertSame($input, $output, 'With the player-name toggle disabled the original input must be returned unchanged.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user