TenantAtlas/apps/platform/tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php
Ahmed Darrazi 59b45becc1
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m35s
feat: enforce Spec392 customer output gating
2026-06-20 22:52:43 +02:00

454 lines
21 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Filament\Resources\EnvironmentReviewResource;
use App\Models\EvidenceSnapshot;
use App\Models\ManagedEnvironment;
use App\Models\ReviewPack;
use App\Services\ReviewPackService;
use App\Support\EnvironmentReviewCompletenessState;
use App\Support\EnvironmentReviewStatus;
use App\Support\Evidence\EvidenceCompletenessState;
use App\Support\Governance\Controls\ComplianceEvidenceMappingV1;
use App\Support\ReviewPacks\ReportProfileRegistry;
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('smokes the customer review workspace handoff from environment review detail', function (): void {
$tenantPublished = ManagedEnvironment::factory()->create(['name' => 'Published ManagedEnvironment']);
[$user, $tenantPublished] = createUserWithTenant(
tenant: $tenantPublished,
role: 'owner',
workspaceRole: 'manager',
);
$user->forceFill(['preferred_locale' => 'de'])->save();
$tenantWithoutPublished = ManagedEnvironment::factory()->create([
'workspace_id' => (int) $tenantPublished->workspace_id,
'name' => 'No Published ManagedEnvironment',
]);
createUserWithTenant(
tenant: $tenantWithoutPublished,
user: $user,
role: 'owner',
workspaceRole: 'manager',
);
$publishedSnapshot = seedEnvironmentReviewEvidence($tenantPublished, findingCount: 0, driftCount: 0);
$noPublishedSnapshot = seedEnvironmentReviewEvidence($tenantWithoutPublished, findingCount: 0, driftCount: 0);
$publishedReview = composeEnvironmentReviewForTest($tenantPublished, $user, $publishedSnapshot);
$publishedSummary = array_replace_recursive(is_array($publishedReview->summary) ? $publishedReview->summary : [], [
'control_interpretation' => [
'version_key' => ComplianceEvidenceMappingV1::VERSION_KEY,
'controls' => [
[
'control_key' => 'customer-handoff-readiness',
'title' => 'Customer handoff readiness',
'readiness_bucket' => 'evidence_on_record',
'readiness_label' => 'Evidence on record',
'primary_reason' => 'Evidence path is complete.',
'recommended_next_action' => 'Share the current review pack.',
],
],
],
'governance_package' => [
'decision_summary' => [
'status' => 'none',
'evidence_state' => EnvironmentReviewCompletenessState::Complete->value,
'decision_data_state' => 'complete',
'total_count' => 0,
'summary' => '',
'next_action' => '',
'entries' => [],
],
],
]);
$publishedReview->forceFill([
'status' => EnvironmentReviewStatus::Published->value,
'summary' => $publishedSummary,
'published_at' => now(),
'published_by_user_id' => (int) $user->getKey(),
])->save();
$publishedReview = markEnvironmentReviewCustomerSafeReady($publishedReview);
$internalOnlyReview = composeEnvironmentReviewForTest($tenantWithoutPublished, $user, $noPublishedSnapshot);
$internalOnlyReview->forceFill([
'status' => EnvironmentReviewStatus::Ready->value,
'published_at' => null,
'published_by_user_id' => null,
])->save();
Storage::disk('exports')->put('review-packs/customer-review-workspace-smoke.zip', 'PK-test');
$pack = ReviewPack::factory()->ready()->create([
'managed_environment_id' => (int) $tenantPublished->getKey(),
'workspace_id' => (int) $tenantPublished->workspace_id,
'environment_review_id' => (int) $publishedReview->getKey(),
'evidence_snapshot_id' => (int) $publishedSnapshot->getKey(),
'initiated_by_user_id' => (int) $user->getKey(),
'options' => [
'include_pii' => false,
'include_operations' => true,
],
'summary' => [
'governance_package' => [
'executive_summary' => 'Customer steering summary for the rendered browser smoke report.',
'evidence_basis_summary' => 'Evidence basis is complete for the published smoke review.',
'top_findings' => [
[
'title' => 'Conditional access drift requires review',
'summary' => 'An inherited drift signal should stay visible in the rendered report.',
],
],
'accepted_risks' => [
[
'title' => 'Temporary exception remains accepted',
'customer_safe_summary' => 'The accepted risk stays visible as stakeholder context.',
],
],
'decision_summary' => [
'status' => 'attention_required',
'summary' => 'A governance decision still requires stakeholder awareness.',
'next_action' => 'Review the accepted exception before external sharing.',
'entries' => [
[
'title' => 'Conditional access exception',
'summary' => 'Temporary risk is accepted until remediation finishes.',
'next_action' => 'Confirm the remediation timeline before customer handoff.',
],
],
],
],
'control_interpretation' => [
'non_certification_disclosure' => 'Nur Service-Delivery-Zusammenfassung. Ersetzt weder formales Auditurteil noch Zertifizierung oder rechtliche Attestierung.',
],
'recommended_next_actions' => [
'Share the rendered report with the customer steering group.',
],
'delivery_bundle' => [
'executive_entrypoint_file' => 'executive-summary.md',
'appendix_files' => ['metadata.json', 'summary.json', 'sections.json'],
],
],
'file_path' => 'review-packs/customer-review-workspace-smoke.zip',
'file_disk' => 'exports',
]);
$publishedReview->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
$limitedEnvironment = ManagedEnvironment::factory()->active()->create([
'workspace_id' => (int) $tenantPublished->workspace_id,
'name' => 'Limited Report ManagedEnvironment',
]);
createUserWithTenant(tenant: $limitedEnvironment, user: $user, role: 'owner', workspaceRole: 'manager');
[$limitedReview, $limitedPack] = spec356BrowserCreateRenderedReportPack(
environment: $limitedEnvironment,
user: $user,
snapshot: seedEnvironmentReviewEvidence($limitedEnvironment, findingCount: 0, driftCount: 0),
filePath: 'review-packs/spec356-browser-limited.zip',
evidenceOverride: EvidenceCompletenessState::Partial,
);
$internalEnvironment = ManagedEnvironment::factory()->active()->create([
'workspace_id' => (int) $tenantPublished->workspace_id,
'name' => 'Internal PII Report ManagedEnvironment',
]);
createUserWithTenant(tenant: $internalEnvironment, user: $user, role: 'owner', workspaceRole: 'manager');
[$internalReview, $internalPack] = spec356BrowserCreateRenderedReportPack(
environment: $internalEnvironment,
user: $user,
snapshot: seedEnvironmentReviewEvidence($internalEnvironment, findingCount: 0, driftCount: 0),
packOptions: [
'include_pii' => true,
'include_operations' => true,
],
filePath: 'review-packs/spec356-browser-internal-pii.zip',
);
$this->actingAs($user)->withSession([
WorkspaceContext::SESSION_KEY => (int) $tenantPublished->workspace_id,
WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY => [
(string) $tenantPublished->workspace_id => (int) $tenantPublished->getKey(),
],
]);
$environmentReviewPage = visit(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $publishedReview], $tenantPublished))
->waitForText('Verwandter Kontext')
->assertSee('Kunden-Workspace öffnen')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$environmentReviewPage
->assertScript('document.querySelector(\'a.fi-link[href*="/admin/reviews/workspace?environment_id="]\') instanceof HTMLAnchorElement', true);
$workspacePage = visit(CustomerReviewWorkspace::environmentFilterUrl($tenantPublished))
->waitForText('Kundensichere Review-Pakete')
->assertSee('Filter löschen')
->assertSee('Review öffnen')
->assertSee('Governance-Paket')
->assertSee('Status')
->assertSee('Nachweise')
->assertSee('Prüfen Sie veröffentlichte Governance-Pakete, Evidence-Bereitschaft, akzeptierte Risiken und Übergabestatus über berechtigte Umgebungen hinweg.')
->assertSee('Nur Service-Delivery-Zusammenfassung. Ersetzt weder formales Auditurteil noch Zertifizierung oder rechtliche Attestierung.')
->assertSee('Letztes veröffentlichtes Review')
->assertSee('Review-Paket-Index')
->assertSee('Offenlegungsregel')
->assertSee('Eingeklappt')
->assertSee('Kundensicheres Review-Paket herunterladen')
->assertSee('Das aktuelle Review-Paket ist verfügbar, erfüllt den kundensicheren Output-Vertrag und kann aus dem Review-Detail als gerenderter Bericht geöffnet werden.')
->assertSee('In diesem veröffentlichten Review benötigen keine Governance-Entscheidungen Kundenaufmerksamkeit.')
->assertSee('Kundensicheres Review-Paket bereit')
->assertSee('Verfügbar')
->assertDontSee('Customer-safe governance package index')
->assertDontSee('localization.review.customer_safe_review_workspace')
->assertDontSee('Publishable')
->assertDontSee('No mapped controls')
->assertDontSee('Compliance evidence mapping v1')
->assertDontSee('Publish review')
->assertDontSee('Refresh review');
$workspacePage
->assertScript('document.querySelector(\'[data-testid="workspace-hub-environment-filter-clear"]\') instanceof HTMLAnchorElement', true);
visit(CustomerReviewWorkspace::getUrl(panel: 'admin'))
->waitForText('Published ManagedEnvironment')
->assertDontSee('No Published ManagedEnvironment')
->assertDontSee('No published review available yet')
->assertSeeIn('tbody tr.fi-ta-row:first-of-type td:last-child', 'Review öffnen');
$customerContextReviewUrl = EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $publishedReview], $tenantPublished)
.'?'.http_build_query([
CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY => 1,
'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE,
'tenant_filter_id' => (int) $tenantPublished->getKey(),
]);
$customerReviewDetailPage = visit($customerContextReviewUrl)
->waitForText('Ergebniszusammenfassung')
->assertSee('Kundensicheren Bericht anzeigen')
->assertSee('Governance-Paket')
->assertSee('Veröffentlichter Governance-Nachweis')
->assertSee('Review-Status')
->assertSee('Primäre Aktion')
->assertSee('Executive-Einstieg')
->assertSee('Strukturierter Auditor-Anhang')
->assertSee('Prüfgrundlage')
->assertDontSee('Released governance record')
->assertDontSee('Control readiness interpretation')
->assertDontSee('Compliance evidence mapping v1')
->assertDontSee('Publish review')
->assertDontSee('Refresh review')
->assertDontSee('Create next review')
->assertDontSee('Export executive pack')
->assertDontSee('Archive review')
->assertScript('document.querySelector(\'a[href*="/admin/review-packs/"][href*="/report"]\') instanceof HTMLAnchorElement', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$renderedReportUrl = app(ReviewPackService::class)->generateRenderedReportUrl($pack, [
CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY => 1,
'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE,
'tenant_filter_id' => (int) $tenantPublished->getKey(),
'review_id' => (int) $publishedReview->getKey(),
'interpretation_version' => $publishedReview->controlInterpretationVersion(),
]);
$renderedReportPage = visit($renderedReportUrl)
->resize(1280, 1440)
->waitForText('Kundensicherer Bericht bereit')
->assertPathContains('/admin/review-packs/')
->assertPathEndsWith('/report')
->assertSee('Evidence-Basis')
->assertSee('Bericht drucken')
->assertSee('Review-Detail öffnen')
->assertSee('Review-Pack-Detail öffnen')
->assertSee('Conditional access drift requires review')
->assertSee('A governance decision still requires stakeholder awareness.')
->assertSee('Unterstützender Anhang')
->assertSee('Nur Service-Delivery-Zusammenfassung. Ersetzt weder formales Auditurteil noch Zertifizierung oder rechtliche Attestierung.')
->assertDontSee('localization.')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$renderedReportPage->screenshot(true, spec356BrowserScreenshotName('report-customer-safe-ready'));
spec356CopyBrowserScreenshot('report-customer-safe-ready');
$renderedReportPage->script('document.body.classList.add("print-preview-smoke"); window.scrollTo(0, 0);');
$renderedReportPage
->assertScript('window.getComputedStyle(document.querySelector("[data-testid=\"rendered-report-toolbar\"]")).display === "none"', true);
$renderedReportPage->screenshot(true, spec356BrowserScreenshotName('report-print-view'));
spec356CopyBrowserScreenshot('report-print-view');
$renderedReportPage->script('document.body.classList.remove("print-preview-smoke"); document.querySelector("[data-testid=\"rendered-report-supporting-appendix\"]")?.scrollIntoView({ block: "start" });');
$renderedReportPage->screenshot(false, spec356BrowserScreenshotName('report-appendix'));
spec356CopyBrowserScreenshot('report-appendix');
$limitedReportUrl = app(ReviewPackService::class)->generateRenderedReportUrl($limitedPack, [
CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY => 1,
'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE,
'tenant_filter_id' => (int) $limitedEnvironment->getKey(),
'review_id' => (int) $limitedReview->getKey(),
'interpretation_version' => $limitedReview->controlInterpretationVersion(),
]);
visit($limitedReportUrl)
->resize(1280, 1440)
->assertScript('document.body.innerText.includes("403") || document.body.innerText.includes("Forbidden")', true)
->assertDontSee('Bericht mit Einschränkungen')
->assertDontSee('Kundensicherer Bericht bereit')
->assertDontSee('localization.')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$customerWorkspaceInternalReportUrl = app(ReviewPackService::class)->generateRenderedReportUrl($internalPack, [
CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY => 1,
'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE,
'tenant_filter_id' => (int) $internalEnvironment->getKey(),
'review_id' => (int) $internalReview->getKey(),
'interpretation_version' => $internalReview->controlInterpretationVersion(),
]);
visit($customerWorkspaceInternalReportUrl)
->resize(1280, 1440)
->assertScript('document.body.innerText.includes("403") || document.body.innerText.includes("Forbidden")', true)
->assertDontSee('Interner Bericht mit Einschränkungen')
->assertDontSee('Kundensicherer Bericht bereit')
->assertDontSee('localization.')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$internalReportUrl = app(ReviewPackService::class)->generateRenderedReportUrl($internalPack, [
'source_surface' => 'review_pack',
'review_id' => (int) $internalReview->getKey(),
'interpretation_version' => $internalReview->controlInterpretationVersion(),
ReportProfileRegistry::QUERY_PARAMETER => ReportProfileRegistry::INTERNAL_MSP_REVIEW,
]);
$internalReportPage = visit($internalReportUrl)
->resize(1280, 1440)
->waitForText('Interner Bericht mit Einschränkungen')
->assertSee('Nicht extern weitergeben, bevor der Bericht geprüft wurde.')
->assertSee('interne oder PII-tragende Details')
->assertSee('Interne Vorschau herunterladen')
->assertDontSee('Kundensicherer Bericht bereit')
->assertDontSee('localization.')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$internalReportPage->screenshot(true, spec356BrowserScreenshotName('report-internal-pii'));
spec356CopyBrowserScreenshot('report-internal-pii');
});
/**
* @param array<string, mixed> $packOptions
* @return array{0:\App\Models\EnvironmentReview,1:ReviewPack}
*/
function spec356BrowserCreateRenderedReportPack(
ManagedEnvironment $environment,
\App\Models\User $user,
EvidenceSnapshot $snapshot,
array $packOptions = [],
string $filePath = 'review-packs/spec356-browser-report.zip',
?EvidenceCompletenessState $evidenceOverride = null,
): array {
$review = composeEnvironmentReviewForTest($environment, $user, $snapshot);
$review->forceFill([
'status' => EnvironmentReviewStatus::Published->value,
'published_at' => now()->subMinutes(5),
'published_by_user_id' => (int) $user->getKey(),
])->save();
$review = markEnvironmentReviewCustomerSafeReady($review);
if ($evidenceOverride instanceof EvidenceCompletenessState) {
restateEnvironmentReviewEvidenceSnapshot($review->evidenceSnapshot, $evidenceOverride);
$review = $review->fresh(['sections', 'evidenceSnapshot']);
}
Storage::disk('exports')->put($filePath, 'PK-spec356-browser-test');
$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' => array_replace([
'include_pii' => false,
'include_operations' => true,
], $packOptions),
'summary' => [
'governance_package' => [
'executive_summary' => 'Management summary for the Spec 356 rendered browser report.',
'evidence_basis_summary' => 'The report is anchored to the stored review evidence basis.',
'top_findings' => [],
'accepted_risks' => [],
'decision_summary' => [
'status' => 'none',
'summary' => '',
'next_action' => '',
'entries' => [],
],
],
'control_interpretation' => [
'non_certification_disclosure' => 'Nur Service-Delivery-Zusammenfassung. Ersetzt weder formales Auditurteil noch Zertifizierung oder rechtliche Attestierung.',
],
'recommended_next_actions' => [],
'delivery_bundle' => [
'executive_entrypoint_file' => 'executive-summary.md',
'appendix_files' => ['metadata.json', 'summary.json', 'sections.json'],
],
],
'file_path' => $filePath,
'file_disk' => 'exports',
'generated_at' => now()->subMinutes(4),
]);
$review->forceFill([
'current_export_review_pack_id' => (int) $pack->getKey(),
])->save();
return [$review->refresh(), $pack->refresh()];
}
function spec356BrowserScreenshotName(string $name): string
{
return 'spec356-review-pack-rendered-report-'.$name;
}
function spec356CopyBrowserScreenshot(string $name): void
{
$filename = spec356BrowserScreenshotName($name).'.png';
$source = base_path('tests/Browser/Screenshots/'.$filename);
$targetDirectory = repo_path('specs/356-review-pack-pdf-html-renderer-v1/artifacts/screenshots');
if (! is_dir($targetDirectory)) {
@mkdir($targetDirectory, 0755, true);
}
if (! is_file($source)) {
$source = \Pest\Browser\Support\Screenshot::path($filename);
}
for ($attempt = 0; $attempt < 10 && ! is_file($source); $attempt++) {
usleep(100_000);
clearstatcache(true, $source);
}
if (is_file($source) && is_dir($targetDirectory) && is_writable($targetDirectory)) {
@copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$name.'.png');
}
}