Implemented the first version of the PDF and HTML renderer for review packs. Added ReviewPackRenderedReportController and related blade views to render reports. Updated EnvironmentReviewResource, ReviewPackResource, ReviewPackService, and routing. Added new tests for the renderer and download actions, and updated UI documentation. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #427
294 lines
12 KiB
PHP
294 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
|
|
use App\Filament\Pages\Reviews\ReviewRegister;
|
|
use App\Filament\Resources\EnvironmentReviewResource;
|
|
use App\Filament\Resources\EnvironmentReviewResource\Pages\ListEnvironmentReviews;
|
|
use App\Filament\Resources\EnvironmentReviewResource\Pages\ViewEnvironmentReview;
|
|
use App\Models\EnvironmentReview;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\ReviewPack;
|
|
use App\Models\User;
|
|
use App\Services\EnvironmentReviews\EnvironmentReviewLifecycleService;
|
|
use App\Support\EnvironmentReviewStatus;
|
|
use App\Support\Ui\GovernanceActions\GovernanceActionCatalog;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Filament\Actions\Action;
|
|
use Filament\Actions\ActionGroup;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Livewire\Features\SupportTesting\Testable;
|
|
use Livewire\Livewire;
|
|
use Tests\Feature\Concerns\BuildsGovernanceArtifactTruthFixtures;
|
|
|
|
uses(BuildsGovernanceArtifactTruthFixtures::class);
|
|
|
|
beforeEach(function (): void {
|
|
Storage::fake('exports');
|
|
});
|
|
|
|
function environmentReviewContractHeaderActions(Testable $component): array
|
|
{
|
|
$instance = $component->instance();
|
|
|
|
if ($instance->getCachedHeaderActions() === []) {
|
|
$instance->cacheInteractsWithHeaderActions();
|
|
}
|
|
|
|
return $instance->getCachedHeaderActions();
|
|
}
|
|
|
|
it('disables environment-review global search while keeping the view page available for resource inspection', function (): void {
|
|
$reflection = new ReflectionClass(EnvironmentReviewResource::class);
|
|
|
|
expect($reflection->getStaticPropertyValue('isGloballySearchable'))->toBeFalse()
|
|
->and(array_keys(EnvironmentReviewResource::getPages()))->toContain('view');
|
|
});
|
|
|
|
it('keeps environment review list and canonical register empty states to a single CTA', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
$this->actingAs($user);
|
|
setAdminEnvironmentContext($tenant);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ListEnvironmentReviews::class)
|
|
->assertTableEmptyStateActionsExistInOrder(['create_first_review'])
|
|
->assertSee('No environment reviews yet')
|
|
->mountAction('create_review')
|
|
->assertActionMounted('create_review');
|
|
|
|
setAdminPanelContext();
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(ReviewRegister::class)
|
|
->searchTable('no-such-review')
|
|
->assertTableEmptyStateActionsExistInOrder(['clear_filters_empty'])
|
|
->assertSee('No review records match this view');
|
|
});
|
|
|
|
it('keeps environment review list inspection on row click and reserves the row action for executive export', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
$review = composeEnvironmentReviewForTest($tenant, $user);
|
|
|
|
$this->actingAs($user);
|
|
setAdminEnvironmentContext($tenant);
|
|
|
|
$livewire = Livewire::actingAs($user)
|
|
->test(ListEnvironmentReviews::class)
|
|
->assertCanSeeTableRecords([$review]);
|
|
|
|
$table = $livewire->instance()->getTable();
|
|
$rowActionNames = collect($table->getActions())
|
|
->map(static fn ($action): ?string => $action->getName())
|
|
->filter()
|
|
->values()
|
|
->all();
|
|
|
|
expect($rowActionNames)->toEqualCanonicalizing(['export_executive_pack'])
|
|
->and($table->getBulkActions())->toBeEmpty()
|
|
->and($table->getRecordUrl($review))->toBe(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review], $tenant));
|
|
});
|
|
|
|
it('requires confirmation for destructive environment-review actions and preserves disabled management visibility for readonly users', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$owner, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
[$readonly] = createUserWithTenant(tenant: $tenant, user: User::factory()->create(), role: 'readonly');
|
|
$review = composeEnvironmentReviewForTest($tenant, $owner);
|
|
$refreshRule = GovernanceActionCatalog::rule('refresh_review');
|
|
|
|
setAdminEnvironmentContext($tenant);
|
|
|
|
Livewire::actingAs($readonly)
|
|
->test(ViewEnvironmentReview::class, ['record' => $review->getKey()])
|
|
->assertActionVisible('refresh_review')
|
|
->assertActionDisabled('refresh_review')
|
|
->assertActionVisible('publish_review')
|
|
->assertActionDisabled('publish_review')
|
|
->assertActionVisible('export_executive_pack')
|
|
->assertActionDisabled('export_executive_pack')
|
|
->assertActionVisible('archive_review')
|
|
->assertActionDisabled('archive_review');
|
|
|
|
Livewire::actingAs($owner)
|
|
->test(ViewEnvironmentReview::class, ['record' => $review->getKey()])
|
|
->assertActionExists('refresh_review', fn (Action $action): bool => $action->getLabel() === $refreshRule->canonicalLabel
|
|
&& $action->isConfirmationRequired()
|
|
&& $action->getModalHeading() === $refreshRule->modalHeading
|
|
&& $action->getModalDescription() === $refreshRule->modalDescription)
|
|
->mountAction('refresh_review')
|
|
->assertActionMounted('refresh_review');
|
|
|
|
Livewire::actingAs($owner)
|
|
->test(ViewEnvironmentReview::class, ['record' => $review->getKey()])
|
|
->mountAction('publish_review')
|
|
->assertActionMounted('publish_review')
|
|
->callMountedAction()
|
|
->assertHasActionErrors(['publish_reason']);
|
|
|
|
$published = app(EnvironmentReviewLifecycleService::class)->publish($review, $owner, 'Ready for publication.');
|
|
|
|
Livewire::actingAs($owner)
|
|
->test(ViewEnvironmentReview::class, ['record' => $published->getKey()])
|
|
->assertActionExists('create_next_review', fn (Action $action): bool => $action->isConfirmationRequired())
|
|
->mountAction('create_next_review')
|
|
->assertActionMounted('create_next_review');
|
|
|
|
Livewire::actingAs($owner)
|
|
->test(ViewEnvironmentReview::class, ['record' => $published->getKey()])
|
|
->mountAction('archive_review')
|
|
->assertActionMounted('archive_review')
|
|
->callMountedAction()
|
|
->assertHasActionErrors(['archive_reason']);
|
|
});
|
|
|
|
it('keeps environment review header hierarchy to one primary action and moves related links into summary context', function (): void {
|
|
[$owner, $tenant] = createUserWithTenant(role: 'owner');
|
|
$review = composeEnvironmentReviewForTest($tenant, $owner);
|
|
|
|
setAdminEnvironmentContext($tenant);
|
|
|
|
$this->actingAs($owner)
|
|
->get(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review], $tenant))
|
|
->assertOk()
|
|
->assertSee('Related context')
|
|
->assertSee('Evidence snapshot');
|
|
|
|
$component = Livewire::actingAs($owner)
|
|
->test(ViewEnvironmentReview::class, ['record' => $review->getKey()]);
|
|
|
|
$topLevelActionNames = collect(environmentReviewContractHeaderActions($component))
|
|
->reject(static fn ($action): bool => $action instanceof ActionGroup)
|
|
->map(static fn ($action): ?string => $action instanceof Action ? $action->getName() : null)
|
|
->filter()
|
|
->values()
|
|
->all();
|
|
$groupLabels = collect(environmentReviewContractHeaderActions($component))
|
|
->filter(static fn ($action): bool => $action instanceof ActionGroup)
|
|
->map(static fn (ActionGroup $action): string => (string) $action->getLabel())
|
|
->values()
|
|
->all();
|
|
|
|
expect($topLevelActionNames)->toBe(['publish_review'])
|
|
->and($groupLabels)->toBe(['More', 'Danger']);
|
|
});
|
|
|
|
it('uses the current review-pack download as the only customer-workspace detail header action', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'readonly');
|
|
$snapshot = seedEnvironmentReviewEvidence($tenant);
|
|
|
|
$review = composeEnvironmentReviewForTest($tenant, $user, $snapshot);
|
|
$review->forceFill([
|
|
'status' => EnvironmentReviewStatus::Published->value,
|
|
'published_at' => now(),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
Storage::disk('exports')->put('review-packs/customer-detail-primary.zip', 'PK-test');
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'environment_review_id' => (int) $review->getKey(),
|
|
'evidence_snapshot_id' => (int) $snapshot->getKey(),
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
'file_path' => 'review-packs/customer-detail-primary.zip',
|
|
'file_disk' => 'exports',
|
|
]);
|
|
|
|
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
|
|
|
setAdminEnvironmentContext($tenant);
|
|
|
|
$this->actingAs($user)
|
|
->get(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review], $tenant).'?'.http_build_query([
|
|
CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY => 1,
|
|
]))
|
|
->assertOk()
|
|
->assertSee('Artifact reference')
|
|
->assertSee('Current export')
|
|
->assertSee('#'.$pack->getKey())
|
|
->assertSee('Lifecycle')
|
|
->assertSee('Current')
|
|
->assertSee('Retention')
|
|
->assertSee('Retained');
|
|
|
|
$component = Livewire::withQueryParams([CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY => 1])
|
|
->actingAs($user)
|
|
->test(ViewEnvironmentReview::class, ['record' => $review->getKey()])
|
|
->assertActionVisible('open_current_rendered_report')
|
|
->assertActionEnabled('open_current_rendered_report')
|
|
->assertActionDoesNotExist('publish_review')
|
|
->assertActionDoesNotExist('refresh_review')
|
|
->assertActionDoesNotExist('create_next_review')
|
|
->assertActionDoesNotExist('export_executive_pack')
|
|
->assertActionDoesNotExist('archive_review');
|
|
|
|
$component->assertActionExists('open_current_rendered_report', function (Action $action): bool {
|
|
$label = $action->getLabel();
|
|
|
|
return $label === 'View internal report';
|
|
});
|
|
|
|
$topLevelActionNames = collect(environmentReviewContractHeaderActions($component))
|
|
->map(static fn ($action): ?string => $action instanceof Action ? $action->getName() : null)
|
|
->filter()
|
|
->values()
|
|
->all();
|
|
|
|
expect($topLevelActionNames)->toBe(['open_current_rendered_report']);
|
|
});
|
|
|
|
it('shows publication truth and next-step guidance when a review is not yet publishable', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$owner, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
$snapshot = seedEnvironmentReviewEvidence($tenant);
|
|
|
|
$review = EnvironmentReview::query()->create([
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'evidence_snapshot_id' => (int) $snapshot->getKey(),
|
|
'initiated_by_user_id' => (int) $owner->getKey(),
|
|
'status' => 'draft',
|
|
'completeness_state' => 'complete',
|
|
'summary' => [
|
|
'publish_blockers' => ['Review the approval note before publication.'],
|
|
'section_state_counts' => ['complete' => 6, 'partial' => 0, 'missing' => 0, 'stale' => 0],
|
|
],
|
|
'fingerprint' => hash('sha256', 'environment-review-ui-contract'),
|
|
'generated_at' => now(),
|
|
]);
|
|
|
|
$this->actingAs($owner)
|
|
->get(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review], $tenant))
|
|
->assertOk()
|
|
->assertSee('Outcome summary')
|
|
->assertDontSee('Artifact truth')
|
|
->assertSee('Artifact reference')
|
|
->assertSee('Review #'.$review->getKey())
|
|
->assertSee('Lifecycle')
|
|
->assertSee('Current')
|
|
->assertSee('Retention')
|
|
->assertSee('Retained')
|
|
->assertSee('Publication blocked')
|
|
->assertSee('Resolve the review blockers before publication');
|
|
});
|
|
|
|
it('keeps executive posture from claiming publication readiness for internal-only reviews', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create();
|
|
[$owner, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
|
|
$review = $this->makeInternalOnlyArtifactTruthReview($tenant, $owner);
|
|
|
|
$this->actingAs($owner)
|
|
->get(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review], $tenant))
|
|
->assertOk()
|
|
->assertSee('Internal only')
|
|
->assertSee('Publication readiness')
|
|
->assertSee('This review is currently safe for internal use only.')
|
|
->assertDontSee('This review is ready for publication and executive-pack export.');
|
|
});
|