333 lines
15 KiB
PHP
333 lines
15 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\EnvironmentReview;
|
|
use App\Models\EnvironmentReviewSection;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\ReviewPack;
|
|
use App\Models\User;
|
|
use App\Services\ReviewPackService;
|
|
use App\Support\Evidence\EvidenceCompletenessState;
|
|
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(90_000);
|
|
|
|
beforeEach(function (): void {
|
|
Storage::fake('exports');
|
|
});
|
|
|
|
it('Spec366 smokes management report layout variants and print behavior', function (): void {
|
|
[$user, $customerEnvironment, $customerReview, $customerPack] = spec366BrowserCreateLayoutPack(
|
|
environmentName: 'Spec366 Browser Customer',
|
|
customerSafeReady: true,
|
|
);
|
|
[$limitedUser, $limitedEnvironment, $limitedReview, $limitedPack] = spec366BrowserCreateLayoutPack(
|
|
user: $user,
|
|
workspaceId: (int) $customerEnvironment->workspace_id,
|
|
environmentName: 'Spec366 Browser Limited',
|
|
customerSafeReady: true,
|
|
partialEvidence: true,
|
|
);
|
|
[$internalUser, $internalEnvironment, $internalReview, $internalPack] = spec366BrowserCreateLayoutPack(
|
|
user: $user,
|
|
workspaceId: (int) $customerEnvironment->workspace_id,
|
|
environmentName: 'Spec366 Browser Internal',
|
|
customerSafeReady: true,
|
|
packOverrides: [
|
|
'options' => [
|
|
'include_pii' => true,
|
|
'include_operations' => true,
|
|
],
|
|
],
|
|
);
|
|
|
|
spec366BrowserAuthenticate($this, $user, $customerEnvironment);
|
|
|
|
$customerExecutivePage = visit(spec366BrowserRenderedReportUrl($customerPack, $customerReview, ReportProfileRegistry::CUSTOMER_EXECUTIVE))
|
|
->resize(1280, 1440)
|
|
->waitForText(__('localization.review.report_profile_customer_executive'))
|
|
->assertSee(__('localization.review.report_kpi_key_risks'))
|
|
->assertSee(__('localization.review.report_kpi_open_decisions'))
|
|
->assertSee('Spec366 Browser Control')
|
|
->assertSee(__('localization.review.report_appendix_hidden_for_profile'))
|
|
->assertDontSee('Spec366 Browser Technical Appendix')
|
|
->assertScript('document.querySelector("[data-testid=\"rendered-report-canvas\"]").dataset.layoutMode === "executive"', true)
|
|
->assertScript('Boolean(document.querySelector("[data-testid=\"rendered-report-toolbar\"]").compareDocumentPosition(document.querySelector("[data-testid=\"rendered-report-canvas\"]")) & Node.DOCUMENT_POSITION_FOLLOWING)', true)
|
|
->assertScript('(() => { const control = document.querySelector("[data-testid=\"rendered-report-toolbar\"] a, [data-testid=\"rendered-report-print-action\"]"); control?.focus(); return document.activeElement === control; })()', true)
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$customerExecutivePage->screenshot(true, spec366BrowserScreenshotName('01-customer-executive-report'));
|
|
spec366CopyBrowserScreenshot('01-customer-executive-report');
|
|
|
|
$customerExecutivePage->script('document.body.classList.add("print-preview-smoke"); window.scrollTo(0, 0);');
|
|
$customerExecutivePage
|
|
->assertScript('window.getComputedStyle(document.querySelector("[data-testid=\"rendered-report-toolbar\"]")).display === "none"', true)
|
|
->assertScript('document.querySelector("[data-testid=\"rendered-report-canvas\"]").offsetParent !== null', true)
|
|
->assertScript('document.querySelector("[data-testid=\"rendered-report-disclosure\"]") instanceof HTMLElement', true)
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$customerExecutivePage->screenshot(true, spec366BrowserScreenshotName('06-print-view'));
|
|
spec366CopyBrowserScreenshot('06-print-view');
|
|
$customerExecutivePage->screenshot(false, spec366BrowserScreenshotName('07-report-toolbar-hidden-print'));
|
|
spec366CopyBrowserScreenshot('07-report-toolbar-hidden-print');
|
|
$customerExecutivePage->script('document.body.classList.remove("print-preview-smoke");');
|
|
|
|
visit(spec366BrowserRenderedReportUrl($limitedPack, $limitedReview, ReportProfileRegistry::CUSTOMER_EXECUTIVE))
|
|
->resize(1280, 1440)
|
|
->waitForText(__('localization.review.report_state_with_limitations'))
|
|
->assertSee(__('localization.review.report_external_sharing_warning'))
|
|
->assertSee(__('localization.review.output_limitations'))
|
|
->assertScript('document.querySelector("[data-testid=\"rendered-report-canvas\"]").dataset.layoutMode === "executive"', true)
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs()
|
|
->screenshot(true, spec366BrowserScreenshotName('02-customer-executive-limited-report'));
|
|
spec366CopyBrowserScreenshot('02-customer-executive-limited-report');
|
|
|
|
visit(spec366BrowserRenderedReportUrl($internalPack, $internalReview, ReportProfileRegistry::INTERNAL_MSP_REVIEW))
|
|
->resize(1280, 1440)
|
|
->waitForText(__('localization.review.report_profile_internal_msp_review'))
|
|
->assertSee(__('localization.review.report_state_internal_with_limitations'))
|
|
->assertSee('Spec366 Browser Technical Appendix')
|
|
->assertScript('document.querySelector("[data-testid=\"rendered-report-canvas\"]").dataset.layoutMode === "internal"', true)
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs()
|
|
->screenshot(true, spec366BrowserScreenshotName('03-internal-msp-report'));
|
|
spec366CopyBrowserScreenshot('03-internal-msp-report');
|
|
|
|
visit(spec366BrowserRenderedReportUrl($customerPack, $customerReview, ReportProfileRegistry::CUSTOMER_TECHNICAL))
|
|
->resize(390, 1100)
|
|
->waitForText(__('localization.review.report_profile_customer_technical'))
|
|
->assertSee(__('localization.review.report_kpi_key_risks'))
|
|
->assertSee('Spec366 Browser Technical Appendix')
|
|
->assertScript('document.querySelector("[data-testid=\"rendered-report-canvas\"]").dataset.layoutMode === "technical"', true)
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs()
|
|
->screenshot(true, spec366BrowserScreenshotName('04-customer-technical-report'));
|
|
spec366CopyBrowserScreenshot('04-customer-technical-report');
|
|
|
|
visit(spec366BrowserRenderedReportUrl($customerPack, $customerReview, ReportProfileRegistry::AUDITOR_APPENDIX))
|
|
->resize(1280, 1440)
|
|
->waitForText(__('localization.review.report_profile_auditor_appendix'))
|
|
->assertSee(__('localization.review.report_audience_controlled_auditor'))
|
|
->assertSee('Spec366 Browser Technical Appendix')
|
|
->assertScript('document.querySelector("[data-testid=\"rendered-report-canvas\"]").dataset.layoutMode === "auditor_appendix"', true)
|
|
->assertScript('document.querySelector("[data-testid=\"rendered-report-canvas\"]").dataset.appendixProminence === "high"', true)
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs()
|
|
->screenshot(true, spec366BrowserScreenshotName('05-auditor-appendix-report'));
|
|
spec366CopyBrowserScreenshot('05-auditor-appendix-report');
|
|
|
|
expect($limitedUser)->toBeInstanceOf(User::class)
|
|
->and($limitedEnvironment)->toBeInstanceOf(ManagedEnvironment::class)
|
|
->and($internalUser)->toBeInstanceOf(User::class)
|
|
->and($internalEnvironment)->toBeInstanceOf(ManagedEnvironment::class);
|
|
});
|
|
|
|
function spec366BrowserScreenshotName(string $name): string
|
|
{
|
|
return $name;
|
|
}
|
|
|
|
function spec366CopyBrowserScreenshot(string $name): void
|
|
{
|
|
$filename = spec366BrowserScreenshotName($name).'.png';
|
|
$primarySource = base_path('tests/Browser/Screenshots/'.$filename);
|
|
$fallbackSource = \Pest\Browser\Support\Screenshot::path($filename);
|
|
$targetDirectories = array_values(array_unique([
|
|
repo_path('specs/366-management-report-layout-branded-report-themes-v1/artifacts/screenshots'),
|
|
base_path('specs/366-management-report-layout-branded-report-themes-v1/artifacts/screenshots'),
|
|
]));
|
|
|
|
foreach ($targetDirectories as $targetDirectory) {
|
|
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)) {
|
|
foreach ($targetDirectories as $targetDirectory) {
|
|
if (is_dir($targetDirectory) && is_writable($targetDirectory)) {
|
|
@copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$name.'.png');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function spec366BrowserAuthenticate(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);
|
|
}
|
|
|
|
function spec366BrowserRenderedReportUrl(ReviewPack $pack, EnvironmentReview $review, string $profile): string
|
|
{
|
|
return app(ReviewPackService::class)->generateRenderedReportUrl($pack, [
|
|
'source_surface' => 'review_pack',
|
|
'review_id' => (int) $review->getKey(),
|
|
'interpretation_version' => $review->controlInterpretationVersion(),
|
|
ReportProfileRegistry::QUERY_PARAMETER => $profile,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed>|null $packOverrides
|
|
* @return array{0:User,1:ManagedEnvironment,2:EnvironmentReview,3:ReviewPack}
|
|
*/
|
|
function spec366BrowserCreateLayoutPack(
|
|
?User $user = null,
|
|
?int $workspaceId = null,
|
|
string $environmentName = 'Spec366 Browser Environment',
|
|
bool $customerSafeReady = false,
|
|
bool $partialEvidence = false,
|
|
?array $packOverrides = [],
|
|
): array {
|
|
$environment = ManagedEnvironment::factory()->active()->create([
|
|
'workspace_id' => $workspaceId,
|
|
'name' => $environmentName,
|
|
]);
|
|
|
|
if ($user === null) {
|
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
|
|
} else {
|
|
createUserWithTenant(tenant: $environment, user: $user, role: 'owner', workspaceRole: 'manager');
|
|
}
|
|
|
|
$environment->workspace?->forceFill(['name' => 'Spec366 Browser MSP'])->save();
|
|
$environment = $environment->fresh('workspace');
|
|
$snapshot = seedEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0);
|
|
$review = composeEnvironmentReviewForTest($environment, $user, $snapshot);
|
|
$review->forceFill([
|
|
'status' => 'published',
|
|
'published_at' => now(),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
if ($customerSafeReady) {
|
|
$review = markEnvironmentReviewCustomerSafeReady($review);
|
|
}
|
|
|
|
if ($partialEvidence) {
|
|
restateEnvironmentReviewEvidenceSnapshot($review->evidenceSnapshot, EvidenceCompletenessState::Partial);
|
|
$review = $review->fresh(['sections', 'evidenceSnapshot']);
|
|
}
|
|
|
|
$review->loadMissing('sections');
|
|
$appendixSection = $review->sections->first();
|
|
|
|
if ($appendixSection instanceof EnvironmentReviewSection) {
|
|
$appendixSection->forceFill([
|
|
'render_payload' => array_replace_recursive(
|
|
is_array($appendixSection->render_payload) ? $appendixSection->render_payload : [],
|
|
[
|
|
'entries' => [
|
|
[
|
|
'title' => 'Spec366 Browser Technical Appendix',
|
|
'summary' => 'Visible in technical, internal, and auditor layouts.',
|
|
],
|
|
],
|
|
'highlights' => ['Spec366 browser appendix highlight.'],
|
|
],
|
|
),
|
|
])->save();
|
|
}
|
|
|
|
$filePath = 'review-packs/'.($environment->external_id ?: 'spec366').'/browser-report.zip';
|
|
Storage::disk('exports')->put($filePath, 'PK-spec366-browser-report');
|
|
|
|
$summary = array_replace_recursive([
|
|
'governance_package' => [
|
|
'executive_summary' => 'Spec 366 browser management report.',
|
|
'top_findings' => [
|
|
[
|
|
'title' => 'Spec366 Browser Control',
|
|
'summary' => 'A management-visible control requires owner awareness.',
|
|
],
|
|
],
|
|
'accepted_risks' => [],
|
|
'decision_summary' => [
|
|
'status' => 'open',
|
|
'summary' => 'A governance decision still requires stakeholder awareness.',
|
|
'next_action' => 'Confirm the owner review date.',
|
|
'entries' => [
|
|
[
|
|
'title' => 'Confirm owner review',
|
|
'summary' => 'Owner review remains the next governance step.',
|
|
],
|
|
],
|
|
],
|
|
],
|
|
'control_interpretation' => [
|
|
'non_certification_disclosure' => 'Spec 366 browser non-certification disclosure.',
|
|
],
|
|
'recommended_next_actions' => [
|
|
'Confirm the owner review date.',
|
|
],
|
|
'delivery_bundle' => [
|
|
'executive_entrypoint_file' => 'executive-summary.md',
|
|
'appendix_files' => ['metadata.json', 'summary.json', 'sections.json'],
|
|
],
|
|
], is_array($packOverrides['summary'] ?? null) ? $packOverrides['summary'] : []);
|
|
|
|
$pack = ReviewPack::factory()->ready()->create(array_merge([
|
|
'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,
|
|
],
|
|
'summary' => $summary,
|
|
'file_path' => $filePath,
|
|
'file_disk' => 'exports',
|
|
'generated_at' => now()->subMinutes(3),
|
|
'expires_at' => now()->addDay(),
|
|
], $packOverrides ?? []));
|
|
|
|
$review->forceFill([
|
|
'current_export_review_pack_id' => (int) $pack->getKey(),
|
|
])->save();
|
|
|
|
return [$user, $environment, $review->fresh(['sections', 'evidenceSnapshot', 'currentExportReviewPack']), $pack->fresh()];
|
|
}
|