'application/json']; private const OLD_LOGO_PATH = 'institution_logo/old.jpg'; private const GONE_LOGO_PATH = 'institution_logo/gone.jpg'; /** * @param array $overrides * @return array */ private function validPayload(array $overrides = []): array { return array_merge([ 'category_id' => InstitutionCategory::factory()->create()->id, 'name' => self::TEST_MUSEUM, 'abbreviation' => 'TM', 'address' => 'Some Road 1', 'city' => 'Lund', 'lat' => 55.7, 'lon' => 13.19, 'color' => '#ffffff', 'is_storage_place' => true, 'logo' => UploadedFile::fake()->image('logo.png'), ], $overrides); } // --- Lettura (tier.app) -------------------------------------------------- public function test_index_lists_institutions_for_an_operational_user(): void { $this->actingAs($this->operationalUser(), 'sanctum'); Institution::factory()->count(2)->create(); $this->getJson(self::BASE_ROUTE) ->assertOk() ->assertJsonStructure(['message', 'data', 'meta' => ['current_page', 'total']]) ->assertJsonCount(2, 'data'); } public function test_index_filters_by_category_and_search(): void { $this->actingAs($this->operationalUser(), 'sanctum'); $category = InstitutionCategory::factory()->create(); Institution::factory()->create(['category_id' => $category->id, 'name' => 'Blekinge Museum']); Institution::factory()->create(['name' => 'Other Place']); $this->getJson(self::BASE_ROUTE."?category_id={$category->id}") ->assertOk() ->assertJsonCount(1, 'data') ->assertJsonPath('data.0.name', 'Blekinge Museum'); $this->getJson(self::BASE_ROUTE.'?search=Blekinge') ->assertOk() ->assertJsonCount(1, 'data'); } public function test_index_can_list_only_trashed(): void { $this->actingAs($this->operationalUser(), 'sanctum'); $trashed = Institution::factory()->create(); Institution::factory()->create(); $trashed->delete(); $this->getJson(self::BASE_ROUTE)->assertJsonCount(1, 'data'); $this->getJson(self::BASE_ROUTE.'?trashed=only') ->assertOk() ->assertJsonCount(1, 'data') ->assertJsonPath('data.0.id', $trashed->id); $this->getJson(self::BASE_ROUTE.'?trashed=with')->assertJsonCount(2, 'data'); } public function test_show_returns_institution_with_category_and_links(): void { $this->actingAs($this->operationalUser(), 'sanctum'); $institution = Institution::factory()->create(); InstitutionLink::factory()->create(['institution_id' => $institution->id]); $this->getJson(self::BASE_ROUTE."/{$institution->uuid}") ->assertOk() ->assertJsonPath('data.uuid', $institution->uuid) ->assertJsonStructure(['data' => ['category', 'links']]); } public function test_show_resolves_by_uuid_not_id(): void { $this->actingAs($this->operationalUser(), 'sanctum'); $institution = Institution::factory()->create(); // L'id interno non รจ una route key valida. $this->getJson(self::BASE_ROUTE."/{$institution->id}")->assertNotFound(); } // --- Scrittura (tier.admin) --------------------------------------------- public function test_admin_can_create_an_institution_with_a_logo(): void { Storage::fake('public'); $this->actingAs($this->adminUser(), 'sanctum'); $this->post(self::BASE_ROUTE, $this->validPayload(), self::JSON_HEADERS) ->assertCreated() ->assertJsonPath('data.name', self::TEST_MUSEUM) ->assertJsonStructure(['data' => ['uuid', 'category']]); $institution = Institution::firstWhere('name', self::TEST_MUSEUM); $this->assertNotNull($institution); $this->assertNotEmpty($institution->uuid); $this->assertTrue(Storage::disk('public')->exists($institution->logo)); } public function test_create_applies_default_color_and_storage_flag(): void { Storage::fake('public'); $this->actingAs($this->adminUser(), 'sanctum'); $payload = $this->validPayload(); unset($payload['color'], $payload['is_storage_place']); $this->post(self::BASE_ROUTE, $payload, self::JSON_HEADERS)->assertCreated(); $this->assertDatabaseHas('institutions', [ 'name' => self::TEST_MUSEUM, 'color' => '#c5cae9', 'is_storage_place' => true, ]); } public function test_create_validates_required_fields(): void { $this->actingAs($this->adminUser(), 'sanctum'); $this->postJson(self::BASE_ROUTE, []) ->assertStatus(422) ->assertJsonValidationErrors(['category_id', 'name', 'abbreviation', 'lat', 'lon', 'logo']); } public function test_admin_can_update_an_institution_keeping_the_logo(): void { $this->actingAs($this->adminUser(), 'sanctum'); $institution = Institution::factory()->create(['logo' => 'institution_logo/keep.jpg', 'name' => 'Old']); $payload = $this->validPayload(['name' => 'New Name', 'category_id' => $institution->category_id]); unset($payload['logo']); $this->put(self::BASE_ROUTE."/{$institution->uuid}", $payload, self::JSON_HEADERS) ->assertOk() ->assertJsonPath('data.name', 'New Name'); $this->assertSame('institution_logo/keep.jpg', $institution->fresh()->logo); } public function test_update_replaces_the_logo_when_a_new_one_is_uploaded(): void { Storage::fake('public'); $this->actingAs($this->adminUser(), 'sanctum'); Storage::disk('public')->put(self::OLD_LOGO_PATH, 'x'); $institution = Institution::factory()->create(['logo' => self::OLD_LOGO_PATH]); $payload = $this->validPayload(['category_id' => $institution->category_id]); $this->put(self::BASE_ROUTE."/{$institution->uuid}", $payload, self::JSON_HEADERS)->assertOk(); $this->assertFalse(Storage::disk('public')->exists(self::OLD_LOGO_PATH)); $this->assertTrue(Storage::disk('public')->exists($institution->fresh()->logo)); } public function test_admin_can_soft_delete_an_institution(): void { $this->actingAs($this->adminUser(), 'sanctum'); $institution = Institution::factory()->create(); $this->deleteJson(self::BASE_ROUTE."/{$institution->uuid}")->assertOk(); $this->assertSoftDeleted($institution); } public function test_admin_can_restore_a_trashed_institution(): void { $this->actingAs($this->adminUser(), 'sanctum'); $institution = Institution::factory()->create(); $institution->delete(); $this->postJson(self::BASE_ROUTE."/{$institution->uuid}/restore") ->assertOk() ->assertJsonPath('data.uuid', $institution->uuid); $this->assertNotSoftDeleted($institution); } public function test_admin_can_force_delete_an_institution_and_its_logo_and_links(): void { Storage::fake('public'); $this->actingAs($this->adminUser(), 'sanctum'); Storage::disk('public')->put(self::GONE_LOGO_PATH, 'x'); $institution = Institution::factory()->create(['logo' => self::GONE_LOGO_PATH]); $link = InstitutionLink::factory()->create(['institution_id' => $institution->id]); $institution->delete(); $this->deleteJson(self::BASE_ROUTE."/{$institution->uuid}/force")->assertOk(); $this->assertDatabaseMissing('institutions', ['id' => $institution->id]); $this->assertDatabaseMissing('institution_links', ['id' => $link->id]); Storage::assertMissing(self::GONE_LOGO_PATH); } // --- Autorizzazione ------------------------------------------------------ public function test_a_non_admin_cannot_write(): void { $this->actingAs($this->operationalUser(), 'sanctum'); $institution = Institution::factory()->create(); $this->postJson(self::BASE_ROUTE, [])->assertForbidden(); $this->deleteJson(self::BASE_ROUTE."/{$institution->uuid}")->assertForbidden(); } public function test_guests_can_read_institutions(): void { Institution::factory()->create(); $this->getJson(self::BASE_ROUTE)->assertOk(); } public function test_guests_cannot_write_institutions(): void { $institution = Institution::factory()->create(); $this->postJson(self::BASE_ROUTE, [])->assertUnauthorized(); $this->deleteJson(self::BASE_ROUTE."/{$institution->uuid}")->assertUnauthorized(); } }