TenantAtlas/apps/platform/tests/Feature/ReviewPack/Spec366RenderedReportLayoutTest.php
Ahmed Darrazi 58c0064cb0
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m1s
feat: implement management report layout branded report themes
2026-06-08 05:05:36 +02:00

398 lines
16 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\AuditLog;
use App\Models\EnvironmentReview;
use App\Models\EnvironmentReviewSection;
use App\Models\EvidenceSnapshot;
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Models\ReviewPack;
use App\Services\ReviewPackService;
use App\Support\Audit\AuditActionId;
use App\Support\Evidence\EvidenceCompletenessState;
use App\Support\ReviewPacks\ReportProfileRegistry;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
uses(RefreshDatabase::class);
beforeEach(function (): void {
Storage::fake('exports');
});
it('renders a management-ready first screen with repo-backed identity, KPIs, and safe copy', function (): void {
[$user, $tenant, $review, $pack] = spec366CreateRenderedReportPack(
packOverrides: [
'summary' => [
'governance_package' => [
'executive_summary' => 'Spec 366 executive narrative for management readers.',
'top_findings' => [
[
'title' => 'Conditional access drift',
'summary' => 'One policy drift requires owner awareness.',
],
],
'accepted_risks' => [
[
'title' => 'Migration exception',
'governance_state' => 'valid_exception',
'customer_safe_summary' => 'The exception is time-bound and governance-tracked.',
'owner_label' => 'Service Delivery',
'expires_at' => '2026-07-15',
],
],
'decision_summary' => [
'status' => 'open',
'summary' => 'One governance decision requires owner awareness.',
'next_action' => 'Brief named stakeholders and retain the structured appendix.',
'entries' => [
[
'title' => 'Retain monitoring exception',
'summary' => 'Owner accepted limited monitoring until the next review.',
'next_action' => 'Confirm the next review date.',
],
],
],
],
'recommended_next_actions' => [
'Brief named stakeholders and retain the ZIP package as the structured appendix.',
],
],
],
customerSafeReady: true,
);
$signedUrl = spec366RenderedReportUrl($pack, $review, ReportProfileRegistry::CUSTOMER_EXECUTIVE);
bindFailHardGraphClient();
$packCount = ReviewPack::query()->count();
$operationRunCount = OperationRun::query()->count();
$response = $this->actingAs($user)->get($signedUrl);
$content = (string) $response->getContent();
$toolbarPosition = strpos($content, 'data-testid="rendered-report-toolbar"');
$canvasPosition = strpos($content, 'data-testid="rendered-report-canvas"');
$response->assertOk()
->assertSee('Spec 366 executive narrative for management readers.')
->assertSee('Prepared by Spec366 MSP for Spec366 Production')
->assertSee('Generated by TenantPilot')
->assertSee('Customer-safe report ready')
->assertSee('Report decision strip')
->assertSee('Governance status')
->assertSee('Evidence coverage')
->assertSee('Key risks')
->assertSee('Open decisions')
->assertSee('Conditional access drift')
->assertSee('Retain monitoring exception')
->assertSee('Migration exception')
->assertSee('Audience')
->assertSee('Non-certification disclosure')
->assertSee('Supporting appendix')
->assertSee('data-layout-mode="executive"', false)
->assertSee('data-appendix-prominence="minimal"', false)
->assertSee('data-testid="rendered-report-kpi-strip"', false)
->assertSee('body.print-preview-smoke .report-toolbar', false)
->assertSee('@media print', false)
->assertDontSee('localization.')
->assertDontSee('Certified report')
->assertDontSee('Approved compliance report')
->assertDontSee('Share with customer')
->assertDontSee((string) $review->fingerprint);
expect($toolbarPosition)->not->toBeFalse()
->and($canvasPosition)->not->toBeFalse()
->and($toolbarPosition)->toBeLessThan($canvasPosition)
->and(AuditLog::query()->where('action', AuditActionId::ReviewPackDownloaded->value)->count())->toBe(0)
->and(ReviewPack::query()->count())->toBe($packCount)
->and(OperationRun::query()->count())->toBe($operationRunCount);
});
it('keeps limited and PII-bearing report states visibly bounded', function (): void {
[$limitedUser, $limitedTenant, $limitedReview, $limitedPack] = spec366CreateRenderedReportPack(customerSafeReady: true);
restateEnvironmentReviewEvidenceSnapshot($limitedReview->evidenceSnapshot, EvidenceCompletenessState::Partial);
$limitedResponse = $this->actingAs($limitedUser)->get(
spec366RenderedReportUrl(
$limitedPack->fresh(['environmentReview']),
$limitedReview->fresh(['sections', 'evidenceSnapshot', 'currentExportReviewPack']),
ReportProfileRegistry::CUSTOMER_EXECUTIVE,
),
);
$limitedResponse->assertOk()
->assertSee('Report with limitations')
->assertSee('Do not share externally before review.')
->assertSee('Output limitations')
->assertSee('Review or refresh the evidence basis before external sharing.')
->assertSee('data-layout-mode="executive"', false)
->assertSee('data-section-rank="30"', false)
->assertDontSee('Customer-ready report')
->assertDontSee('Certified report')
->assertDontSee('Approved compliance report')
->assertDontSee('Share with customer')
->assertDontSee('localization.');
expect($limitedTenant)->toBeInstanceOf(ManagedEnvironment::class);
[$piiUser, $piiTenant, $piiReview, $piiPack] = spec366CreateRenderedReportPack(
packOverrides: [
'options' => [
'include_pii' => true,
'include_operations' => true,
],
],
customerSafeReady: true,
environmentName: 'Spec366 PII Production',
);
$this->actingAs($piiUser)
->get(spec366RenderedReportUrl($piiPack, $piiReview, ReportProfileRegistry::CUSTOMER_EXECUTIVE))
->assertOk()
->assertSee('Internal report with limitations')
->assertSee('Do not share externally before review.')
->assertSee('Customer-facing profile blocked by internal-only detail')
->assertSee('Protected values boundary')
->assertSee('data-layout-mode="executive"', false)
->assertDontSee('Customer-safe report ready')
->assertDontSee('Certified report')
->assertDontSee('Approved compliance report')
->assertDontSee('Share with customer')
->assertDontSee('localization.');
expect($piiTenant)->toBeInstanceOf(ManagedEnvironment::class);
});
it('renders profile-aware hierarchy and keeps fallback requests visible', function (): void {
[$user, $tenant, $review, $pack] = spec366CreateRenderedReportPack(customerSafeReady: true);
$profiles = [
ReportProfileRegistry::CUSTOMER_EXECUTIVE => [
'mode' => 'executive',
'prominence' => 'minimal',
'label' => 'Customer executive',
'shows_appendix' => false,
'fallback' => false,
],
ReportProfileRegistry::CUSTOMER_TECHNICAL => [
'mode' => 'technical',
'prominence' => 'standard',
'label' => 'Customer technical',
'shows_appendix' => true,
'fallback' => false,
],
ReportProfileRegistry::INTERNAL_MSP_REVIEW => [
'mode' => 'internal',
'prominence' => 'standard',
'label' => 'Internal MSP review',
'shows_appendix' => true,
'fallback' => false,
],
ReportProfileRegistry::AUDITOR_APPENDIX => [
'mode' => 'auditor_appendix',
'prominence' => 'high',
'label' => 'Auditor appendix',
'shows_appendix' => true,
'fallback' => false,
],
ReportProfileRegistry::FRAMEWORK_READINESS => [
'mode' => 'internal',
'prominence' => 'standard',
'label' => 'Internal MSP review',
'shows_appendix' => true,
'fallback' => true,
],
];
foreach ($profiles as $requestedProfile => $expectation) {
$response = $this->actingAs($user)->get(spec366RenderedReportUrl($pack, $review, $requestedProfile));
$response->assertOk()
->assertSee($expectation['label'])
->assertSee('data-layout-mode="'.$expectation['mode'].'"', false)
->assertSee('data-appendix-prominence="'.$expectation['prominence'].'"', false)
->assertDontSee('localization.');
if ($expectation['shows_appendix']) {
$response->assertSee('Spec366 Technical Control')
->assertDontSee(__('localization.review.report_appendix_hidden_for_profile'));
} else {
$response->assertSee(__('localization.review.report_appendix_hidden_for_profile'))
->assertDontSee('Spec366 Technical Control');
}
if ($expectation['fallback']) {
$response->assertSee(__('localization.review.report_profile_fallback_notice'))
->assertSee(ReportProfileRegistry::FRAMEWORK_READINESS)
->assertSee(ReportProfileRegistry::INTERNAL_MSP_REVIEW);
}
}
expect($tenant)->toBeInstanceOf(ManagedEnvironment::class);
});
it('preserves rendered route guards and the Review Pack ZIP download contract', function (): void {
[$owner, $tenant, $review, $pack] = spec366CreateRenderedReportPack(customerSafeReady: true);
$renderedUrl = spec366RenderedReportUrl($pack, $review, ReportProfileRegistry::CUSTOMER_EXECUTIVE);
$downloadUrl = app(ReviewPackService::class)->generateDownloadUrl($pack, [
'source_surface' => 'review_pack',
'review_id' => (int) $review->getKey(),
'interpretation_version' => $review->controlInterpretationVersion(),
]);
$this->actingAs($owner)
->get($renderedUrl)
->assertOk();
$this->actingAs($owner)
->get($downloadUrl)
->assertOk()
->assertDownload();
[$outsider] = createUserWithTenant();
$this->actingAs($outsider)
->get($renderedUrl)
->assertNotFound();
[$guardUser, $guardTenant, $guardReview, $guardPack] = spec366CreateRenderedReportPack(
customerSafeReady: true,
environmentName: 'Spec366 Guard Production',
);
$guardUrl = spec366RenderedReportUrl($guardPack, $guardReview, ReportProfileRegistry::CUSTOMER_EXECUTIVE);
$guardReview->forceFill(['current_export_review_pack_id' => null])->save();
$this->actingAs($guardUser)
->get($guardUrl)
->assertNotFound();
[$expiredUser, $expiredTenant, $expiredReview, $expiredPack] = spec366CreateRenderedReportPack(
customerSafeReady: true,
environmentName: 'Spec366 Expired Production',
);
$expiredUrl = spec366RenderedReportUrl($expiredPack, $expiredReview, ReportProfileRegistry::CUSTOMER_EXECUTIVE);
$expiredPack->forceFill(['expires_at' => now()->subMinute()])->save();
$this->actingAs($expiredUser)
->get($expiredUrl)
->assertNotFound();
expect($tenant)->toBeInstanceOf(ManagedEnvironment::class)
->and($guardTenant)->toBeInstanceOf(ManagedEnvironment::class)
->and($expiredTenant)->toBeInstanceOf(ManagedEnvironment::class);
});
function spec366RenderedReportUrl(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:\App\Models\User,1:ManagedEnvironment,2:EnvironmentReview,3:ReviewPack}
*/
function spec366CreateRenderedReportPack(
?array $packOverrides = [],
bool $customerSafeReady = false,
?EvidenceSnapshot $snapshot = null,
string $environmentName = 'Spec366 Production',
): array {
$packOverrides ??= [];
$tenant = ManagedEnvironment::factory()->active()->create([
'name' => $environmentName,
]);
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner', workspaceRole: 'manager');
$tenant->workspace?->forceFill(['name' => 'Spec366 MSP'])->save();
$tenant = $tenant->fresh('workspace');
$snapshot ??= seedEnvironmentReviewEvidence($tenant, findingCount: 0, driftCount: 0);
$review = composeEnvironmentReviewForTest($tenant, $user, $snapshot);
$review->forceFill([
'status' => 'published',
'published_at' => now(),
'published_by_user_id' => (int) $user->getKey(),
])->save();
if ($customerSafeReady) {
$review = markEnvironmentReviewCustomerSafeReady($review);
}
$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 Technical Control',
'summary' => 'Visible only when the profile allows detailed appendix content.',
],
],
'highlights' => ['Spec366 appendix highlight.'],
],
),
])->save();
}
$filePath = 'review-packs/'.$tenant->external_id.'/spec366-rendered-report.zip';
Storage::disk('exports')->put($filePath, 'PK-spec366-rendered-report-content');
$summary = array_replace_recursive([
'governance_package' => [
'executive_summary' => 'Spec 366 management report summary.',
'evidence_basis_summary' => 'The report is anchored to the current released evidence snapshot.',
'top_findings' => [],
'accepted_risks' => [],
'decision_summary' => [
'status' => 'none',
'summary' => '',
'next_action' => '',
'entries' => [],
],
],
'control_interpretation' => [
'non_certification_disclosure' => 'TenantPilot summarizes available service-delivery evidence for governance review. This report is not a certification, legal attestation, audit opinion, or compliance guarantee.',
],
'recommended_next_actions' => [],
'delivery_bundle' => [
'executive_entrypoint_file' => 'executive-summary.md',
'appendix_files' => ['metadata.json', 'summary.json', 'sections.json'],
],
], is_array($packOverrides['summary'] ?? null) ? $packOverrides['summary'] : []);
$packAttributes = array_merge([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->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',
'sha256' => hash('sha256', 'PK-spec366-rendered-report-content'),
'generated_at' => now()->subMinutes(5),
'expires_at' => now()->addDay(),
], $packOverrides);
$packAttributes['summary'] = $summary;
$pack = ReviewPack::factory()->ready()->create($packAttributes);
$review->forceFill([
'current_export_review_pack_id' => (int) $pack->getKey(),
])->save();
return [$user, $tenant, $review->fresh(['sections', 'evidenceSnapshot', 'currentExportReviewPack']), $pack->fresh()];
}