users and institutions route, tests fixed

This commit is contained in:
Giuseppe Naponiello
2026-06-23 12:29:02 +02:00
parent fe73662903
commit 032f6a08df
23 changed files with 346 additions and 287 deletions

View File

@@ -14,6 +14,16 @@ class InstitutionControllerTest extends TestCase
{
use RefreshDatabase;
private const BASE_ROUTE = '/api/institutions';
private const TEST_MUSEUM = 'Test Museum';
private const JSON_HEADERS = ['Accept' => 'application/json'];
private const OLD_LOGO_PATH = 'institution_logo/old.jpg';
private const GONE_LOGO_PATH = 'institution_logo/gone.jpg';
/**
* @param array<string, mixed> $overrides
* @return array<string, mixed>
@@ -22,7 +32,7 @@ class InstitutionControllerTest extends TestCase
{
return array_merge([
'category_id' => InstitutionCategory::factory()->create()->id,
'name' => 'Test Museum',
'name' => self::TEST_MUSEUM,
'abbreviation' => 'TM',
'address' => 'Some Road 1',
'city' => 'Lund',
@@ -41,7 +51,7 @@ class InstitutionControllerTest extends TestCase
$this->actingAs($this->operationalUser(), 'sanctum');
Institution::factory()->count(2)->create();
$this->getJson('/api/institutions')
$this->getJson(self::BASE_ROUTE)
->assertOk()
->assertJsonStructure(['message', 'data', 'meta' => ['current_page', 'total']])
->assertJsonCount(2, 'data');
@@ -54,12 +64,12 @@ class InstitutionControllerTest extends TestCase
Institution::factory()->create(['category_id' => $category->id, 'name' => 'Blekinge Museum']);
Institution::factory()->create(['name' => 'Other Place']);
$this->getJson("/api/institutions?category_id={$category->id}")
$this->getJson(self::BASE_ROUTE."?category_id={$category->id}")
->assertOk()
->assertJsonCount(1, 'data')
->assertJsonPath('data.0.name', 'Blekinge Museum');
$this->getJson('/api/institutions?search=Blekinge')
$this->getJson(self::BASE_ROUTE.'?search=Blekinge')
->assertOk()
->assertJsonCount(1, 'data');
}
@@ -71,12 +81,12 @@ class InstitutionControllerTest extends TestCase
Institution::factory()->create();
$trashed->delete();
$this->getJson('/api/institutions')->assertJsonCount(1, 'data');
$this->getJson('/api/institutions?trashed=only')
$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('/api/institutions?trashed=with')->assertJsonCount(2, 'data');
$this->getJson(self::BASE_ROUTE.'?trashed=with')->assertJsonCount(2, 'data');
}
public function test_show_returns_institution_with_category_and_links(): void
@@ -85,7 +95,7 @@ class InstitutionControllerTest extends TestCase
$institution = Institution::factory()->create();
InstitutionLink::factory()->create(['institution_id' => $institution->id]);
$this->getJson("/api/institutions/{$institution->uuid}")
$this->getJson(self::BASE_ROUTE."/{$institution->uuid}")
->assertOk()
->assertJsonPath('data.uuid', $institution->uuid)
->assertJsonStructure(['data' => ['category', 'links']]);
@@ -97,7 +107,7 @@ class InstitutionControllerTest extends TestCase
$institution = Institution::factory()->create();
// L'id interno non è una route key valida.
$this->getJson("/api/institutions/{$institution->id}")->assertNotFound();
$this->getJson(self::BASE_ROUTE."/{$institution->id}")->assertNotFound();
}
// --- Scrittura (tier.admin) ---------------------------------------------
@@ -107,15 +117,15 @@ class InstitutionControllerTest extends TestCase
Storage::fake('public');
$this->actingAs($this->adminUser(), 'sanctum');
$this->post('/api/institutions', $this->validPayload(), ['Accept' => 'application/json'])
$this->post(self::BASE_ROUTE, $this->validPayload(), self::JSON_HEADERS)
->assertCreated()
->assertJsonPath('data.name', 'Test Museum')
->assertJsonPath('data.name', self::TEST_MUSEUM)
->assertJsonStructure(['data' => ['uuid', 'category']]);
$institution = Institution::firstWhere('name', 'Test Museum');
$institution = Institution::firstWhere('name', self::TEST_MUSEUM);
$this->assertNotNull($institution);
$this->assertNotEmpty($institution->uuid);
Storage::disk('public')->assertExists($institution->logo);
$this->assertTrue(Storage::disk('public')->exists($institution->logo));
}
public function test_create_applies_default_color_and_storage_flag(): void
@@ -126,10 +136,10 @@ class InstitutionControllerTest extends TestCase
$payload = $this->validPayload();
unset($payload['color'], $payload['is_storage_place']);
$this->post('/api/institutions', $payload, ['Accept' => 'application/json'])->assertCreated();
$this->post(self::BASE_ROUTE, $payload, self::JSON_HEADERS)->assertCreated();
$this->assertDatabaseHas('institutions', [
'name' => 'Test Museum',
'name' => self::TEST_MUSEUM,
'color' => '#c5cae9',
'is_storage_place' => true,
]);
@@ -139,7 +149,7 @@ class InstitutionControllerTest extends TestCase
{
$this->actingAs($this->adminUser(), 'sanctum');
$this->postJson('/api/institutions', [])
$this->postJson(self::BASE_ROUTE, [])
->assertStatus(422)
->assertJsonValidationErrors(['category_id', 'name', 'abbreviation', 'lat', 'lon', 'logo']);
}
@@ -152,7 +162,7 @@ class InstitutionControllerTest extends TestCase
$payload = $this->validPayload(['name' => 'New Name', 'category_id' => $institution->category_id]);
unset($payload['logo']);
$this->put("/api/institutions/{$institution->uuid}", $payload, ['Accept' => 'application/json'])
$this->put(self::BASE_ROUTE."/{$institution->uuid}", $payload, self::JSON_HEADERS)
->assertOk()
->assertJsonPath('data.name', 'New Name');
@@ -163,15 +173,15 @@ class InstitutionControllerTest extends TestCase
{
Storage::fake('public');
$this->actingAs($this->adminUser(), 'sanctum');
Storage::disk('public')->put('institution_logo/old.jpg', 'x');
$institution = Institution::factory()->create(['logo' => 'institution_logo/old.jpg']);
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("/api/institutions/{$institution->uuid}", $payload, ['Accept' => 'application/json'])->assertOk();
$this->put(self::BASE_ROUTE."/{$institution->uuid}", $payload, self::JSON_HEADERS)->assertOk();
Storage::disk('public')->assertMissing('institution_logo/old.jpg');
Storage::disk('public')->assertExists($institution->fresh()->logo);
$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
@@ -179,7 +189,7 @@ class InstitutionControllerTest extends TestCase
$this->actingAs($this->adminUser(), 'sanctum');
$institution = Institution::factory()->create();
$this->deleteJson("/api/institutions/{$institution->uuid}")->assertOk();
$this->deleteJson(self::BASE_ROUTE."/{$institution->uuid}")->assertOk();
$this->assertSoftDeleted($institution);
}
@@ -190,7 +200,7 @@ class InstitutionControllerTest extends TestCase
$institution = Institution::factory()->create();
$institution->delete();
$this->postJson("/api/institutions/{$institution->uuid}/restore")
$this->postJson(self::BASE_ROUTE."/{$institution->uuid}/restore")
->assertOk()
->assertJsonPath('data.uuid', $institution->uuid);
@@ -201,16 +211,16 @@ class InstitutionControllerTest extends TestCase
{
Storage::fake('public');
$this->actingAs($this->adminUser(), 'sanctum');
Storage::disk('public')->put('institution_logo/gone.jpg', 'x');
$institution = Institution::factory()->create(['logo' => 'institution_logo/gone.jpg']);
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("/api/institutions/{$institution->uuid}/force")->assertOk();
$this->deleteJson(self::BASE_ROUTE."/{$institution->uuid}/force")->assertOk();
$this->assertDatabaseMissing('institutions', ['id' => $institution->id]);
$this->assertDatabaseMissing('institution_links', ['id' => $link->id]);
Storage::disk('public')->assertMissing('institution_logo/gone.jpg');
Storage::assertMissing(self::GONE_LOGO_PATH);
}
// --- Autorizzazione ------------------------------------------------------
@@ -220,12 +230,22 @@ class InstitutionControllerTest extends TestCase
$this->actingAs($this->operationalUser(), 'sanctum');
$institution = Institution::factory()->create();
$this->postJson('/api/institutions', [])->assertForbidden();
$this->deleteJson("/api/institutions/{$institution->uuid}")->assertForbidden();
$this->postJson(self::BASE_ROUTE, [])->assertForbidden();
$this->deleteJson(self::BASE_ROUTE."/{$institution->uuid}")->assertForbidden();
}
public function test_guests_cannot_access_institutions(): void
public function test_guests_can_read_institutions(): void
{
$this->getJson('/api/institutions')->assertUnauthorized();
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();
}
}

View File

@@ -140,10 +140,18 @@ class InstitutionLinkControllerTest extends TestCase
$this->postJson("/api/institutions/{$institution->uuid}/links", $this->validPayload())->assertForbidden();
}
public function test_guests_cannot_access_links(): void
public function test_guests_can_read_links(): void
{
$institution = Institution::factory()->create();
InstitutionLink::factory()->create(['institution_id' => $institution->id]);
$this->getJson("/api/institutions/{$institution->uuid}/links")->assertOk();
}
public function test_guests_cannot_write_links(): void
{
$institution = Institution::factory()->create();
$this->getJson("/api/institutions/{$institution->uuid}/links")->assertUnauthorized();
$this->postJson("/api/institutions/{$institution->uuid}/links", $this->validPayload())->assertUnauthorized();
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Tests\Feature;
use App\Models\Lists\UserPosition;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UserPositionControllerTest extends TestCase
{
use RefreshDatabase;
// --- Lettura (pubblica, throttle:public) ----------------------------------
public function test_index_lists_positions_for_an_operational_user(): void
{
$this->actingAs($this->operationalUser(), 'sanctum');
UserPosition::factory()->count(2)->create();
$this->getJson('/api/user-positions')
->assertOk()
->assertJsonStructure(['message', 'data' => [['id', 'value']]]);
}
public function test_show_returns_a_single_position(): void
{
$this->actingAs($this->operationalUser(), 'sanctum');
$position = UserPosition::factory()->create();
$this->getJson("/api/user-positions/{$position->id}")
->assertOk()
->assertJsonPath('data.id', $position->id)
->assertJsonPath('data.value', $position->value);
}
// --- Gating ---------------------------------------------------------------
public function test_guests_can_read_positions(): void
{
UserPosition::factory()->create();
$this->getJson('/api/user-positions')->assertOk();
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Tests\Feature;
use App\Models\Lists\UserPosition;
use Database\Seeders\UserPositionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UserPositionSeederTest extends TestCase
{
use RefreshDatabase;
public function test_it_seeds_the_base_positions(): void
{
$this->seed(UserPositionSeeder::class);
$this->assertSame(5, UserPosition::count());
foreach ([
UserPosition::PROFESSOR,
UserPosition::RESEARCHER,
UserPosition::PHD,
UserPosition::STUDENT,
UserPosition::ADMINISTRATIVE,
] as $value) {
$this->assertDatabaseHas('user_positions', ['value' => $value]);
}
}
public function test_it_is_idempotent(): void
{
$this->seed(UserPositionSeeder::class);
$this->seed(UserPositionSeeder::class);
$this->assertSame(5, UserPosition::count());
}
}

View File

@@ -33,131 +33,32 @@ class UserRoleControllerTest extends TestCase
->assertJsonPath('data.name', $role->name);
}
public function test_usage_flags_system_role_as_locked(): void
// --- Statistiche (tier.admin) ---------------------------------------------
public function test_admin_can_see_the_user_count_of_a_role(): void
{
$this->actingAs($this->operationalUser(), 'sanctum');
$role = UserRole::create(['name' => UserRole::SUPERVISOR]);
$this->actingAs($this->adminUser(), 'sanctum');
$role = UserRole::factory()->create();
$this->operationalUser(['role_id' => $role->id]);
$this->operationalUser(['role_id' => $role->id]);
$this->getJson("/api/user-roles/{$role->id}/usage")
->assertOk()
->assertJsonPath('in_use', true);
->assertJsonPath('user_count', 2);
}
public function test_usage_flags_free_custom_role_as_unlocked(): void
public function test_a_non_admin_cannot_see_usage_stats(): void
{
$this->actingAs($this->operationalUser(), 'sanctum');
$role = UserRole::factory()->create();
$this->getJson("/api/user-roles/{$role->id}/usage")
->assertOk()
->assertJsonPath('in_use', false);
}
// --- Scrittura (tier.admin) ---------------------------------------------
public function test_admin_can_create_a_role(): void
{
$this->actingAs($this->adminUser(), 'sanctum');
$this->postJson('/api/user-roles', ['name' => 'Editor', 'description' => 'Edits stuff'])
->assertCreated()
->assertJsonPath('data.name', 'Editor');
$this->assertDatabaseHas('user_roles', ['name' => 'Editor']);
}
public function test_create_requires_a_name(): void
{
$this->actingAs($this->adminUser(), 'sanctum');
$this->postJson('/api/user-roles', ['description' => 'no name'])
->assertUnprocessable()
->assertJsonValidationErrors('name');
}
public function test_create_rejects_a_duplicate_name(): void
{
$this->actingAs($this->adminUser(), 'sanctum');
UserRole::factory()->create(['name' => 'Editor']);
$this->postJson('/api/user-roles', ['name' => 'Editor'])
->assertUnprocessable()
->assertJsonValidationErrors('name');
}
public function test_admin_can_update_a_role(): void
{
$this->actingAs($this->adminUser(), 'sanctum');
$role = UserRole::factory()->create(['name' => 'Old']);
$this->putJson("/api/user-roles/{$role->id}", ['name' => 'New'])
->assertOk()
->assertJsonPath('data.name', 'New');
$this->assertDatabaseHas('user_roles', ['id' => $role->id, 'name' => 'New']);
}
public function test_update_can_keep_the_same_name(): void
{
$this->actingAs($this->adminUser(), 'sanctum');
$role = UserRole::factory()->create(['name' => 'Stable']);
$this->putJson("/api/user-roles/{$role->id}", ['name' => 'Stable'])
->assertOk();
}
public function test_update_rejects_a_name_taken_by_another_role(): void
{
$this->actingAs($this->adminUser(), 'sanctum');
UserRole::factory()->create(['name' => 'Taken']);
$role = UserRole::factory()->create(['name' => 'Mine']);
$this->putJson("/api/user-roles/{$role->id}", ['name' => 'Taken'])
->assertUnprocessable()
->assertJsonValidationErrors('name');
}
public function test_cannot_delete_a_system_role(): void
{
$this->actingAs($this->adminUser(), 'sanctum');
$role = UserRole::create(['name' => UserRole::GUEST]);
$this->deleteJson("/api/user-roles/{$role->id}")
->assertStatus(409)
->assertJsonPath('in_use', true);
$this->assertDatabaseHas('user_roles', ['id' => $role->id]);
}
public function test_cannot_delete_a_role_in_use(): void
{
$this->actingAs($this->adminUser(), 'sanctum');
$role = UserRole::factory()->create(['name' => 'Busy']);
$this->operationalUser(['role_id' => $role->id]);
$this->deleteJson("/api/user-roles/{$role->id}")
->assertStatus(409)
->assertJsonPath('in_use', true);
$this->assertDatabaseHas('user_roles', ['id' => $role->id]);
}
public function test_admin_can_delete_a_free_custom_role(): void
{
$this->actingAs($this->adminUser(), 'sanctum');
$role = UserRole::factory()->create(['name' => 'Disposable']);
$this->deleteJson("/api/user-roles/{$role->id}")->assertOk();
$this->assertDatabaseMissing('user_roles', ['id' => $role->id]);
$this->getJson("/api/user-roles/{$role->id}/usage")->assertForbidden();
}
// --- Gating ---------------------------------------------------------------
public function test_non_admin_cannot_write(): void
public function test_guests_cannot_access_roles(): void
{
$this->actingAs($this->operationalUser(), 'sanctum');
$this->postJson('/api/user-roles', ['name' => 'Nope'])->assertForbidden();
$this->getJson('/api/user-roles')->assertUnauthorized();
}
}