285 lines
8.5 KiB
PHP
285 lines
8.5 KiB
PHP
<?php
|
|
|
|
use App\Models\Attachment;
|
|
use App\Models\AttachmentExtension;
|
|
use App\Models\AttachmentGroup;
|
|
use App\Models\Forum;
|
|
use App\Models\Post;
|
|
use App\Models\Thread;
|
|
use App\Models\User;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Laravel\Sanctum\Sanctum;
|
|
|
|
function makeThreadForAttachments(?User $owner = null): Thread
|
|
{
|
|
$category = Forum::create([
|
|
'name' => 'Category',
|
|
'description' => null,
|
|
'type' => 'category',
|
|
'parent_id' => null,
|
|
'position' => 1,
|
|
]);
|
|
|
|
$forum = Forum::create([
|
|
'name' => 'Forum',
|
|
'description' => null,
|
|
'type' => 'forum',
|
|
'parent_id' => $category->id,
|
|
'position' => 1,
|
|
]);
|
|
|
|
return Thread::create([
|
|
'forum_id' => $forum->id,
|
|
'user_id' => $owner?->id,
|
|
'title' => 'Attachment Thread',
|
|
'body' => 'Thread Body',
|
|
]);
|
|
}
|
|
|
|
function makeAttachmentConfig(string $extension = 'pdf', array $mimes = ['application/pdf']): AttachmentExtension
|
|
{
|
|
$group = AttachmentGroup::create([
|
|
'name' => 'Docs',
|
|
'max_size_kb' => 25600,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
return AttachmentExtension::create([
|
|
'extension' => $extension,
|
|
'attachment_group_id' => $group->id,
|
|
'allowed_mimes' => $mimes,
|
|
]);
|
|
}
|
|
|
|
it('requires authentication to upload attachments', function (): void {
|
|
Storage::fake('local');
|
|
makeAttachmentConfig();
|
|
|
|
$response = $this->postJson('/api/attachments', [
|
|
'thread' => '/api/threads/1',
|
|
'file' => UploadedFile::fake()->create('doc.pdf', 10, 'application/pdf'),
|
|
]);
|
|
|
|
$response->assertStatus(401);
|
|
});
|
|
|
|
it('rejects uploads without thread or post', function (): void {
|
|
Storage::fake('local');
|
|
makeAttachmentConfig();
|
|
$user = User::factory()->create();
|
|
Sanctum::actingAs($user);
|
|
|
|
$response = $this->postJson('/api/attachments', [
|
|
'file' => UploadedFile::fake()->create('doc.pdf', 10, 'application/pdf'),
|
|
]);
|
|
|
|
$response->assertStatus(422);
|
|
$response->assertJsonFragment(['message' => 'Provide either thread or post.']);
|
|
});
|
|
|
|
it('rejects uploads with both thread and post', function (): void {
|
|
Storage::fake('local');
|
|
makeAttachmentConfig();
|
|
$user = User::factory()->create();
|
|
$thread = makeThreadForAttachments($user);
|
|
$post = Post::create([
|
|
'thread_id' => $thread->id,
|
|
'user_id' => $user->id,
|
|
'body' => 'Post',
|
|
]);
|
|
|
|
Sanctum::actingAs($user);
|
|
$response = $this->postJson('/api/attachments', [
|
|
'thread' => "/api/threads/{$thread->id}",
|
|
'post' => "/api/posts/{$post->id}",
|
|
'file' => UploadedFile::fake()->create('doc.pdf', 10, 'application/pdf'),
|
|
]);
|
|
|
|
$response->assertStatus(422);
|
|
$response->assertJsonFragment(['message' => 'Provide either thread or post.']);
|
|
});
|
|
|
|
it('forbids uploads when user is not owner', function (): void {
|
|
Storage::fake('local');
|
|
makeAttachmentConfig();
|
|
|
|
$owner = User::factory()->create();
|
|
$other = User::factory()->create();
|
|
$thread = makeThreadForAttachments($owner);
|
|
|
|
Sanctum::actingAs($other);
|
|
$response = $this->postJson('/api/attachments', [
|
|
'thread' => "/api/threads/{$thread->id}",
|
|
'file' => UploadedFile::fake()->create('doc.pdf', 10, 'application/pdf'),
|
|
]);
|
|
|
|
$response->assertStatus(403);
|
|
});
|
|
|
|
it('stores attachment for a thread', function (): void {
|
|
Storage::fake('local');
|
|
makeAttachmentConfig();
|
|
|
|
$user = User::factory()->create();
|
|
$thread = makeThreadForAttachments($user);
|
|
|
|
Sanctum::actingAs($user);
|
|
$response = $this->postJson('/api/attachments', [
|
|
'thread' => "/api/threads/{$thread->id}",
|
|
'file' => UploadedFile::fake()->create('doc.pdf', 10, 'application/pdf'),
|
|
]);
|
|
|
|
$response->assertStatus(201);
|
|
$attachmentId = $response->json('id');
|
|
$attachment = Attachment::findOrFail($attachmentId);
|
|
|
|
$this->assertDatabaseHas('attachments', [
|
|
'id' => $attachment->id,
|
|
'thread_id' => $thread->id,
|
|
'user_id' => $user->id,
|
|
'extension' => 'pdf',
|
|
]);
|
|
|
|
Storage::disk('local')->assertExists($attachment->path);
|
|
});
|
|
|
|
it('filters attachments by thread', function (): void {
|
|
Storage::fake('local');
|
|
makeAttachmentConfig();
|
|
|
|
$user = User::factory()->create();
|
|
$threadA = makeThreadForAttachments($user);
|
|
$threadB = makeThreadForAttachments($user);
|
|
|
|
Sanctum::actingAs($user);
|
|
|
|
$response = $this->postJson('/api/attachments', [
|
|
'thread' => "/api/threads/{$threadA->id}",
|
|
'file' => UploadedFile::fake()->create('doc.pdf', 10, 'application/pdf'),
|
|
]);
|
|
$response->assertStatus(201);
|
|
$attachmentId = $response->json('id');
|
|
|
|
$this->postJson('/api/attachments', [
|
|
'thread' => "/api/threads/{$threadB->id}",
|
|
'file' => UploadedFile::fake()->create('doc.pdf', 10, 'application/pdf'),
|
|
])->assertStatus(201);
|
|
|
|
$response = $this->getJson("/api/attachments?thread=/api/threads/{$threadA->id}");
|
|
|
|
$response->assertOk();
|
|
$response->assertJsonCount(1);
|
|
$response->assertJsonFragment(['id' => $attachmentId]);
|
|
});
|
|
|
|
it('returns 404 when parent thread is deleted', function (): void {
|
|
Storage::fake('local');
|
|
makeAttachmentConfig();
|
|
|
|
$user = User::factory()->create();
|
|
$thread = makeThreadForAttachments($user);
|
|
|
|
Sanctum::actingAs($user);
|
|
$response = $this->postJson('/api/attachments', [
|
|
'thread' => "/api/threads/{$thread->id}",
|
|
'file' => UploadedFile::fake()->create('doc.pdf', 10, 'application/pdf'),
|
|
]);
|
|
|
|
$response->assertStatus(201);
|
|
$attachmentId = $response->json('id');
|
|
|
|
$thread->delete();
|
|
|
|
$this->getJson("/api/attachments/{$attachmentId}")
|
|
->assertStatus(404);
|
|
});
|
|
|
|
it('downloads attachment file when available', function (): void {
|
|
Storage::fake('local');
|
|
makeAttachmentConfig();
|
|
|
|
$user = User::factory()->create();
|
|
$thread = makeThreadForAttachments($user);
|
|
|
|
$attachment = Attachment::create([
|
|
'thread_id' => $thread->id,
|
|
'post_id' => null,
|
|
'attachment_extension_id' => null,
|
|
'attachment_group_id' => null,
|
|
'user_id' => $user->id,
|
|
'disk' => 'local',
|
|
'path' => 'attachments/threads/'.$thread->id.'/file.pdf',
|
|
'original_name' => 'file.pdf',
|
|
'extension' => 'pdf',
|
|
'mime_type' => 'application/pdf',
|
|
'size_bytes' => 4,
|
|
]);
|
|
|
|
Storage::disk('local')->put($attachment->path, 'data');
|
|
|
|
$response = $this->get("/api/attachments/{$attachment->id}/download");
|
|
$response->assertOk();
|
|
$response->assertHeader('content-type', 'application/pdf');
|
|
});
|
|
|
|
it('serves attachment thumbnail when present', function (): void {
|
|
Storage::fake('local');
|
|
makeAttachmentConfig();
|
|
|
|
$user = User::factory()->create();
|
|
$thread = makeThreadForAttachments($user);
|
|
|
|
$attachment = Attachment::create([
|
|
'thread_id' => $thread->id,
|
|
'post_id' => null,
|
|
'attachment_extension_id' => null,
|
|
'attachment_group_id' => null,
|
|
'user_id' => $user->id,
|
|
'disk' => 'local',
|
|
'path' => 'attachments/threads/'.$thread->id.'/file.pdf',
|
|
'thumbnail_path' => 'attachments/threads/'.$thread->id.'/thumbs/thumb.jpg',
|
|
'thumbnail_mime_type' => 'image/jpeg',
|
|
'thumbnail_size_bytes' => 4,
|
|
'original_name' => 'file.pdf',
|
|
'extension' => 'pdf',
|
|
'mime_type' => 'application/pdf',
|
|
'size_bytes' => 4,
|
|
]);
|
|
|
|
Storage::disk('local')->put($attachment->path, 'data');
|
|
Storage::disk('local')->put($attachment->thumbnail_path, 'thumb');
|
|
|
|
$response = $this->get("/api/attachments/{$attachment->id}/thumbnail");
|
|
$response->assertOk();
|
|
$response->assertHeader('content-type', 'image/jpeg');
|
|
});
|
|
|
|
it('soft deletes attachments when owner requests', function (): void {
|
|
Storage::fake('local');
|
|
makeAttachmentConfig();
|
|
|
|
$user = User::factory()->create();
|
|
$thread = makeThreadForAttachments($user);
|
|
|
|
$attachment = Attachment::create([
|
|
'thread_id' => $thread->id,
|
|
'post_id' => null,
|
|
'attachment_extension_id' => null,
|
|
'attachment_group_id' => null,
|
|
'user_id' => $user->id,
|
|
'disk' => 'local',
|
|
'path' => 'attachments/threads/'.$thread->id.'/file.pdf',
|
|
'original_name' => 'file.pdf',
|
|
'extension' => 'pdf',
|
|
'mime_type' => 'application/pdf',
|
|
'size_bytes' => 4,
|
|
]);
|
|
|
|
Sanctum::actingAs($user);
|
|
$response = $this->deleteJson("/api/attachments/{$attachment->id}");
|
|
|
|
$response->assertStatus(204);
|
|
$this->assertSoftDeleted('attachments', ['id' => $attachment->id]);
|
|
});
|