Files
SealShare/tests/Feature/ShareDownloadTest.php
T
surtic86andClaude Opus 4.6 729e01462b Replace ZipStream with native ZipArchive for FrankenPHP compatibility
ZipStream writes via fwrite(php://output) which FrankenPHP silently
drops, resulting in 0-byte ZIP downloads. Switch to ZipArchive to build
the ZIP as a temp file on disk, then serve with response()->download().

- Rewrite download() to use ZipArchive + BinaryFileResponse
- Remove decryptFileToCallback() from FileEncryptionService
- Update tests for BinaryFileResponse instead of StreamedResponse
- Remove maennchen/zipstream-php dependency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:34:05 +01:00

212 lines
6.2 KiB
PHP

<?php
use App\Models\Share;
use App\Services\FileEncryptionService;
use App\Services\ShareService;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Livewire\Livewire;
test('share download page renders for valid share', function () {
Storage::fake('shares');
$share = createShareWithFile();
$response = $this->get(route('share.download', $share));
$response->assertOk();
});
test('share download page returns 404 for expired share', function () {
$share = Share::factory()->expired()->create();
$response = $this->get(route('share.download', $share));
$response->assertNotFound();
});
test('share download page returns 404 when download limit reached', function () {
$share = Share::factory()->withMaxDownloads(1)->create(['download_count' => 1]);
$response = $this->get(route('share.download', $share));
$response->assertNotFound();
});
test('share download page shows password form for password-protected share', function () {
Storage::fake('shares');
$share = createShareWithFile('secret-pass');
$response = $this->get(route('share.download', $share));
$response->assertOk();
$response->assertSee('password');
});
test('password verification works for protected share', function () {
Storage::fake('shares');
$share = createShareWithFile('my-password');
Livewire::test(\App\Livewire\ShareDownload::class, ['share' => $share])
->assertSet('authenticated', false)
->set('password', 'my-password')
->call('verifyPassword')
->assertSet('authenticated', true)
->assertHasNoErrors();
});
test('wrong password is rejected', function () {
Storage::fake('shares');
$share = createShareWithFile('my-password');
Livewire::test(\App\Livewire\ShareDownload::class, ['share' => $share])
->set('password', 'wrong-password')
->call('verifyPassword')
->assertSet('authenticated', false)
->assertHasErrors(['password']);
});
test('non-password share shows files directly', function () {
Storage::fake('shares');
$share = createShareWithFile();
Livewire::test(\App\Livewire\ShareDownload::class, ['share' => $share])
->assertSet('authenticated', true);
});
test('download counter increments on zip download', function () {
Storage::fake('shares');
$share = createShareWithFile();
$share->load('files');
$encryptionService = app(FileEncryptionService::class);
$key = $share->encryption_key;
$content = 'test content';
foreach ($share->files as $file) {
$dir = Storage::disk('shares')->path($share->token);
if (! is_dir($dir)) {
mkdir($dir, 0755, true);
}
$encryptedPath = $dir.'/'.basename($file->stored_path);
$tempSource = tempnam(sys_get_temp_dir(), 'test');
file_put_contents($tempSource, $content);
$encryptionService->encryptFile($tempSource, $encryptedPath, $key);
unlink($tempSource);
$file->update(['file_size' => strlen($content)]);
}
$response = $this->withSession(['share_password_'.$share->token => null])
->get(route('share.download.all', $share));
$response->assertDownload();
expect($share->fresh()->download_count)->toBe(1);
});
test('zip download produces a valid archive', function () {
Storage::fake('shares');
$share = createShareWithFile();
$share->load('files');
$encryptionService = app(FileEncryptionService::class);
$key = $share->encryption_key;
$content = 'hello zip content';
foreach ($share->files as $file) {
$dir = Storage::disk('shares')->path($share->token);
if (! is_dir($dir)) {
mkdir($dir, 0755, true);
}
$encryptedPath = $dir.'/'.basename($file->stored_path);
$tempSource = tempnam(sys_get_temp_dir(), 'test');
file_put_contents($tempSource, $content);
$encryptionService->encryptFile($tempSource, $encryptedPath, $key);
unlink($tempSource);
$file->update(['file_size' => strlen($content)]);
}
$response = $this->get(route('share.download.all', $share));
$response->assertDownload();
$zipPath = $response->getFile()->getPathname();
$zip = new \ZipArchive;
$result = $zip->open($zipPath);
expect($result)->toBe(true);
expect($zip->numFiles)->toBe(1);
expect($zip->statIndex(0)['size'])->toBe(strlen($content));
$zip->close();
});
test('last download streams successfully before auto-delete', function () {
Storage::fake('shares');
$share = createShareWithFile();
$share->update(['max_downloads' => 1]);
$share->load('files');
$encryptionService = app(FileEncryptionService::class);
$key = $share->encryption_key;
$content = 'last download content';
foreach ($share->files as $file) {
$dir = Storage::disk('shares')->path($share->token);
if (! is_dir($dir)) {
mkdir($dir, 0755, true);
}
$encryptedPath = $dir.'/'.basename($file->stored_path);
$tempSource = tempnam(sys_get_temp_dir(), 'test');
file_put_contents($tempSource, $content);
$encryptionService->encryptFile($tempSource, $encryptedPath, $key);
unlink($tempSource);
$file->update(['file_size' => strlen($content)]);
}
$response = $this->get(route('share.download.all', $share));
$response->assertDownload();
// Share was deleted after download
expect(Share::query()->find($share->id))->toBeNull();
});
test('share auto-deletes after reaching download limit', function () {
Storage::fake('shares');
$service = app(ShareService::class);
$file = UploadedFile::fake()->create('file.txt', 100);
$share = $service->createShare([
['file' => $file, 'relativePath' => null],
], [
'max_downloads' => 1,
]);
$service->recordDownload($share);
expect(Share::query()->find($share->id))->toBeNull();
});
/**
* Helper to create a share with an actual encrypted file.
*/
function createShareWithFile(?string $password = null): Share
{
$service = app(ShareService::class);
$file = UploadedFile::fake()->create('testfile.txt', 100);
return $service->createShare([
['file' => $file, 'relativePath' => null],
], [
'password' => $password,
]);
}