1
0
Files
breadcrumb-the-shire/modules/audit/tests/Module/Audit/Service/SystemAuditRowPresenterTest.php
fs 3b6896266e refactor(audit): extract SystemAuditRowPresenter shared by data + export
Move the row formatting that was duplicated between system-audit's
data() and export() endpoints into a single presenter. Both endpoints
now call presentAll() on the same object, so enum label translation,
actor resolution, and timestamp formatting cannot drift between the
Grid.js UI and the CSV download.

- SystemAuditRowPresenter::present()/presentAll(): canonical row shape
  with outcome + outcome_label + outcome_badge, channel + channel_label,
  actor_user_label with display-name-then-email fallback, and safe
  defaults for every missing field.
- data().php shrinks from ~45 to ~15 lines; export().php drops its
  inline outcome/channel/actor resolvers and reads the already-
  resolved fields from the presenter output.
- AuditContainerRegistrar registers the presenter.
- tests/Module/Audit/Service/SystemAuditRowPresenterTest: 9 cases
  covering enum normalization, unknown-value fallback, actor label
  precedence (display name → email → "-"), whitespace trimming, safe
  defaults for missing keys, and iterable input.
- StatusTaxonomyContractFiles: the taxonomy data contract now points
  at the presenter (the single source of truth for badge/label
  resolution) instead of the thin data endpoint, and the presenter is
  added to the literal-guard file list.

Gates: PHPUnit 1889 OK, PHPStan 0 errors, module:sync ok.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:20:39 +02:00

116 lines
3.8 KiB
PHP

<?php
namespace MintyPHP\Tests\Module\Audit\Service;
use MintyPHP\Module\Audit\Service\SystemAuditRowPresenter;
use PHPUnit\Framework\TestCase;
class SystemAuditRowPresenterTest extends TestCase
{
private SystemAuditRowPresenter $presenter;
protected function setUp(): void
{
$this->presenter = new SystemAuditRowPresenter();
}
public function testPresentNormalizesEnumsAndFillsDefaults(): void
{
$out = $this->presenter->present([
'id' => 42,
'outcome' => 'success',
'channel' => 'web',
'event_type' => 'user.login',
]);
$this->assertSame(42, $out['id']);
$this->assertSame('success', $out['outcome']);
$this->assertSame('user.login', $out['event_type']);
$this->assertNotSame('', $out['outcome_label'], 'outcome_label should be translated, not empty');
$this->assertNotSame('', $out['outcome_badge'], 'outcome_badge variant should be set');
$this->assertSame(strtoupper($out['channel_label']), $out['channel_label'], 'channel label is uppercased');
$this->assertSame($out['channel'], $out['channel_label'], 'channel and channel_label stay in sync');
}
public function testPresentFallsBackToDefaultsForUnknownEnumValues(): void
{
$out = $this->presenter->present([
'outcome' => 'not-a-valid-outcome',
'channel' => 'not-a-valid-channel',
]);
$this->assertSame('success', $out['outcome'], 'unknown outcome falls back to success');
$this->assertNotSame('', $out['channel'], 'unknown channel falls back to web/default');
}
public function testActorLabelPrefersDisplayName(): void
{
$out = $this->presenter->present([
'actor_user_display_name' => 'Alice Doe',
'actor_user_email' => 'alice@example.com',
]);
$this->assertSame('Alice Doe', $out['actor_user_label']);
$this->assertSame('alice@example.com', $out['actor_user_email']);
}
public function testActorLabelFallsBackToEmail(): void
{
$out = $this->presenter->present([
'actor_user_display_name' => '',
'actor_user_email' => 'bob@example.com',
]);
$this->assertSame('bob@example.com', $out['actor_user_label']);
}
public function testActorLabelFallsBackToDashWhenNothingAvailable(): void
{
$out = $this->presenter->present([]);
$this->assertSame('-', $out['actor_user_label']);
}
public function testActorLabelTrimsWhitespace(): void
{
$out = $this->presenter->present([
'actor_user_display_name' => " \t ",
'actor_user_email' => ' carol@example.com ',
]);
$this->assertSame('carol@example.com', $out['actor_user_label']);
}
public function testMissingFieldsAreSafeDefaults(): void
{
$out = $this->presenter->present([]);
$this->assertSame(0, $out['id']);
$this->assertSame('', $out['event_type']);
$this->assertSame('', $out['target_type']);
$this->assertSame('', $out['target_uuid']);
$this->assertSame('', $out['request_id']);
$this->assertSame('', $out['error_code']);
$this->assertSame(0, $out['actor_user_id']);
}
public function testPresentAllMapsIterableToList(): void
{
$rows = (function () {
yield ['id' => 1, 'outcome' => 'success'];
yield ['id' => 2, 'outcome' => 'failed'];
})();
$out = $this->presenter->presentAll($rows);
$this->assertCount(2, $out);
$this->assertSame(1, $out[0]['id']);
$this->assertSame(2, $out[1]['id']);
$this->assertSame('failed', $out[1]['outcome']);
}
public function testPresentAllWithEmptyIterableReturnsEmptyArray(): void
{
$this->assertSame([], $this->presenter->presentAll([]));
}
}