external_id.'/test.zip'; $zipContents = reviewPackDownloadTestZipContents([ 'executive-summary.md' => 'Ready review pack download fixture.', ]); Storage::disk('exports')->put($filePath, $zipContents); $pack = ReviewPack::factory()->ready()->create(array_merge([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'initiated_by_user_id' => (int) $user->getKey(), 'file_path' => $filePath, 'file_disk' => 'exports', 'file_size' => strlen($zipContents), 'sha256' => hash('sha256', $zipContents), ], $packOverrides)); return [$user, $tenant, $pack]; } /** * @param array $files */ function reviewPackDownloadTestZipContents(array $files = []): string { $tempFile = tempnam(sys_get_temp_dir(), 'review-pack-download-test-'); if ($tempFile === false) { throw new RuntimeException('Failed to allocate a temporary review pack test archive.'); } try { $zip = new \ZipArchive(); $result = $zip->open($tempFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE); if ($result !== true) { throw new RuntimeException("Failed to create review pack test archive: error code {$result}"); } foreach (array_replace([ 'metadata.json' => json_encode(['fixture' => 'review-pack-download-test'], JSON_THROW_ON_ERROR), 'summary.json' => json_encode(['status' => 'ready'], JSON_THROW_ON_ERROR), ], $files) as $filename => $contents) { $zip->addFromString($filename, $contents); } if ($zip->close() !== true) { throw new RuntimeException('Failed to finalize review pack test archive.'); } $contents = file_get_contents($tempFile); if (! is_string($contents) || $contents === '') { throw new RuntimeException('Failed to read review pack test archive contents.'); } return $contents; } finally { if (file_exists($tempFile)) { unlink($tempFile); } } } function createCurrentReviewPackForRenderedReport( ?array $packOverrides = [], bool $customerSafeReady = false, ?\App\Models\EvidenceSnapshot $snapshot = null, ): array { $packOverrides ??= []; $tenant = \App\Models\ManagedEnvironment::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); $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); } $filePath = 'review-packs/'.$tenant->external_id.'/rendered-report.zip'; $zipContents = reviewPackDownloadTestZipContents([ 'executive-summary.md' => 'Rendered report download fixture.', ]); Storage::disk('exports')->put($filePath, $zipContents); $summary = array_replace_recursive([ 'governance_package' => [ 'executive_summary' => 'The released review is ready for management handoff.', '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', 'file_size' => strlen($zipContents), 'sha256' => hash('sha256', $zipContents), '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()]; } function suspendReadyPackWorkspaceForDownloadTest(ReviewPack $pack): void { app(SettingsWriter::class)->updateWorkspaceCommercialLifecycle( actor: PlatformUser::factory()->create([ 'capabilities' => [ PlatformCapabilities::ACCESS_SYSTEM_PANEL, PlatformCapabilities::DIRECTORY_VIEW, PlatformCapabilities::COMMERCIAL_LIFECYCLE_MANAGE, ], 'is_active' => true, ]), workspace: $pack->workspace, state: WorkspaceCommercialLifecycleResolver::STATE_SUSPENDED_READ_ONLY, reason: 'Download preservation test', ); } // ─── Happy Path: Signed URL → 200 ─────────────────────────── it('downloads a ready pack via signed URL with correct headers', function (): void { [$user, $tenant, $pack] = createReadyPackWithFile(); $signedUrl = app(ReviewPackService::class)->generateDownloadUrl($pack, [ 'source_surface' => 'customer_review_workspace', 'review_id' => '789', 'tenant_filter_id' => (string) $tenant->getKey(), 'interpretation_version' => 'compliance_evidence_mapping.v1', ]); $packCount = ReviewPack::query()->count(); $operationRunCount = OperationRun::query()->count(); $response = $this->actingAs($user)->get($signedUrl); $response->assertOk(); $response->assertHeader('X-Review-Pack-SHA256', $pack->sha256); $response->assertDownload(); $audit = AuditLog::query() ->where('action', AuditActionId::ReviewPackDownloaded->value) ->latest('id') ->first(); expect($audit)->not->toBeNull() ->and($audit?->resource_type)->toBe('review_pack') ->and(data_get($audit?->metadata, 'review_pack_id'))->toBe((int) $pack->getKey()) ->and(data_get($audit?->metadata, 'source_surface'))->toBe('customer_review_workspace') ->and(data_get($audit?->metadata, 'review_id'))->toBe('789') ->and(data_get($audit?->metadata, 'tenant_filter_id'))->toBe((string) $tenant->getKey()) ->and(data_get($audit?->metadata, 'interpretation_version'))->toBe('compliance_evidence_mapping.v1') ->and(ReviewPack::query()->count())->toBe($packCount) ->and(OperationRun::query()->count())->toBe($operationRunCount); }); it('keeps ready pack downloads available while the workspace is suspended read-only', function (): void { [$user, $tenant, $pack] = createReadyPackWithFile(); suspendReadyPackWorkspaceForDownloadTest($pack); $signedUrl = app(ReviewPackService::class)->generateDownloadUrl($pack, [ 'source_surface' => 'suspended_read_only_check', ]); $response = $this->actingAs($user)->get($signedUrl); $response->assertOk(); $response->assertHeader('X-Review-Pack-SHA256', $pack->sha256); $response->assertDownload(); }); it('renders the current review pack as a customer-safe management report via signed URL without creating a download audit event or provider calls', function (): void { [$user, $tenant, $review, $pack] = createCurrentReviewPackForRenderedReport(customerSafeReady: true); $signedUrl = app(ReviewPackService::class)->generateRenderedReportUrl($pack, [ 'source_surface' => 'review_pack', 'review_id' => (int) $review->getKey(), 'interpretation_version' => $review->controlInterpretationVersion(), ]); $this->app->instance(GraphClientInterface::class, new FailHardGraphClient()); $packCount = ReviewPack::query()->count(); $operationRunCount = OperationRun::query()->count(); expect(\App\Filament\Resources\EnvironmentReviewResource::renderedReportActionLabelFor($review))->toBe('View customer-safe report') ->and(\App\Filament\Resources\ReviewPackResource::downloadActionLabelFor($pack))->toBe('Download customer-safe review pack'); $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('Rendered review report') ->assertSee('Customer-safe report ready') ->assertSee('Executive summary') ->assertSee('Overall state') ->assertSee('Reason') ->assertSee('Impact') ->assertSee('Recommended next action') ->assertSee('Prepared by '.$tenant->workspace->name.' for '.$tenant->name) ->assertSee('Generated by TenantPilot') ->assertSee('Download customer-safe review pack') ->assertSee('No open risks are listed for this review.') ->assertSee('No accepted risks are listed for this review.') ->assertSee('No governance decisions require customer awareness in this released review.') ->assertSee('Evidence basis') ->assertSee('Supporting appendix') ->assertSee('Non-certification disclosure') ->assertSee('@media print', false) ->assertSee('.report-toolbar, .screen-only { display: none !important; }', false) ->assertSee((string) data_get($pack->summary, 'governance_package.executive_summary')) ->assertDontSee('Platform reason family') ->assertDontSee('localization.') ->assertDontSee('Evidence state:') ->assertDontSee('Section completeness:') ->assertDontSee('Total findings') ->assertDontSee('Certified report') ->assertDontSee('Approved compliance report') ->assertDontSee('Share with customer') ->assertDontSee('Do not share externally before review.') ->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('renders limitations near the top without claiming customer-safe readiness', function (): void { [$user, $tenant, $review, $pack] = createCurrentReviewPackForRenderedReport(customerSafeReady: true); restateEnvironmentReviewEvidenceSnapshot($review->evidenceSnapshot, EvidenceCompletenessState::Partial); $review = $review->fresh(['sections', 'evidenceSnapshot', 'currentExportReviewPack']); $pack = $pack->fresh(['environmentReview']); expect(\App\Filament\Resources\EnvironmentReviewResource::renderedReportActionLabelFor($review))->toBe('View report with limitations') ->and(\App\Filament\Resources\ReviewPackResource::downloadActionLabelFor($pack))->toBe('Download review pack with limitations'); $signedUrl = app(ReviewPackService::class)->generateRenderedReportUrl($pack, [ 'source_surface' => 'review_pack', 'review_id' => (int) $review->getKey(), 'interpretation_version' => $review->controlInterpretationVersion(), ]); $response = $this->actingAs($user)->get($signedUrl); $content = (string) $response->getContent(); $response->assertOk() ->assertSee('Report with limitations') ->assertSee('Do not share externally before review.') ->assertSee('Output limitations') ->assertSee('Download review pack with limitations') ->assertSee('The evidence basis is incomplete, stale, or missing.') ->assertSee('Review or refresh the evidence basis before external sharing.') ->assertSee('This report is anchored to Evidence snapshot #') ->assertDontSee('Customer-safe report ready') ->assertDontSee('Customer-ready report') ->assertDontSee('Certified report') ->assertDontSee('Approved compliance report') ->assertDontSee('Share with customer') ->assertDontSee('localization.'); expect(strpos($content, 'data-testid="rendered-report-output-limitations"')) ->toBeLessThan(strpos($content, 'data-testid="rendered-report-evidence-basis"')); }); it('renders internal pii reports with a visible internal warning and qualified labels', function (): void { [$user, $tenant, $review, $pack] = createCurrentReviewPackForRenderedReport( packOverrides: [ 'options' => [ 'include_pii' => true, 'include_operations' => true, ], ], customerSafeReady: true, ); expect(\App\Filament\Resources\EnvironmentReviewResource::renderedReportActionLabelFor($review))->toBe('View internal report') ->and(\App\Filament\Resources\ReviewPackResource::downloadActionLabelFor($pack))->toBe('Download internal review pack'); $signedUrl = app(ReviewPackService::class)->generateRenderedReportUrl($pack, [ 'source_surface' => 'review_pack', 'review_id' => (int) $review->getKey(), 'interpretation_version' => $review->controlInterpretationVersion(), ]); $this->actingAs($user) ->get($signedUrl) ->assertOk() ->assertSee('Internal report with limitations') ->assertSee('Do not share externally before review.') ->assertSee('This output includes internal or PII-bearing detail.') ->assertSee('Download internal review pack') ->assertDontSee('Customer-safe report ready') ->assertDontSee('Customer-ready report') ->assertDontSee('Certified report') ->assertDontSee('Approved compliance report') ->assertDontSee('Share with customer') ->assertDontSee('localization.'); }); it('renders localized report chrome and copy without exposing localization keys', function (): void { [$user, $tenant, $review, $pack] = createCurrentReviewPackForRenderedReport(customerSafeReady: true); $user->forceFill(['preferred_locale' => 'de'])->save(); $signedUrl = app(ReviewPackService::class)->generateRenderedReportUrl($pack, [ 'source_surface' => 'review_pack', 'review_id' => (int) $review->getKey(), 'interpretation_version' => $review->controlInterpretationVersion(), ]); $this->actingAs($user) ->get($signedUrl) ->assertOk() ->assertSee('Kundensicherer Bericht bereit') ->assertSee('Executive-Zusammenfassung') ->assertSee('Erstellt von '.$tenant->workspace->name.' für '.$tenant->name) ->assertSee('Unterstützender Anhang') ->assertSee('Nicht-Zertifizierungs-Offenlegung') ->assertDontSee('localization.'); }); it('renders accepted risks from customer-safe summaries without leaking internal rationale', function (): void { [$user, $tenant, $review, $pack] = createCurrentReviewPackForRenderedReport( packOverrides: [ 'summary' => [ 'governance_package' => [ 'accepted_risks' => [ [ 'title' => 'MFA exception accepted during migration', 'governance_state' => 'expiring_exception', 'customer_safe_summary' => 'The exception is time-bound and tracked for customer awareness.', 'summary' => 'Internal committee rationale must stay internal.', 'owner_label' => 'Customer Success Lead', 'expires_at' => '2026-07-15', ], [ 'title' => 'Legacy device exception', 'governance_state' => 'expired_exception', 'summary' => 'Internal rationale without customer-safe copy.', 'owner_label' => 'Security Operations', 'review_due_at' => '2026-06-01', ], ], ], ], ], customerSafeReady: true, ); $signedUrl = app(ReviewPackService::class)->generateRenderedReportUrl($pack, [ 'source_surface' => 'review_pack', 'review_id' => (int) $review->getKey(), 'interpretation_version' => $review->controlInterpretationVersion(), ]); $this->actingAs($user) ->get($signedUrl) ->assertOk() ->assertSee('MFA exception accepted during migration') ->assertSee('Expiring soon') ->assertSee('Expires on 2026-07-15') ->assertSee('Owner: Customer Success Lead') ->assertSee('The exception is time-bound and tracked for customer awareness.') ->assertSee('Legacy device exception') ->assertSee('Expired') ->assertSee('Review due on 2026-06-01') ->assertSee('A customer-safe accepted-risk summary is not recorded.') ->assertDontSee('Internal committee rationale must stay internal.') ->assertDontSee('Internal rationale without customer-safe copy.') ->assertDontSee('Certified report') ->assertDontSee('Approved compliance report') ->assertDontSee('localization.'); }); it('returns 404 for a rendered report when the pack is no longer the current export', function (): void { [$user, $tenant, $review] = createCurrentReviewPackForRenderedReport(); $oldPack = ReviewPack::factory()->ready()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'environment_review_id' => (int) $review->getKey(), 'evidence_snapshot_id' => (int) $review->evidence_snapshot_id, 'initiated_by_user_id' => (int) $user->getKey(), 'expires_at' => now()->addDay(), ]); $signedUrl = app(ReviewPackService::class)->generateRenderedReportUrl($oldPack, [ 'source_surface' => 'review_pack', 'review_id' => (int) $review->getKey(), ]); $this->actingAs($user) ->get($signedUrl) ->assertNotFound(); }); it('returns 404 for a rendered report when the user is not a tenant member', function (): void { [$owner, $tenant, $review, $pack] = createCurrentReviewPackForRenderedReport(); $otherTenant = \App\Models\ManagedEnvironment::factory()->create(); [$otherUser] = createUserWithTenant(tenant: $otherTenant, role: 'owner'); $signedUrl = app(ReviewPackService::class)->generateRenderedReportUrl($pack, [ 'source_surface' => 'review_pack', 'review_id' => (int) $review->getKey(), ]); $this->actingAs($otherUser) ->get($signedUrl) ->assertNotFound(); }); // ─── Expired Signature → 403 ──────────────────────────────── it('rejects requests with an expired signature', function (): void { [$user, $tenant, $pack] = createReadyPackWithFile(); // Generate a signed URL that expires immediately $signedUrl = URL::signedRoute( 'admin.review-packs.download', ['reviewPack' => $pack->getKey()], now()->subMinute(), ); $response = $this->actingAs($user)->get($signedUrl); $response->assertForbidden(); }); // ─── Expired Pack → 404 ───────────────────────────────────── it('returns 404 for an expired pack', function (): void { [$user, $tenant, $pack] = createReadyPackWithFile([ 'status' => ReviewPackStatus::Expired->value, ]); $signedUrl = app(ReviewPackService::class)->generateDownloadUrl($pack); $response = $this->actingAs($user)->get($signedUrl); $response->assertNotFound(); }); // ─── Non-Ready Pack → 404 ─────────────────────────────────── it('returns 404 for a queued pack', function (): void { [$user, $tenant] = createUserWithTenant(); $pack = ReviewPack::factory()->queued()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'initiated_by_user_id' => (int) $user->getKey(), ]); $signedUrl = URL::signedRoute( 'admin.review-packs.download', ['reviewPack' => $pack->getKey()], now()->addHour(), ); $response = $this->actingAs($user)->get($signedUrl); $response->assertNotFound(); }); // ─── Non-Existent Pack → 404 ──────────────────────────────── it('returns 404 for a non-existent pack', function (): void { [$user, $tenant] = createUserWithTenant(); $signedUrl = URL::signedRoute( 'admin.review-packs.download', ['reviewPack' => 99999], now()->addHour(), ); $response = $this->actingAs($user)->get($signedUrl); $response->assertNotFound(); }); // ─── Past Expiry Date → 404 ───────────────────────────────── it('returns 404 when pack status is ready but expires_at is in the past', function (): void { [$user, $tenant, $pack] = createReadyPackWithFile([ 'expires_at' => now()->subDay(), ]); $signedUrl = URL::signedRoute( 'admin.review-packs.download', ['reviewPack' => $pack->getKey()], now()->addHour(), ); $response = $this->actingAs($user)->get($signedUrl); $response->assertNotFound(); }); // ─── Missing File on Disk → 404 ───────────────────────────── it('returns 404 when file does not exist on disk', function (): void { [$user, $tenant] = createUserWithTenant(); $pack = ReviewPack::factory()->ready()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'initiated_by_user_id' => (int) $user->getKey(), 'file_path' => 'review-packs/does-not-exist.zip', 'file_disk' => 'exports', ]); $signedUrl = URL::signedRoute( 'admin.review-packs.download', ['reviewPack' => $pack->getKey()], now()->addHour(), ); $response = $this->actingAs($user)->get($signedUrl); $response->assertNotFound(); }); // ─── Unsigned URL → 403 ───────────────────────────────────── it('returns 403 for an unsigned URL', function (): void { [$user, $tenant, $pack] = createReadyPackWithFile(); $response = $this->actingAs($user)->get( route('admin.review-packs.download', ['reviewPack' => $pack->getKey()]), ); $response->assertForbidden(); });