TenantAtlas/apps/platform/tests/Feature/Filament/Spec351CustomerReviewWorkspaceResolveActionTest.php
ahmido d4e4d2d109 feat: review output resolve actions v1 (spec 351) (#422)
Implemented the first version of review output resolve actions. Included a ReviewOutputResolveActionMapper, commands to seed browser fixtures, updated CustomerReviewWorkspace, EnvironmentReviewResource, UI enforcement, and related views. Also added extensive unit, feature, and browser tests, and updated the design coverage matrix.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #422
2026-06-04 00:55:02 +00:00

190 lines
9.4 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
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\Workspaces\WorkspaceContext;
use Filament\Actions\Action;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use Livewire\Livewire;
uses(RefreshDatabase::class);
beforeEach(function (): void {
Storage::fake('exports');
});
it('mounts create next review as the dominant workspace CTA for published blocked output', function (): void {
$environment = ManagedEnvironment::factory()->create(['name' => 'Spec351 Workspace Blocked']);
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
$snapshot = seedPartialEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0);
$review = composeEnvironmentReviewForTest($environment, $user, $snapshot);
$review->forceFill([
'status' => EnvironmentReviewStatus::Published->value,
'published_at' => now(),
'published_by_user_id' => (int) $user->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-workspace-blocked.zip', 'PK-spec351-workspace-blocked');
$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(),
'initiated_by_user_id' => (int) $user->getKey(),
'options' => [
'include_pii' => false,
'include_operations' => true,
],
'file_path' => 'review-packs/spec351-workspace-blocked.zip',
'file_disk' => 'exports',
'generated_at' => now()->subMinutes(3),
]);
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
$component = spec351WorkspaceComponent($user, $environment)
->assertSee('Create next review')
->assertSee('Inspect review blockers')
->assertSee('Supporting actions')
->assertSee('Package exists')
->assertSee('Internal export')
->assertSee('Customer sharing')
->assertSee('Customer-ready status is still determined by the output state above.');
$payload = $component->instance()->latestReviewConsumptionPayload();
expect(data_get($payload, 'readiness.resolution_case.primary_action.key'))->toBe('create_next_review')
->and(data_get($payload, 'readiness.resolution_case.primary_action.action_name'))->toBe('createNextReview')
->and(data_get($payload, 'readiness.resolution_case.secondary_actions.0.key'))->toBe('resolve_review_blockers')
->and(collect(data_get($payload, 'review_pack_panel.sections', []))->pluck('key')->all())->toBe([
'package_exists',
'internal_export',
'customer_sharing',
])
->and(data_get($payload, 'acknowledgement.impact'))->toBe('Acknowledgement records review consumption for this published package. Customer-ready status is still determined by the output state above.');
$component
->assertActionExists('createNextReview', fn (Action $action): bool => $action->isConfirmationRequired())
->mountAction('createNextReview')
->assertActionMounted('createNextReview');
});
it('falls back to review navigation on the workspace when the actor cannot execute the mutation', function (): void {
$environment = ManagedEnvironment::factory()->create(['name' => 'Spec351 Workspace Readonly']);
[$owner, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
[$readonly] = createUserWithTenant(tenant: $environment, user: User::factory()->create(), role: 'readonly', workspaceRole: 'readonly');
$snapshot = seedPartialEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0);
$review = composeEnvironmentReviewForTest($environment, $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();
$component = spec351WorkspaceComponent($readonly, $environment)
->assertSee('Inspect review blockers');
$html = $component->html();
$dom = new DOMDocument;
@$dom->loadHTML($html);
$xpath = new DOMXPath($dom);
$primaryActionNode = $xpath->query('//*[@data-testid="customer-review-primary-action"]')->item(0);
$primaryActionLabel = $primaryActionNode?->textContent !== null
? trim($primaryActionNode->textContent)
: null;
expect($primaryActionLabel)->toBe('Inspect review blockers');
});
it('keeps the workspace guidance on an existing draft instead of falling into an empty state', function (): void {
$environment = ManagedEnvironment::factory()->create(['name' => 'Spec351 Workspace Existing Draft']);
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
$publishedSnapshot = seedPartialEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0);
$published = composeEnvironmentReviewForTest($environment, $user, $publishedSnapshot);
$published->forceFill([
'status' => EnvironmentReviewStatus::Published->value,
'published_at' => now()->subDay(),
'published_by_user_id' => (int) $user->getKey(),
'summary' => array_replace_recursive(is_array($published->summary) ? $published->summary : [], [
'publish_blockers' => ['Operator approval note is still missing.'],
]),
])->save();
$draft = app(EnvironmentReviewLifecycleService::class)->createNextReview($published->fresh(), $user, $publishedSnapshot);
expect($published->fresh()->status)->toBe(EnvironmentReviewStatus::Superseded->value)
->and($draft->status)->toBe(EnvironmentReviewStatus::Draft->value);
$component = spec351WorkspaceComponent($user, $environment)
->assertDontSee('No released customer reviews match the active environment filter.')
->assertSee('Draft review exists')
->assertSee('Open draft review')
->assertSee('Supporting actions');
$payload = $component->instance()->latestReviewConsumptionPayload();
expect(data_get($payload, 'readiness.resolution_case.primary_action.key'))->toBe('open_successor_review')
->and(data_get($payload, 'readiness.resolution_case.primary_action.label'))->toBe('Open draft review')
->and(data_get($payload, 'readiness.resolution_case.title'))->toBe('Draft review exists')
->and(data_get($payload, 'readiness.resolution_case.reason'))->toContain('Open the draft review to refresh inputs');
});
it('recognizes the newly created successor when the operator returns to the workspace', function (): void {
$environment = ManagedEnvironment::factory()->create(['name' => 'Spec351 Workspace Return Loop']);
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
$snapshot = seedPartialEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0);
$review = composeEnvironmentReviewForTest($environment, $user, $snapshot);
$review->forceFill([
'status' => EnvironmentReviewStatus::Published->value,
'published_at' => now(),
'published_by_user_id' => (int) $user->getKey(),
'summary' => array_replace_recursive(is_array($review->summary) ? $review->summary : [], [
'publish_blockers' => ['Operator approval note is still missing.'],
]),
])->save();
spec351WorkspaceComponent($user, $environment)
->mountAction('createNextReview')
->callMountedAction();
$published = $review->fresh();
$successor = EnvironmentReview::query()->findOrFail((int) $published->superseded_by_review_id);
expect($published->status)->toBe(EnvironmentReviewStatus::Superseded->value)
->and($successor->status)->toBe(EnvironmentReviewStatus::Draft->value);
$component = spec351WorkspaceComponent($user, $environment)
->assertDontSee('No released customer reviews match the active environment filter.')
->assertSee('Draft review exists')
->assertSee('Open draft review');
$payload = $component->instance()->latestReviewConsumptionPayload();
expect(data_get($payload, 'readiness.resolution_case.primary_action.url'))->toContain('/environment-reviews/'.$successor->getKey())
->and(data_get($payload, 'readiness.output_guidance.action_help'))->toContain('Open the existing draft review');
});
function spec351WorkspaceComponent(User $user, ManagedEnvironment $environment): mixed
{
session()->put(WorkspaceContext::SESSION_KEY, (int) $environment->workspace_id);
setAdminPanelContext();
return Livewire::actingAs($user)
->test(CustomerReviewWorkspace::class);
}