create([ 'codigo' => 'acme', 'nombre' => 'Acme', 'dominio' => 'acme.com', ]); $file = UploadedFile::fake()->image('logo.png'); $attachment = app(AttachmentService::class)->store( $file, 'attachments/acme/logo.png', ); $tenant->attachments()->attach($attachment->getKey()); $this->assertSame(AttachmentType::Image, $attachment->type); $this->assertTrue(Str::isUuid($attachment->key)); $this->assertSame('logo.png', $attachment->filename); $this->assertSame('attachments/acme/'.$attachment->key, $attachment->path); Storage::disk('s3')->assertExists('attachments/acme/'.$attachment->key); $this->assertDatabaseHas('attachments', [ 'id' => $attachment->id, 'key' => $attachment->key, 'path' => 'attachments/acme/'.$attachment->key, 'filename' => 'logo.png', 'type' => AttachmentType::Image->value, ]); $this->assertDatabaseHas('attachable_attachments', [ 'attachable_type' => $tenant->getMorphClass(), 'attachable_id' => $tenant->getKey(), 'attachment_id' => $attachment->id, ]); $freshAttachment = Attachment::query()->findOrFail($attachment->id); $this->assertSame(AttachmentType::Image, $freshAttachment->type); $this->assertTrue($tenant->attachments->contains($freshAttachment)); } public function test_it_does_not_persist_the_attachment_when_the_s3_upload_fails(): void { $tenant = Tenant::query()->create([ 'codigo' => 'globex', 'nombre' => 'Globex', 'dominio' => 'globex.com', ]); $disk = Mockery::mock(); Storage::shouldReceive('disk') ->once() ->with('s3') ->andReturn($disk); $disk->shouldReceive('putFileAs') ->once() ->with('attachments/globex', Mockery::type(UploadedFile::class), Mockery::on(static fn (string $value): bool => Str::isUuid($value))) ->andReturn(false); try { app(AttachmentService::class)->store( UploadedFile::fake()->create('manual.pdf', 10, 'application/pdf'), 'attachments/globex/manual.pdf', ); $this->fail('Expected an AttachmentStorageException to be thrown.'); } catch (AttachmentStorageException) { $this->assertDatabaseCount('attachments', 0); } } public function test_it_deletes_from_s3_before_removing_the_database_record(): void { Storage::fake('s3'); $tenant = Tenant::query()->create([ 'codigo' => 'initech', 'nombre' => 'Initech', 'dominio' => 'initech.com', ]); Storage::disk('s3')->put('attachments/initech/spec.pdf', 'spec'); $attachment = Attachment::query()->create([ 'path' => 'attachments/initech/spec.pdf', 'key' => (string) Str::uuid(), 'filename' => 'spec.pdf', 'type' => AttachmentType::Pdf, 'mime_type' => 'application/pdf', 'extension' => 'pdf', 'size' => 512, ]); $tenant->attachments()->attach($attachment->getKey()); app(AttachmentService::class)->delete($attachment); Storage::disk('s3')->assertMissing('attachments/initech/spec.pdf'); $this->assertDatabaseMissing('attachments', [ 'id' => $attachment->id, ]); $this->assertDatabaseMissing('attachable_attachments', [ 'attachment_id' => $attachment->id, ]); } public function test_it_keeps_the_database_record_when_the_s3_delete_fails(): void { $tenant = Tenant::query()->create([ 'codigo' => 'umbrella', 'nombre' => 'Umbrella', 'dominio' => 'umbrella.com', ]); $attachment = Attachment::query()->create([ 'path' => 'attachments/umbrella/audio.mp3', 'key' => (string) Str::uuid(), 'filename' => 'audio.mp3', 'type' => AttachmentType::Audio, 'mime_type' => 'audio/mpeg', 'extension' => 'mp3', 'size' => 1024, ]); $tenant->attachments()->attach($attachment->getKey()); $disk = Mockery::mock(); Storage::shouldReceive('disk') ->once() ->with('s3') ->andReturn($disk); $disk->shouldReceive('delete') ->once() ->with('attachments/umbrella/audio.mp3') ->andReturn(false); try { app(AttachmentService::class)->delete($attachment); $this->fail('Expected an AttachmentStorageException to be thrown.'); } catch (AttachmentStorageException) { $this->assertDatabaseHas('attachments', [ 'id' => $attachment->id, ]); $this->assertDatabaseHas('attachable_attachments', [ 'attachment_id' => $attachment->id, 'attachable_type' => $tenant->getMorphClass(), 'attachable_id' => $tenant->getKey(), ]); } } }