create(['role' => 'provider']); } private function makeOffer(User $provider): DivingOffer { return DivingOffer::create([ 'provider_id' => $provider->id, 'title' => 'Test Course', 'location' => 'Test', 'spot' => 'Test Spot', 'price' => 1000, 'region' => '南部', 'rating' => 0, 'reviews' => 0, ]); } private function fakeImage(string $name = 'test.png'): UploadedFile { return UploadedFile::fake()->image($name, 100, 100)->size(500); } public function test_upload_cover_success(): void { Storage::fake('public'); $provider = $this->makeProvider(); $offer = $this->makeOffer($provider); $this->actingAs($provider) ->postJson("/api/provider/offers/{$offer->id}/cover", ['image' => $this->fakeImage()]) ->assertOk()->assertJsonPath('status', true); $offer->refresh(); $this->assertNotNull($offer->cover_image); Storage::disk('public')->assertExists($offer->cover_image); } public function test_upload_cover_wrong_mime(): void { Storage::fake('public'); $provider = $this->makeProvider(); $offer = $this->makeOffer($provider); $this->actingAs($provider) ->postJson("/api/provider/offers/{$offer->id}/cover", [ 'image' => UploadedFile::fake()->create('doc.pdf', 100, 'application/pdf'), ])->assertStatus(422); } public function test_upload_cover_too_large(): void { Storage::fake('public'); $provider = $this->makeProvider(); $offer = $this->makeOffer($provider); $this->actingAs($provider) ->postJson("/api/provider/offers/{$offer->id}/cover", [ 'image' => UploadedFile::fake()->image('big.png')->size(3000), ])->assertStatus(422); } public function test_upload_cover_forbidden_for_other_provider(): void { Storage::fake('public'); $offer = $this->makeOffer($this->makeProvider()); $this->actingAs($this->makeProvider()) ->postJson("/api/provider/offers/{$offer->id}/cover", ['image' => $this->fakeImage()]) ->assertStatus(403); } public function test_delete_cover_removes_file(): void { Storage::fake('public'); $provider = $this->makeProvider(); $offer = $this->makeOffer($provider); $this->actingAs($provider) ->postJson("/api/provider/offers/{$offer->id}/cover", ['image' => $this->fakeImage()]); $offer->refresh(); $oldPath = $offer->cover_image; $this->actingAs($provider) ->deleteJson("/api/provider/offers/{$offer->id}/cover") ->assertOk(); Storage::disk('public')->assertMissing($oldPath); $this->assertNull($offer->fresh()->cover_image); } public function test_delete_cover_when_no_cover_is_ok(): void { Storage::fake('public'); $provider = $this->makeProvider(); $offer = $this->makeOffer($provider); $this->actingAs($provider) ->deleteJson("/api/provider/offers/{$offer->id}/cover") ->assertOk(); } public function test_delete_cover_forbidden_for_other_provider(): void { Storage::fake('public'); $offer = $this->makeOffer($this->makeProvider()); $this->actingAs($this->makeProvider()) ->deleteJson("/api/provider/offers/{$offer->id}/cover") ->assertStatus(403); } public function test_upload_gallery_image_success(): void { Storage::fake('public'); $provider = $this->makeProvider(); $offer = $this->makeOffer($provider); $this->actingAs($provider) ->postJson("/api/provider/offers/{$offer->id}/images", ['image' => $this->fakeImage()]) ->assertStatus(201); $this->assertDatabaseCount('course_images', 1); } public function test_gallery_max_3_images(): void { Storage::fake('public'); $provider = $this->makeProvider(); $offer = $this->makeOffer($provider); for ($i = 0; $i < 3; $i++) { $this->actingAs($provider) ->postJson("/api/provider/offers/{$offer->id}/images", ['image' => $this->fakeImage()]); } $this->actingAs($provider) ->postJson("/api/provider/offers/{$offer->id}/images", ['image' => $this->fakeImage()]) ->assertStatus(422); } public function test_gallery_sort_order_increments(): void { Storage::fake('public'); $provider = $this->makeProvider(); $offer = $this->makeOffer($provider); $r1 = $this->actingAs($provider)->postJson("/api/provider/offers/{$offer->id}/images", ['image' => $this->fakeImage()]); $r2 = $this->actingAs($provider)->postJson("/api/provider/offers/{$offer->id}/images", ['image' => $this->fakeImage()]); $this->assertEquals(1, $r1->json('data.sort_order')); $this->assertEquals(2, $r2->json('data.sort_order')); } public function test_upload_image_forbidden_for_other_provider(): void { Storage::fake('public'); $offer = $this->makeOffer($this->makeProvider()); $this->actingAs($this->makeProvider()) ->postJson("/api/provider/offers/{$offer->id}/images", ['image' => $this->fakeImage()]) ->assertStatus(403); } public function test_delete_gallery_image_removes_file(): void { Storage::fake('public'); $provider = $this->makeProvider(); $offer = $this->makeOffer($provider); $res = $this->actingAs($provider)->postJson("/api/provider/offers/{$offer->id}/images", ['image' => $this->fakeImage()]); $imgId = $res->json('data.id'); $path = CourseImage::find($imgId)->image_path; $this->actingAs($provider)->deleteJson("/api/provider/images/{$imgId}")->assertOk(); $this->assertDatabaseMissing('course_images', ['id' => $imgId]); Storage::disk('public')->assertMissing($path); } public function test_delete_image_forbidden_for_other_provider(): void { Storage::fake('public'); $provider = $this->makeProvider(); $offer = $this->makeOffer($provider); $res = $this->actingAs($provider)->postJson("/api/provider/offers/{$offer->id}/images", ['image' => $this->fakeImage()]); $imgId = $res->json('data.id'); $this->actingAs($this->makeProvider()) ->deleteJson("/api/provider/images/{$imgId}") ->assertStatus(403); } public function test_deleting_offer_removes_storage_directory(): void { Storage::fake('public'); $provider = $this->makeProvider(); $offer = $this->makeOffer($provider); $this->actingAs($provider)->postJson("/api/provider/offers/{$offer->id}/cover", ['image' => $this->fakeImage()]); $this->actingAs($provider)->postJson("/api/provider/offers/{$offer->id}/images", ['image' => $this->fakeImage()]); $offerId = $offer->id; $offer->delete(); Storage::disk('public')->assertDirectoryEmpty("offers/{$offerId}"); } }