forked from fa/breadcrumb-the-shire
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>
116 lines
3.8 KiB
PHP
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([]));
|
|
}
|
|
}
|