## 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
158 lines
6.6 KiB
PHP
158 lines
6.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
|
|
use App\Models\AuditLog;
|
|
use App\Models\EnvironmentReview;
|
|
use App\Models\EnvironmentReviewAcknowledgement;
|
|
use App\Models\EvidenceSnapshot;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\User;
|
|
use App\Services\EnvironmentReviews\EnvironmentReviewAcknowledgementService;
|
|
use App\Support\Audit\AuditActionId;
|
|
use App\Support\EnvironmentReviewStatus;
|
|
use App\Support\Evidence\EvidenceSnapshotStatus;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Filament\Actions\Action;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Livewire\Livewire;
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
it('renders acknowledgement required for a published review and allows authorized acknowledgement', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create(['name' => 'Spec343 Ack Required']);
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner', workspaceRole: 'manager');
|
|
|
|
$review = spec343CreatePublishedReview($tenant, $user, seedEnvironmentReviewEvidence($tenant, findingCount: 0, driftCount: 0));
|
|
|
|
$this->actingAs($user);
|
|
setAdminPanelContext();
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
|
|
|
$component = Livewire::actingAs($user)->test(CustomerReviewWorkspace::class)
|
|
->assertSee(__('localization.review.review_acknowledgement'))
|
|
->assertSee(__('localization.review.acknowledgement_required'))
|
|
->assertSee(__('localization.review.acknowledge_review'))
|
|
->assertActionExists('acknowledgeReview', fn (Action $action): bool => $action->isConfirmationRequired());
|
|
|
|
$payload = $component->instance()->latestReviewConsumptionPayload();
|
|
|
|
expect(data_get($payload, 'acknowledgement.state'))->toBe('required');
|
|
|
|
$component
|
|
->mountAction('acknowledgeReview')
|
|
->setActionData([
|
|
'comment' => 'Reviewed with stakeholders.',
|
|
])
|
|
->callMountedAction()
|
|
->assertNotified(__('localization.review.review_acknowledged'));
|
|
|
|
$ack = EnvironmentReviewAcknowledgement::query()
|
|
->where('environment_review_id', (int) $review->getKey())
|
|
->first();
|
|
|
|
expect($ack)->toBeInstanceOf(EnvironmentReviewAcknowledgement::class)
|
|
->and((int) $ack?->managed_environment_id)->toBe((int) $tenant->getKey())
|
|
->and((int) $ack?->workspace_id)->toBe((int) $tenant->workspace_id)
|
|
->and((int) $ack?->environment_review_id)->toBe((int) $review->getKey())
|
|
->and((int) $ack?->acknowledged_by_user_id)->toBe((int) $user->getKey())
|
|
->and($ack?->comment)->toBe('Reviewed with stakeholders.')
|
|
->and((int) $ack?->evidence_snapshot_id)->toBe((int) $review->evidence_snapshot_id);
|
|
|
|
$audit = AuditLog::query()
|
|
->where('action', AuditActionId::EnvironmentReviewAcknowledged->value)
|
|
->where('resource_type', 'environment_review')
|
|
->where('resource_id', (string) $review->getKey())
|
|
->latest('id')
|
|
->first();
|
|
|
|
expect($audit)->not->toBeNull()
|
|
->and((int) $audit?->managed_environment_id)->toBe((int) $tenant->getKey())
|
|
->and((int) data_get($audit?->metadata, 'review_id'))->toBe((int) $review->getKey());
|
|
});
|
|
|
|
it('surfaces re-ack required when the evidence basis changes after acknowledgement', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create(['name' => 'Spec343 Re-ack']);
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner', workspaceRole: 'manager');
|
|
|
|
$snapshotA = seedEnvironmentReviewEvidence($tenant, findingCount: 0, driftCount: 0);
|
|
$review = spec343CreatePublishedReview($tenant, $user, $snapshotA);
|
|
|
|
$this->actingAs($user);
|
|
setAdminPanelContext();
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(CustomerReviewWorkspace::class)
|
|
->mountAction('acknowledgeReview')
|
|
->callMountedAction()
|
|
->assertNotified(__('localization.review.review_acknowledged'));
|
|
|
|
$snapshotA->forceFill([
|
|
'status' => EvidenceSnapshotStatus::Superseded->value,
|
|
])->save();
|
|
|
|
$snapshotB = seedEnvironmentReviewEvidence($tenant, findingCount: 0, driftCount: 0);
|
|
|
|
$review->forceFill([
|
|
'evidence_snapshot_id' => (int) $snapshotB->getKey(),
|
|
])->save();
|
|
|
|
$component = Livewire::actingAs($user)->test(CustomerReviewWorkspace::class);
|
|
$payload = $component->instance()->latestReviewConsumptionPayload();
|
|
|
|
expect(data_get($payload, 'acknowledgement.state'))->toBe('re_ack_required');
|
|
});
|
|
|
|
it('shows a visible empty state when no accepted risks are recorded', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create(['name' => 'Spec343 Empty Accepted Risks']);
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner', workspaceRole: 'manager');
|
|
|
|
spec343CreatePublishedReview($tenant, $user, seedEnvironmentReviewEvidence($tenant, findingCount: 0, driftCount: 0));
|
|
|
|
$this->actingAs($user);
|
|
setAdminPanelContext();
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(CustomerReviewWorkspace::class)
|
|
->assertSee(__('localization.review.accepted_risk_summary'))
|
|
->assertSee(__('localization.review.no_accepted_risks_recorded'));
|
|
});
|
|
|
|
it('denies acknowledgement outside the actor tenant scope as not found', function (): void {
|
|
[$actor] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
|
|
$otherTenant = ManagedEnvironment::factory()->create(['name' => 'Spec343 Other Tenant']);
|
|
[$creator, $otherTenant] = createUserWithTenant(tenant: $otherTenant, role: 'owner', workspaceRole: 'manager');
|
|
|
|
$review = spec343CreatePublishedReview(
|
|
$otherTenant,
|
|
$creator,
|
|
seedEnvironmentReviewEvidence($otherTenant, findingCount: 0, driftCount: 0),
|
|
);
|
|
|
|
expect(fn (): mixed => app(EnvironmentReviewAcknowledgementService::class)->acknowledge(
|
|
tenant: $otherTenant,
|
|
review: $review,
|
|
actor: $actor,
|
|
comment: null,
|
|
))->toThrow(NotFoundHttpException::class);
|
|
});
|
|
|
|
function spec343CreatePublishedReview(ManagedEnvironment $tenant, User $user, EvidenceSnapshot $snapshot): EnvironmentReview
|
|
{
|
|
$review = composeEnvironmentReviewForTest($tenant, $user, $snapshot);
|
|
|
|
$review->forceFill([
|
|
'status' => EnvironmentReviewStatus::Published->value,
|
|
'generated_at' => now(),
|
|
'published_at' => now(),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
return $review->refresh();
|
|
}
|