TenantAtlas/apps/platform/tests/Feature/ReviewPack/ReviewPackDownloadTest.php
ahmido 55338a88c6
Some checks failed
Main Confidence / confidence (push) Failing after 59s
merge: platform-dev into dev (#311)
## Summary
- sync platform-dev back into dev with the latest integrated feature and spec work
- include the customer review workspace productization flow and its related review, review-pack, evidence, audit, and test updates
- carry forward the recent governance and roadmap/spec updates already merged on platform-dev

## Included highlights
- customer review workspace productization and customer-safe released-review drilldown
- governance decision convergence work
- cross-tenant compare and promotion work
- external support desk handoff work
- product, roadmap, permissions, and spec artifact updates

## Validation context
- platform-dev currently contains the already-validated feature work from the merged branch PRs
- latest customer review workspace batch included focused Pest suites, one bounded browser smoke, and Pint

## Notes
- this is an integration PR from platform-dev into dev
- no separate provider-registration or asset-strategy expansion is introduced by the customer review workspace slice

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #311
2026-04-30 18:33:56 +00:00

230 lines
7.8 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\Entitlements\WorkspaceCommercialLifecycleResolver;
use App\Services\ReviewPackService;
use App\Services\Settings\SettingsWriter;
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;
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';
Storage::disk('exports')->put($filePath, 'PK-fake-zip-content');
$pack = ReviewPack::factory()->ready()->create(array_merge([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'initiated_by_user_id' => (int) $user->getKey(),
'file_path' => $filePath,
'file_disk' => 'exports',
'sha256' => hash('sha256', 'PK-fake-zip-content'),
], $packOverrides));
return [$user, $tenant, $pack];
}
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',
]);
$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(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();
});
// ─── 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([
'tenant_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([
'tenant_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();
});