Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 3m42s
Implemented the output resolution guidance for the customer review workspace and internal views. Added ReviewPackOutputResolutionGuidance, updated CustomerReviewWorkspace and EnvironmentReviewResource, and added related blade views and tests.
293 lines
12 KiB
PHP
293 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()])
|
|
->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('download_current_review_pack')
|
|
->assertActionEnabled('download_current_review_pack')
|
|
->assertActionDoesNotExist('publish_review')
|
|
->assertActionDoesNotExist('refresh_review')
|
|
->assertActionDoesNotExist('create_next_review')
|
|
->assertActionDoesNotExist('export_executive_pack')
|
|
->assertActionDoesNotExist('archive_review');
|
|
|
|
$component->assertActionExists('download_current_review_pack', function (Action $action): bool {
|
|
$label = $action->getLabel();
|
|
|
|
return $label !== 'Download governance package'
|
|
&& in_array($label, [
|
|
'Download customer-safe review pack',
|
|
'Download review pack with limitations',
|
|
'Download internal review pack',
|
|
], true);
|
|
});
|
|
|
|
$topLevelActionNames = collect(environmentReviewContractHeaderActions($component))
|
|
->map(static fn ($action): ?string => $action instanceof Action ? $action->getName() : null)
|
|
->filter()
|
|
->values()
|
|
->all();
|
|
|
|
expect($topLevelActionNames)->toBe(['download_current_review_pack']);
|
|
});
|
|
|
|
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.');
|
|
});
|