190 lines
7.5 KiB
PHP
190 lines
7.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\AuditLog;
|
|
use App\Models\ReviewPack;
|
|
use App\Models\StoredReport;
|
|
use App\Services\ReviewPacks\ManagementReportPdfService;
|
|
use App\Services\ReviewPackService;
|
|
use App\Support\Audit\AuditActionId;
|
|
use App\Support\Evidence\EvidenceCompletenessState;
|
|
use App\Support\ReviewPacks\CustomerOutputGate;
|
|
use App\Support\ReviewPacks\ReportProfileRegistry;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function (): void {
|
|
Storage::fake('exports');
|
|
});
|
|
|
|
function spec392RouteCurrentReviewPack(bool $customerSafeReady = true): array
|
|
{
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner', clearCapabilityCaches: true);
|
|
$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();
|
|
$review = markEnvironmentReviewCustomerSafeReady($review);
|
|
|
|
if (! $customerSafeReady) {
|
|
restateEnvironmentReviewEvidenceSnapshot($review->evidenceSnapshot, EvidenceCompletenessState::Partial);
|
|
$review = $review->fresh(['sections', 'evidenceSnapshot']);
|
|
}
|
|
|
|
$zipBytes = 'spec392 review pack bytes';
|
|
$filePath = sprintf('review-packs/%s/spec392-current.zip', $tenant->external_id);
|
|
Storage::disk('exports')->put($filePath, $zipBytes);
|
|
|
|
$pack = 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(),
|
|
'options' => [
|
|
'include_pii' => false,
|
|
'include_operations' => true,
|
|
],
|
|
'summary' => [
|
|
'governance_package' => [
|
|
'executive_summary' => 'Spec392 management-ready review pack.',
|
|
'top_findings' => [],
|
|
'accepted_risks' => [],
|
|
'decision_summary' => [
|
|
'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.',
|
|
],
|
|
'delivery_bundle' => [
|
|
'executive_entrypoint_file' => 'executive-summary.md',
|
|
'appendix_files' => ['metadata.json'],
|
|
],
|
|
],
|
|
'file_disk' => 'exports',
|
|
'file_path' => $filePath,
|
|
'file_size' => strlen($zipBytes),
|
|
'sha256' => hash('sha256', $zipBytes),
|
|
'expires_at' => now()->addDay(),
|
|
]);
|
|
|
|
$review->forceFill([
|
|
'current_export_review_pack_id' => (int) $pack->getKey(),
|
|
])->save();
|
|
|
|
return [$user, $tenant, $review->fresh(['sections', 'evidenceSnapshot', 'currentExportReviewPack']), $pack->fresh(['tenant', 'environmentReview'])];
|
|
}
|
|
|
|
function spec392ReadyManagementPdf(ReviewPack $pack): StoredReport
|
|
{
|
|
$pdfBytes = '%PDF-1.7 Spec392 management report';
|
|
$filePath = sprintf('management-reports/%s/spec392-%d.pdf', $pack->tenant->external_id, (int) $pack->getKey());
|
|
Storage::disk('exports')->put($filePath, $pdfBytes);
|
|
|
|
return StoredReport::factory()->managementReportPdf([
|
|
'title' => 'Spec392 Management Report',
|
|
])->create([
|
|
'workspace_id' => (int) $pack->workspace_id,
|
|
'managed_environment_id' => (int) $pack->managed_environment_id,
|
|
'source_environment_review_id' => (int) $pack->environment_review_id,
|
|
'source_review_pack_id' => (int) $pack->getKey(),
|
|
'profile' => ReportProfileRegistry::CUSTOMER_EXECUTIVE,
|
|
'file_disk' => 'exports',
|
|
'file_path' => $filePath,
|
|
'file_size' => strlen($pdfBytes),
|
|
'sha256' => hash('sha256', $pdfBytes),
|
|
'generated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
it('Spec392 blocks unsafe customer-output downloads but allows authorized internal preview', function (): void {
|
|
[$owner, $tenant, $review, $pack] = spec392RouteCurrentReviewPack(customerSafeReady: false);
|
|
[$readonly] = createUserWithTenant(
|
|
tenant: $tenant,
|
|
user: \App\Models\User::factory()->create(),
|
|
role: 'readonly',
|
|
clearCapabilityCaches: true,
|
|
);
|
|
|
|
$customerUrl = app(ReviewPackService::class)->generateDownloadUrl($pack, [
|
|
'source_surface' => 'customer_review_workspace',
|
|
'review_id' => (int) $review->getKey(),
|
|
]);
|
|
$internalPreviewUrl = app(ReviewPackService::class)->generateDownloadUrl($pack, [
|
|
'source_surface' => 'review_pack',
|
|
CustomerOutputGate::INTERNAL_PREVIEW_QUERY_KEY => 1,
|
|
]);
|
|
|
|
$this->actingAs($owner)->get($customerUrl)->assertForbidden();
|
|
$this->actingAs($readonly)->get($internalPreviewUrl)->assertForbidden();
|
|
|
|
$this->actingAs($owner)
|
|
->get($internalPreviewUrl)
|
|
->assertOk()
|
|
->assertHeader('X-Review-Pack-SHA256', $pack->sha256)
|
|
->assertDownload();
|
|
|
|
$audit = AuditLog::query()
|
|
->where('action', AuditActionId::ReviewPackDownloaded->value)
|
|
->latest('id')
|
|
->first();
|
|
|
|
expect($audit)->not->toBeNull()
|
|
->and(data_get($audit?->metadata, 'download_mode'))->toBe('internal_preview')
|
|
->and(data_get($audit?->metadata, 'customer_output_gate_state'))->toBe(CustomerOutputGate::STATE_NEEDS_ATTENTION);
|
|
});
|
|
|
|
it('Spec392 renders unsafe reports only on internal profile routes without customer download copy', function (): void {
|
|
[$owner, $tenant, $review, $pack] = spec392RouteCurrentReviewPack(customerSafeReady: false);
|
|
[$readonly] = createUserWithTenant(
|
|
tenant: $tenant,
|
|
user: \App\Models\User::factory()->create(),
|
|
role: 'readonly',
|
|
clearCapabilityCaches: true,
|
|
);
|
|
|
|
$customerReportUrl = app(ReviewPackService::class)->generateRenderedReportUrl($pack, [
|
|
'source_surface' => 'customer_review_workspace',
|
|
\App\Filament\Pages\Reviews\CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY => 1,
|
|
'review_id' => (int) $review->getKey(),
|
|
ReportProfileRegistry::QUERY_PARAMETER => ReportProfileRegistry::CUSTOMER_EXECUTIVE,
|
|
]);
|
|
$internalReportUrl = app(ReviewPackService::class)->generateRenderedReportUrl($pack, [
|
|
'source_surface' => 'review_pack',
|
|
'review_id' => (int) $review->getKey(),
|
|
]);
|
|
|
|
$this->actingAs($owner)->get($customerReportUrl)->assertForbidden();
|
|
$this->actingAs($readonly)->get($internalReportUrl)->assertForbidden();
|
|
|
|
$this->actingAs($owner)
|
|
->get($internalReportUrl)
|
|
->assertOk()
|
|
->assertSee('Report with limitations')
|
|
->assertSee('Download internal preview')
|
|
->assertDontSee('Customer-safe report ready');
|
|
});
|
|
|
|
it('Spec392 blocks management PDF downloads when the source review pack is not customer safe', function (): void {
|
|
[$owner, , , $pack] = spec392RouteCurrentReviewPack(customerSafeReady: false);
|
|
$report = spec392ReadyManagementPdf($pack);
|
|
$url = app(ManagementReportPdfService::class)->generateDownloadUrl($report, [
|
|
'source_surface' => 'review_pack',
|
|
]);
|
|
|
|
$this->actingAs($owner)
|
|
->get($url)
|
|
->assertForbidden();
|
|
|
|
expect(AuditLog::query()
|
|
->where('action', AuditActionId::ManagementReportPdfDownloaded->value)
|
|
->count())->toBe(0);
|
|
});
|