Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 3m45s
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.
190 lines
9.4 KiB
PHP
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);
|
|
}
|