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); }