607 lines
25 KiB
PHP
607 lines
25 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\ReviewPack;
|
|
use App\Models\AuditLog;
|
|
use App\Models\OperationRun;
|
|
use App\Models\PlatformUser;
|
|
use App\Services\Graph\GraphClientInterface;
|
|
use App\Services\Entitlements\WorkspaceCommercialLifecycleResolver;
|
|
use App\Services\ReviewPackService;
|
|
use App\Services\Settings\SettingsWriter;
|
|
use App\Support\Evidence\EvidenceCompletenessState;
|
|
use App\Support\Audit\AuditActionId;
|
|
use App\Support\Auth\PlatformCapabilities;
|
|
use App\Support\ReviewPackStatus;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Facades\URL;
|
|
use Tests\Support\FailHardGraphClient;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function (): void {
|
|
Storage::fake('exports');
|
|
});
|
|
|
|
// ─── Helper ──────────────────────────────────────────────────
|
|
|
|
function createReadyPackWithFile(?array $packOverrides = []): array
|
|
{
|
|
[$user, $tenant] = createUserWithTenant();
|
|
|
|
$filePath = 'review-packs/'.$tenant->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<string, string> $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();
|
|
});
|