## Summary - add persisted customer review acknowledgement truth with capability gating and audit emission - extend the customer review workspace with acknowledgement state, evidence basis details, and accepted-risk lifecycle visibility - add focused feature and browser coverage plus Spec 343 screenshot artifacts and UI audit updates ## Scope - Livewire v4 / Filament v5 surface only; no panel provider changes - no new global assets; no `filament:assets` deployment change for this slice - includes a PostgreSQL migration for `environment_review_acknowledgements` ## Guardrail / Exception / Smoke Coverage - reachable UI surface changed: existing `/admin/reviews/workspace` customer-safe page - UI audit updated in `docs/ui-ux-enterprise-audit/page-reports/ui-006-customer-review-workspace.md` - screenshot artifacts included under `specs/343-customer-review-attestation-accepted-risk-lifecycle/artifacts/screenshots/` - spec package includes plan, tasks, repo-truth map, and state contract for the implemented slice ## Notes - target branch requested: `platform-dev` - branch pushed from commit `aaaad441fd13dbac54e971ab48765c502ced6b3f` Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #415
356 lines
15 KiB
PHP
356 lines
15 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
|
|
use App\Models\EnvironmentReview;
|
|
use App\Models\EnvironmentReviewAcknowledgement;
|
|
use App\Models\EvidenceSnapshot;
|
|
use App\Models\Finding;
|
|
use App\Models\FindingException;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\OperationRun;
|
|
use App\Models\ReviewPack;
|
|
use App\Models\User;
|
|
use App\Support\EnvironmentReviewCompletenessState;
|
|
use App\Support\EnvironmentReviewStatus;
|
|
use App\Support\Governance\Controls\ComplianceEvidenceMappingV1;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\OperationRunType;
|
|
use App\Support\ReviewPackStatus;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
pest()->browser()->timeout(60_000);
|
|
|
|
beforeEach(function (): void {
|
|
Storage::fake('exports');
|
|
});
|
|
|
|
it('Spec343 smokes customer review acknowledgement and accepted risk lifecycle surfaces', function (): void {
|
|
[$user, $ackRequiredEnvironment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
$ackRequiredEnvironment->forceFill(['name' => 'Spec343 Browser Ack Required'])->save();
|
|
|
|
$acknowledgedEnvironment = spec343BrowserEnvironmentFor($user, $ackRequiredEnvironment, 'Spec343 Browser Acknowledged');
|
|
$acceptedRiskEnvironment = spec343BrowserEnvironmentFor($user, $ackRequiredEnvironment, 'Spec343 Browser Accepted Risks');
|
|
$acceptedRiskExpiredEnvironment = spec343BrowserEnvironmentFor($user, $ackRequiredEnvironment, 'Spec343 Browser Accepted Risk Expired');
|
|
$noAcceptedRiskEnvironment = spec343BrowserEnvironmentFor($user, $ackRequiredEnvironment, 'Spec343 Browser No Accepted Risks');
|
|
|
|
[$ackRequiredReview] = spec343BrowserCreatePublishedReviewWithPack(
|
|
$ackRequiredEnvironment,
|
|
$user,
|
|
seedEnvironmentReviewEvidence($ackRequiredEnvironment, findingCount: 0, driftCount: 0),
|
|
[],
|
|
'review-packs/spec343-browser-ack-required.zip',
|
|
);
|
|
|
|
[$acknowledgedReview, $acknowledgedPack] = spec343BrowserCreatePublishedReviewWithPack(
|
|
$acknowledgedEnvironment,
|
|
$user,
|
|
seedEnvironmentReviewEvidence($acknowledgedEnvironment, findingCount: 0, driftCount: 0),
|
|
[],
|
|
'review-packs/spec343-browser-acknowledged.zip',
|
|
);
|
|
|
|
EnvironmentReviewAcknowledgement::query()->create([
|
|
'managed_environment_id' => (int) $acknowledgedEnvironment->getKey(),
|
|
'workspace_id' => (int) $acknowledgedEnvironment->workspace_id,
|
|
'environment_review_id' => (int) $acknowledgedReview->getKey(),
|
|
'acknowledged_at' => now()->subHours(2),
|
|
'acknowledged_by_user_id' => (int) $user->getKey(),
|
|
'comment' => 'Review pack reviewed and understood.',
|
|
'evidence_snapshot_id' => (int) $acknowledgedReview->evidence_snapshot_id,
|
|
'review_pack_id' => (int) $acknowledgedPack->getKey(),
|
|
]);
|
|
|
|
spec343BrowserCreatePublishedReviewWithPack(
|
|
$acceptedRiskEnvironment,
|
|
$user,
|
|
seedEnvironmentReviewEvidence($acceptedRiskEnvironment, findingCount: 0, driftCount: 0),
|
|
[
|
|
'governance_package' => [
|
|
'accepted_risks' => [
|
|
[
|
|
'title' => 'Accepted risk renewal',
|
|
'governance_state' => 'expiring_exception',
|
|
'customer_summary' => 'Accepted risk requires customer awareness.',
|
|
],
|
|
],
|
|
],
|
|
],
|
|
'review-packs/spec343-browser-accepted-risk.zip',
|
|
);
|
|
spec343BrowserCreateAcceptedRisk($acceptedRiskEnvironment, $user, state: FindingException::VALIDITY_EXPIRING);
|
|
|
|
spec343BrowserCreatePublishedReviewWithPack(
|
|
$acceptedRiskExpiredEnvironment,
|
|
$user,
|
|
seedEnvironmentReviewEvidence($acceptedRiskExpiredEnvironment, findingCount: 0, driftCount: 0),
|
|
[],
|
|
'review-packs/spec343-browser-accepted-risk-expired.zip',
|
|
);
|
|
spec343BrowserCreateAcceptedRisk($acceptedRiskExpiredEnvironment, $user, state: FindingException::VALIDITY_EXPIRED);
|
|
|
|
spec343BrowserCreatePublishedReviewWithPack(
|
|
$noAcceptedRiskEnvironment,
|
|
$user,
|
|
seedEnvironmentReviewEvidence($noAcceptedRiskEnvironment, findingCount: 0, driftCount: 0),
|
|
[],
|
|
'review-packs/spec343-browser-no-accepted-risk.zip',
|
|
);
|
|
|
|
spec343AuthenticateBrowser($this, $user, $ackRequiredEnvironment);
|
|
|
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($ackRequiredEnvironment))
|
|
->resize(1236, 900)
|
|
->waitForText(__('localization.review.review_acknowledgement'))
|
|
->assertSee(__('localization.review.acknowledgement_required'))
|
|
->assertSee(__('localization.review.acknowledge_review'))
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
|
|
$page->screenshot(true, spec343BrowserScreenshotName('01-acknowledgement-required'));
|
|
spec343CopyBrowserScreenshot('01-acknowledgement-required');
|
|
|
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($acknowledgedEnvironment))
|
|
->waitForText(__('localization.review.review_acknowledgement'))
|
|
->assertSee(__('localization.review.review_acknowledged'))
|
|
->assertSee('Review pack reviewed and understood.')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
|
|
$page->screenshot(true, spec343BrowserScreenshotName('02-review-acknowledged'));
|
|
spec343CopyBrowserScreenshot('02-review-acknowledged');
|
|
|
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($acceptedRiskEnvironment))
|
|
->waitForText(__('localization.review.accepted_risk_summary'))
|
|
->assertSee(__('localization.review.accepted_risk_accountability'))
|
|
->assertSee(__('localization.review.accepted_risk_follow_up'))
|
|
->assertSee('Accepted risk requires customer awareness.')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
|
|
$page->screenshot(true, spec343BrowserScreenshotName('03-accepted-risks-present'));
|
|
spec343CopyBrowserScreenshot('03-accepted-risks-present');
|
|
|
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($acceptedRiskEnvironment))
|
|
->waitForText(__('localization.review.accepted_risk_accountability'))
|
|
->assertSee(__('localization.review.accepted_risks_expiring_soon'))
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
|
|
$page->screenshot(true, spec343BrowserScreenshotName('04-accepted-risk-due-for-review'));
|
|
spec343CopyBrowserScreenshot('04-accepted-risk-due-for-review');
|
|
|
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($acceptedRiskExpiredEnvironment))
|
|
->waitForText(__('localization.review.accepted_risk_summary'))
|
|
->assertSee(__('localization.review.accepted_risks_expired'))
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
|
|
$page->screenshot(true, spec343BrowserScreenshotName('05-accepted-risk-expired'));
|
|
spec343CopyBrowserScreenshot('05-accepted-risk-expired');
|
|
|
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($noAcceptedRiskEnvironment))
|
|
->waitForText(__('localization.review.accepted_risk_summary'))
|
|
->assertSee(__('localization.review.no_accepted_risks_recorded'))
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
|
|
$page->screenshot(true, spec343BrowserScreenshotName('06-no-accepted-risks'));
|
|
spec343CopyBrowserScreenshot('06-no-accepted-risks');
|
|
|
|
$page->assertScript('document.querySelector("[data-testid=\"customer-review-diagnostics\"]")?.open === false', true);
|
|
$page->screenshot(true, spec343BrowserScreenshotName('07-diagnostics-collapsed'));
|
|
spec343CopyBrowserScreenshot('07-diagnostics-collapsed');
|
|
|
|
$page->script("document.documentElement.classList.add('dark');");
|
|
$page->script('window.scrollTo(0, 0);');
|
|
$page->assertScript('document.documentElement.classList.contains("dark")', true);
|
|
$page->screenshot(true, spec343BrowserScreenshotName('08-dark-mode'));
|
|
spec343CopyBrowserScreenshot('08-dark-mode');
|
|
|
|
expect(EnvironmentReviewAcknowledgement::query()
|
|
->where('environment_review_id', (int) $acknowledgedReview->getKey())
|
|
->exists())->toBeTrue();
|
|
|
|
expect(EnvironmentReviewAcknowledgement::query()
|
|
->where('environment_review_id', (int) $ackRequiredReview->getKey())
|
|
->exists())->toBeFalse();
|
|
});
|
|
|
|
function spec343BrowserScreenshotName(string $name): string
|
|
{
|
|
return 'spec343-customer-review-attestation-'.$name;
|
|
}
|
|
|
|
function spec343CopyBrowserScreenshot(string $name): void
|
|
{
|
|
$filename = spec343BrowserScreenshotName($name).'.png';
|
|
$source = base_path('tests/Browser/Screenshots/'.$filename);
|
|
$targetDirectory = repo_path('specs/343-customer-review-attestation-accepted-risk-lifecycle/artifacts/screenshots');
|
|
|
|
if (! is_dir($targetDirectory)) {
|
|
@mkdir($targetDirectory, 0755, true);
|
|
}
|
|
|
|
if (! is_file($source)) {
|
|
$source = \Pest\Browser\Support\Screenshot::path($filename);
|
|
}
|
|
|
|
for ($attempt = 0; $attempt < 10 && ! is_file($source); $attempt++) {
|
|
usleep(100_000);
|
|
clearstatcache(true, $source);
|
|
}
|
|
|
|
if (is_file($source) && is_dir($targetDirectory) && is_writable($targetDirectory)) {
|
|
@copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$name.'.png');
|
|
}
|
|
}
|
|
|
|
function spec343AuthenticateBrowser(mixed $test, User $user, ManagedEnvironment $environment): void
|
|
{
|
|
$workspaceId = (int) $environment->workspace_id;
|
|
|
|
$test->actingAs($user)->withSession([
|
|
WorkspaceContext::SESSION_KEY => $workspaceId,
|
|
WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY => [
|
|
(string) $workspaceId => (int) $environment->getKey(),
|
|
],
|
|
]);
|
|
|
|
session()->put(WorkspaceContext::SESSION_KEY, $workspaceId);
|
|
session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
|
|
(string) $workspaceId => (int) $environment->getKey(),
|
|
]);
|
|
|
|
setAdminPanelContext($environment);
|
|
}
|
|
|
|
function spec343BrowserEnvironmentFor(User $user, ManagedEnvironment $baseEnvironment, string $name): ManagedEnvironment
|
|
{
|
|
$environment = ManagedEnvironment::factory()->active()->create([
|
|
'workspace_id' => (int) $baseEnvironment->workspace_id,
|
|
'name' => $name,
|
|
]);
|
|
|
|
createUserWithTenant(tenant: $environment, user: $user, role: 'owner', workspaceRole: 'manager');
|
|
|
|
return $environment;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $summaryOverrides
|
|
* @return array{0: EnvironmentReview, 1: ReviewPack}
|
|
*/
|
|
function spec343BrowserCreatePublishedReviewWithPack(
|
|
ManagedEnvironment $environment,
|
|
User $user,
|
|
EvidenceSnapshot $snapshot,
|
|
array $summaryOverrides = [],
|
|
string $filePath = 'review-packs/spec343-browser-review-pack.zip',
|
|
): array {
|
|
$review = composeEnvironmentReviewForTest($environment, $user, $snapshot);
|
|
$summary = array_replace_recursive(
|
|
is_array($review->summary) ? $review->summary : [],
|
|
[
|
|
'control_interpretation' => [
|
|
'version_key' => ComplianceEvidenceMappingV1::VERSION_KEY,
|
|
'controls' => [
|
|
[
|
|
'control_key' => 'customer-output',
|
|
'title' => 'Customer output',
|
|
'readiness_bucket' => 'evidence_on_record',
|
|
'readiness_label' => 'Evidence on record',
|
|
'primary_reason' => 'Evidence path is complete.',
|
|
'recommended_next_action' => 'Open the current customer review pack.',
|
|
],
|
|
],
|
|
],
|
|
'governance_package' => [
|
|
'decision_summary' => [
|
|
'status' => 'none',
|
|
'evidence_state' => EnvironmentReviewCompletenessState::Complete->value,
|
|
'decision_data_state' => 'complete',
|
|
'total_count' => 0,
|
|
'summary' => 'No governance decisions require customer awareness.',
|
|
'next_action' => 'Open the current customer review pack.',
|
|
'entries' => [],
|
|
],
|
|
],
|
|
],
|
|
$summaryOverrides,
|
|
);
|
|
|
|
$run = OperationRun::factory()->forTenant($environment)->create([
|
|
'type' => OperationRunType::ReviewPackGenerate->value,
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Succeeded->value,
|
|
'started_at' => now()->subMinutes(6),
|
|
'completed_at' => now()->subMinutes(4),
|
|
'initiator_name' => 'Spec343 Browser Operator',
|
|
]);
|
|
|
|
Storage::disk('exports')->put($filePath, 'PK-spec343-browser-test');
|
|
|
|
$review->forceFill([
|
|
'status' => EnvironmentReviewStatus::Published->value,
|
|
'completeness_state' => EnvironmentReviewCompletenessState::Complete->value,
|
|
'summary' => $summary,
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
'generated_at' => now()->subMinutes(5),
|
|
'published_at' => now()->subMinutes(3),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'environment_review_id' => (int) $review->getKey(),
|
|
'evidence_snapshot_id' => (int) $snapshot->getKey(),
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
'status' => ReviewPackStatus::Ready->value,
|
|
'file_path' => $filePath,
|
|
'file_disk' => 'exports',
|
|
'generated_at' => now()->subMinutes(4),
|
|
]);
|
|
|
|
$review->forceFill([
|
|
'current_export_review_pack_id' => (int) $pack->getKey(),
|
|
])->save();
|
|
|
|
return [$review->refresh(), $pack->refresh()];
|
|
}
|
|
|
|
function spec343BrowserCreateAcceptedRisk(ManagedEnvironment $environment, User $user, string $state): void
|
|
{
|
|
$owner = User::factory()->create(['name' => 'Spec343 Browser Risk Owner']);
|
|
|
|
$finding = Finding::factory()->riskAccepted()->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
]);
|
|
|
|
FindingException::query()->create([
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'finding_id' => (int) $finding->getKey(),
|
|
'status' => FindingException::STATUS_ACTIVE,
|
|
'current_validity_state' => $state,
|
|
'requested_by_user_id' => (int) $user->getKey(),
|
|
'request_reason' => 'Customer-approved maintenance window.',
|
|
'owner_user_id' => (int) $owner->getKey(),
|
|
'approved_by_user_id' => (int) $owner->getKey(),
|
|
'requested_at' => now()->subDays(3),
|
|
'approved_at' => now()->subDays(2),
|
|
'effective_from' => now()->subDays(2),
|
|
'review_due_at' => now()->addDays(30),
|
|
'expires_at' => $state === FindingException::VALIDITY_EXPIRED ? now()->subDay() : null,
|
|
]);
|
|
}
|