TenantAtlas/apps/platform/tests/Feature/EnvironmentReview/Spec351EnvironmentReviewResolveActionTest.php
ahmido ba7622a158 feat: implement ReviewPublicationResolutionWorkflow (Spec 386) (#457)
## Summary\n- Implements the ReviewPublicationResolutionWorkflow for Spec 386.\n- Adds resolution case/step persistence, policies, services, audit action IDs, and Filament integration.\n- Updates specs, UI/UX documentation, screenshots, and Pest coverage.\n\n## Tests\n- Not run during this handoff; branch was already clean and pushed.\n\n## Target\n- Base: platform-dev\n- Head/topic: 386-review-publication-resolution-workflow-v1

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #457
2026-06-18 21:06:20 +00:00

130 lines
5.9 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Resources\EnvironmentReviewResource;
use App\Filament\Resources\EnvironmentReviewResource\Pages\ViewEnvironmentReview;
use App\Models\ManagedEnvironment;
use App\Models\ReviewPack;
use App\Support\EnvironmentReviewStatus;
use App\Support\Ui\GovernanceActions\GovernanceActionCatalog;
use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use Livewire\Livewire;
uses(RefreshDatabase::class);
beforeEach(function (): void {
Storage::fake('exports');
});
it('aligns published blocked detail guidance to the create next review header action without a duplicate card CTA', function (): void {
$tenant = ManagedEnvironment::factory()->create(['name' => 'Spec351 Detail Blocked']);
[$owner, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
$snapshot = seedPartialEnvironmentReviewEvidence($tenant, findingCount: 0, driftCount: 0);
$review = composeEnvironmentReviewForTest($tenant, $owner, $snapshot);
$review->forceFill([
'status' => EnvironmentReviewStatus::Published->value,
'published_at' => now(),
'published_by_user_id' => (int) $owner->getKey(),
'summary' => array_replace_recursive(is_array($review->summary) ? $review->summary : [], [
'publish_blockers' => ['Operator approval note is still missing.'],
]),
])->save();
Storage::disk('exports')->put('review-packs/spec351-detail-blocked.zip', 'PK-spec351-detail-blocked');
$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) $owner->getKey(),
'options' => [
'include_pii' => false,
'include_operations' => true,
],
'file_path' => 'review-packs/spec351-detail-blocked.zip',
'file_disk' => 'exports',
'generated_at' => now()->subMinutes(3),
]);
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
setAdminEnvironmentContext($tenant);
$this->actingAs($owner);
$state = EnvironmentReviewResource::outputGuidanceState($review->fresh(['tenant', 'evidenceSnapshot', 'currentExportReviewPack.operationRun', 'operationRun']));
expect(data_get($state, 'resolution_case.primary_action.key'))->toBe('create_next_review')
->and(data_get($state, 'resolution_case.primary_action.action_name'))->toBe('create_next_review')
->and(data_get($state, 'suppress_primary_action_button'))->toBeTrue();
Livewire::actingAs($owner)
->test(ViewEnvironmentReview::class, ['record' => $review->getKey()])
->assertActionExists('create_next_review', fn (Action $action): bool => $action->isConfirmationRequired())
->mountAction('create_next_review')
->assertActionMounted('create_next_review');
});
it('aligns ready mutable detail guidance to publish review', function (): void {
$tenant = ManagedEnvironment::factory()->create(['name' => 'Spec351 Detail Ready']);
[$owner, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
$review = composeEnvironmentReviewForTest($tenant, $owner);
$review->forceFill([
'status' => EnvironmentReviewStatus::Ready->value,
'published_at' => null,
'published_by_user_id' => null,
])->save();
setAdminEnvironmentContext($tenant);
$this->actingAs($owner);
$state = EnvironmentReviewResource::outputGuidanceState($review->fresh(['tenant', 'evidenceSnapshot', 'currentExportReviewPack.operationRun', 'operationRun']));
expect(data_get($state, 'resolution_case.primary_action.key'))->toBe('publish_review')
->and(data_get($state, 'resolution_case.primary_action.action_name'))->toBe('publish_review')
->and(data_get($state, 'suppress_primary_action_button'))->toBeTrue();
Livewire::actingAs($owner)
->test(ViewEnvironmentReview::class, ['record' => $review->getKey()])
->assertActionExists('publish_review', fn (Action $action): bool => $action->isConfirmationRequired())
->mountAction('publish_review')
->assertActionMounted('publish_review');
});
it('keeps publish review disabled when the draft is still blocked and refresh feedback stays explicit', function (): void {
$tenant = ManagedEnvironment::factory()->create(['name' => 'Spec351 Detail Draft Blocked']);
[$owner, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
$snapshot = seedPartialEnvironmentReviewEvidence($tenant, findingCount: 0, driftCount: 0);
$review = composeEnvironmentReviewForTest($tenant, $owner, $snapshot);
$review->forceFill([
'status' => EnvironmentReviewStatus::Draft->value,
'published_at' => null,
'published_by_user_id' => null,
])->save();
setAdminEnvironmentContext($tenant);
$this->actingAs($owner);
$state = EnvironmentReviewResource::outputGuidanceState($review->fresh(['tenant', 'sections', 'evidenceSnapshot', 'currentExportReviewPack.operationRun', 'operationRun']));
expect(data_get($state, 'resolution_case.primary_action.key'))->toBe('resolve_publication_blockers');
Livewire::actingAs($owner)
->test(ViewEnvironmentReview::class, ['record' => $review->getKey()])
->assertActionVisible('resolve_publication_blockers')
->assertActionVisible('refresh_review')
->assertActionVisible('publish_review')
->assertActionDisabled('publish_review')
->callAction('refresh_review')
->assertNotified(
Notification::make()
->success()
->title(GovernanceActionCatalog::rule('refresh_review')->successTitle)
->body('Review inputs refreshed. Blockers remain.'),
);
});