## Summary - Fixes misleading “queued / running in background” message when Review Pack generation request reuses an existing ready pack (fingerprint dedupe). - Improves resilience of Filament/Livewire interactions by ensuring the Livewire intercept shim applies after Livewire initializes. - Aligns Review Pack operation notifications with Ops-UX patterns (queued + completed notifications) and removes the old ReviewPackStatusNotification. ## Key Changes - Review Pack generate action now: - Shows queued toast only when a new pack is actually created/queued. - Shows a “Review pack already available” success notification with a link when dedupe returns an existing pack. ## Tests - `vendor/bin/sail artisan test --compact tests/Feature/ReviewPack/ReviewPackGenerationTest.php` - `vendor/bin/sail artisan test --compact tests/Feature/ReviewPack/ReviewPackResourceTest.php` - `vendor/bin/sail artisan test --compact tests/Feature/LivewireInterceptShimTest.php` ## Notes - No global search behavior changes for ReviewPacks (still excluded). - Destructive actions remain confirmation-gated (`->requiresConfirmation()`). Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #133
175 lines
5.7 KiB
PHP
175 lines
5.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\ReviewPack;
|
|
use App\Services\ReviewPackService;
|
|
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];
|
|
}
|
|
|
|
// ─── 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);
|
|
|
|
$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();
|
|
});
|