TenantAtlas/apps/platform/tests/Browser/Spec386ReviewPublicationResolutionWorkflowTest.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

135 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Resources\EnvironmentReviewResource;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Support\EnvironmentReviewStatus;
use App\Support\Workspaces\WorkspaceContext;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
uses(RefreshDatabase::class);
pest()->browser()->timeout(60_000);
beforeEach(function (): void {
Storage::fake('exports');
});
it('Spec386 smokes the blocked review publication resolution entry point', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
$environment->forceFill(['name' => 'Spec386 Browser Resolution'])->save();
$review = composeEnvironmentReviewForTest(
$environment,
$user,
seedPartialEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0),
);
$review->forceFill([
'status' => EnvironmentReviewStatus::Draft->value,
'published_at' => null,
'published_by_user_id' => null,
])->save();
spec386AuthenticateBrowser($this, $user, $environment);
$page = visit(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review], $environment))
->resize(1236, 900)
->waitForText('Resolve publication blockers')
->assertSee('Resolve publication blockers')
->click('Resolve publication blockers')
->waitForText('Review can\'t be published yet')
->assertSee('Why publication is blocked')
->assertSee('Next safe action')
->assertSee('TenantPilot will update the missing required reports. It will not publish the review.')
->assertSee('Publication preparation')
->assertSee('Update required reports')
->assertSee('Required reports')
->assertSee('Permission posture')
->assertSee('Technical proof and operation history')
->assertSee('Back to review')
->assertDontSee('Cancel resolution')
->assertDontSee('Report-backed evidence')
->click('Update required reports')
->waitForText('TenantPilot will update the missing required reports. This will not publish the review.')
->assertSee('TenantPilot will update the missing required reports. This will not publish the review.')
->click('Cancel')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$page->screenshot(true, spec386BrowserScreenshotName('01-resolution-page-desktop'));
spec386CopyBrowserScreenshot('01-resolution-page-desktop');
$mobilePage = $page
->resize(390, 844)
->waitForText('Publication preparation')
->assertSee('Update required reports')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$mobilePage->screenshot(true, spec386BrowserScreenshotName('02-resolution-page-mobile'));
spec386CopyBrowserScreenshot('02-resolution-page-mobile');
});
function spec386BrowserScreenshotName(string $name): string
{
return 'spec386-review-publication-resolution-'.$name;
}
function spec386CopyBrowserScreenshot(string $name): void
{
$filename = spec386BrowserScreenshotName($name).'.png';
$primarySource = base_path('tests/Browser/Screenshots/'.$filename);
$fallbackSource = \Pest\Browser\Support\Screenshot::path($filename);
$targetDirectory = repo_path('specs/386-review-publication-resolution-workflow-v1/artifacts/screenshots');
if (! is_dir($targetDirectory)) {
@mkdir($targetDirectory, 0755, true);
}
$source = null;
for ($attempt = 0; $attempt < 50 && $source === null; $attempt++) {
foreach ([$primarySource, $fallbackSource] as $candidate) {
if (is_file($candidate)) {
$source = $candidate;
break;
}
}
if ($source !== null) {
break;
}
usleep(100_000);
clearstatcache(true, $primarySource);
clearstatcache(true, $fallbackSource);
}
if (is_string($source) && is_file($source) && is_dir($targetDirectory) && is_writable($targetDirectory)) {
@copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$name.'.png');
}
}
function spec386AuthenticateBrowser(mixed $test, User $user, ManagedEnvironment $environment): void
{
$workspaceId = (int) $environment->workspace_id;
$test->actingAs($user)->withSession([
WorkspaceContext::SESSION_KEY => $workspaceId,
WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY => [
(string) $workspaceId => (int) $environment->getKey(),
],
]);
session()->put(WorkspaceContext::SESSION_KEY, $workspaceId);
session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
(string) $workspaceId => (int) $environment->getKey(),
]);
setAdminPanelContext($environment);
}