TenantAtlas/apps/platform/tests/Browser/Spec387ReviewPublicationResolutionDecisionUxTest.php
ahmido 83c679cf85 feat: add review publication proof currentness contract (#459)
Automated PR created by Codex via Gitea API.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #459
2026-06-19 19:10:35 +00:00

242 lines
9.4 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Filament\Resources\EnvironmentReviewResource;
use App\Models\EnvironmentReview;
use App\Models\EvidenceSnapshot;
use App\Models\ManagedEnvironment;
use App\Models\StoredReport;
use App\Models\User;
use App\Support\EnvironmentReviewStatus;
use App\Support\Evidence\EvidenceCompletenessState;
use App\Support\ReviewPublicationResolution\ReviewPublicationResolutionService;
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('Spec387 smokes the decision-first publication resolution flow', function (): void {
[$user, $environment, $review] = spec387BrowserBlockedReviewFixture();
spec387AuthenticateBrowser($this, $user, $environment);
$reviewDetailPage = visit(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review], $environment))
->resize(1236, 900)
->waitForText('Resolve publication blockers')
->assertSee('Resolve publication blockers')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$reviewDetailPage->screenshot(true, spec387BrowserScreenshotName('01-review-detail-blocked-cta'));
spec387CopyBrowserScreenshot('01-review-detail-blocked-cta');
$resolutionPage = $reviewDetailPage
->click('Resolve publication blockers')
->waitForText('Review can\'t be published yet')
->assertSee('Publication preparation')
->assertSee('Why publication is blocked')
->assertSee('Next safe action')
->assertSee('Update required reports')
->assertSee('Prepare export')
->assertSee('Return to review')
->assertSee('TenantPilot will update the missing required reports. It will not publish the review.')
->assertSee('Technical proof and operation history')
->assertDontSee('Generate review pack')
->assertDontSee('Return to publication')
->assertDontSee('Report-backed evidence')
->assertDontSee('OperationRun')
->assertDontSee('Artifact proof')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$resolutionPage->screenshot(true, spec387BrowserScreenshotName('02-resolution-decision-desktop'));
spec387CopyBrowserScreenshot('02-resolution-decision-desktop');
$modalPage = $resolutionPage
->click('Update required reports')
->waitForText('Update required reports?')
->assertSee('TenantPilot will update the missing required reports. This will not publish the review.')
->assertSee('Update required reports')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$modalPage->screenshot(true, spec387BrowserScreenshotName('03-confirmation-modal'));
spec387CopyBrowserScreenshot('03-confirmation-modal');
$expandedProofPage = $modalPage
->click('Cancel')
->click('Technical proof and operation history')
->waitForText('Proof and operation links are supporting evidence only.')
->assertSee('Proof and operation links are supporting evidence only.')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$expandedProofPage->screenshot(true, spec387BrowserScreenshotName('04-technical-proof-expanded'));
spec387CopyBrowserScreenshot('04-technical-proof-expanded');
$mobilePage = $expandedProofPage
->resize(390, 844)
->waitForText('Publication preparation')
->assertSee('Review can\'t be published yet')
->assertSee('Update required reports')
->assertSee('Return to review')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$mobilePage->screenshot(true, spec387BrowserScreenshotName('05-resolution-decision-mobile'));
spec387CopyBrowserScreenshot('05-resolution-decision-mobile');
$customerWorkspace = visit(CustomerReviewWorkspace::environmentFilterUrl($environment))
->resize(1236, 900)
->waitForText('Customer Review Workspace')
->assertDontSee('Resolution Case')
->assertDontSee('Current step')
->assertDontSee('OperationRun')
->assertDontSee('Artifact proof')
->assertDontSee('complete_required_reports')
->assertDontSee('generate_review_pack')
->assertDontSee('return_to_publication')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$customerWorkspace->screenshot(true, spec387BrowserScreenshotName('06-customer-no-leakage'));
spec387CopyBrowserScreenshot('06-customer-no-leakage');
});
it('Spec387 smokes readonly inspection copy on the resolution page', function (): void {
[, $environment, $review] = spec387BrowserBlockedReviewFixture();
[$readonly] = createUserWithTenant(
tenant: $environment,
user: User::factory()->create(),
role: 'readonly',
workspaceRole: 'readonly',
);
spec387AuthenticateBrowser($this, $readonly, $environment);
$page = visit(EnvironmentReviewResource::environmentScopedUrl('resolve-publication', ['record' => $review], $environment))
->resize(1236, 900)
->waitForText('You can inspect this preparation flow, but you do not have permission to run the next action.')
->assertSee('Review can\'t be published yet')
->assertSee('You can inspect this preparation flow, but you do not have permission to run the next action.')
->assertSee('Update required reports')
->assertDontSee('Generate review pack')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$page->screenshot(true, spec387BrowserScreenshotName('07-readonly-inspection'));
spec387CopyBrowserScreenshot('07-readonly-inspection');
});
/**
* @return array{0: User, 1: ManagedEnvironment, 2: EnvironmentReview}
*/
function spec387BrowserBlockedReviewFixture(): array
{
$environment = ManagedEnvironment::factory()->create(['name' => 'Spec387 Browser Resolution']);
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
$snapshot = spec387BrowserPartialEvidence($environment);
$review = composeEnvironmentReviewForTest($environment, $user, $snapshot);
$review->forceFill([
'status' => EnvironmentReviewStatus::Draft->value,
'published_at' => null,
'published_by_user_id' => null,
])->save();
app(ReviewPublicationResolutionService::class)->openOrResume($review, $user);
return [$user, $environment, $review];
}
function spec387BrowserPartialEvidence(ManagedEnvironment $environment): EvidenceSnapshot
{
$snapshot = seedPartialEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0);
$snapshot->items()->whereIn('dimension_key', ['permission_posture', 'entra_admin_roles'])->update([
'state' => EvidenceCompletenessState::Missing->value,
'source_record_id' => null,
'source_fingerprint' => null,
]);
spec387BrowserDeleteStoredReport($environment, StoredReport::REPORT_TYPE_PERMISSION_POSTURE);
spec387BrowserDeleteStoredReport($environment, StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES);
return $snapshot->fresh('items');
}
function spec387BrowserDeleteStoredReport(ManagedEnvironment $environment, string $reportType): void
{
StoredReport::query()
->where('workspace_id', (int) $environment->workspace_id)
->where('managed_environment_id', (int) $environment->getKey())
->where('report_type', $reportType)
->delete();
}
function spec387BrowserScreenshotName(string $name): string
{
return 'spec387-review-publication-resolution-'.$name;
}
function spec387CopyBrowserScreenshot(string $name): void
{
$filename = spec387BrowserScreenshotName($name).'.png';
$primarySource = base_path('tests/Browser/Screenshots/'.$filename);
$fallbackSource = \Pest\Browser\Support\Screenshot::path($filename);
$targetDirectory = repo_path('specs/387-review-publication-resolution-decision-ux-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 spec387AuthenticateBrowser(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);
}