Add extensive controller and model tests
This commit is contained in:
287
tests/Feature/UserControllerTest.php
Normal file
287
tests/Feature/UserControllerTest.php
Normal file
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Rank;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
function makeAdmin(): User
|
||||
{
|
||||
$admin = User::factory()->create();
|
||||
$role = Role::firstOrCreate(['name' => 'ROLE_ADMIN'], ['color' => '#111111']);
|
||||
$admin->roles()->attach($role);
|
||||
|
||||
return $admin;
|
||||
}
|
||||
|
||||
it('requires authentication to list users', function (): void {
|
||||
$response = $this->getJson('/api/users');
|
||||
|
||||
$response->assertStatus(401);
|
||||
});
|
||||
|
||||
it('lists users with roles and group color', function (): void {
|
||||
$admin = makeAdmin();
|
||||
$role = Role::firstOrCreate(['name' => 'ROLE_MOD'], ['color' => '#ff0000']);
|
||||
$user = User::factory()->create(['name' => 'Alice']);
|
||||
$user->roles()->attach($role);
|
||||
|
||||
Sanctum::actingAs($admin);
|
||||
$response = $this->getJson('/api/users');
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonFragment([
|
||||
'id' => $user->id,
|
||||
'name' => 'Alice',
|
||||
'group_color' => '#ff0000',
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns current user profile from me endpoint', function (): void {
|
||||
$user = User::factory()->create(['name' => 'Me']);
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
$response = $this->getJson('/api/user/me');
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonFragment([
|
||||
'id' => $user->id,
|
||||
'name' => 'Me',
|
||||
'email' => $user->email,
|
||||
]);
|
||||
});
|
||||
|
||||
it('rejects unauthenticated me requests', function (): void {
|
||||
$response = $this->getJson('/api/user/me');
|
||||
|
||||
$response->assertStatus(401);
|
||||
$response->assertJsonFragment(['message' => 'Unauthenticated.']);
|
||||
});
|
||||
|
||||
it('returns user profile details', function (): void {
|
||||
$viewer = User::factory()->create();
|
||||
$target = User::factory()->create(['name' => 'ProfileUser']);
|
||||
|
||||
Sanctum::actingAs($viewer);
|
||||
$response = $this->getJson("/api/user/profile/{$target->id}");
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonFragment([
|
||||
'id' => $target->id,
|
||||
'name' => 'ProfileUser',
|
||||
]);
|
||||
});
|
||||
|
||||
it('updates user location via updateMe', function (): void {
|
||||
$user = User::factory()->create(['location' => null]);
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
$response = $this->patchJson('/api/user/me', [
|
||||
'location' => ' New York ',
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonFragment([
|
||||
'id' => $user->id,
|
||||
'location' => 'New York',
|
||||
]);
|
||||
|
||||
$user->refresh();
|
||||
expect($user->location)->toBe('New York');
|
||||
});
|
||||
|
||||
it('rejects updateMe when unauthenticated', function (): void {
|
||||
$response = $this->patchJson('/api/user/me', [
|
||||
'location' => 'Somewhere',
|
||||
]);
|
||||
|
||||
$response->assertStatus(401);
|
||||
$response->assertJsonFragment(['message' => 'Unauthenticated.']);
|
||||
});
|
||||
|
||||
it('clears location when updateMe receives blank value', function (): void {
|
||||
$user = User::factory()->create(['location' => 'Somewhere']);
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
$response = $this->patchJson('/api/user/me', [
|
||||
'location' => ' ',
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonFragment([
|
||||
'id' => $user->id,
|
||||
'location' => null,
|
||||
]);
|
||||
|
||||
$user->refresh();
|
||||
expect($user->location)->toBeNull();
|
||||
});
|
||||
|
||||
it('forbids non-admin rank updates', function (): void {
|
||||
$user = User::factory()->create();
|
||||
$target = User::factory()->create();
|
||||
$rank = Rank::create(['name' => 'Silver']);
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
$response = $this->patchJson("/api/users/{$target->id}/rank", [
|
||||
'rank_id' => $rank->id,
|
||||
]);
|
||||
|
||||
$response->assertStatus(403);
|
||||
});
|
||||
|
||||
it('forbids founder rank updates by non-founder admin', function (): void {
|
||||
$admin = makeAdmin();
|
||||
$founderRole = Role::firstOrCreate(['name' => 'ROLE_FOUNDER'], ['color' => '#111111']);
|
||||
$founder = User::factory()->create();
|
||||
$founder->roles()->attach($founderRole);
|
||||
$rank = Rank::create(['name' => 'Founder Rank']);
|
||||
|
||||
Sanctum::actingAs($admin);
|
||||
$response = $this->patchJson("/api/users/{$founder->id}/rank", [
|
||||
'rank_id' => $rank->id,
|
||||
]);
|
||||
|
||||
$response->assertStatus(403);
|
||||
});
|
||||
|
||||
it('allows admins to update user rank', function (): void {
|
||||
$admin = makeAdmin();
|
||||
$target = User::factory()->create();
|
||||
$rank = Rank::create(['name' => 'Gold']);
|
||||
|
||||
Sanctum::actingAs($admin);
|
||||
$response = $this->patchJson("/api/users/{$target->id}/rank", [
|
||||
'rank_id' => $rank->id,
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('id', $target->id);
|
||||
$response->assertJsonPath('rank.id', $rank->id);
|
||||
$response->assertJsonPath('rank.name', 'Gold');
|
||||
|
||||
$target->refresh();
|
||||
expect($target->rank_id)->toBe($rank->id);
|
||||
});
|
||||
|
||||
it('rejects update without admin role', function (): void {
|
||||
$user = User::factory()->create();
|
||||
$target = User::factory()->create();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
$response = $this->patchJson("/api/users/{$target->id}", [
|
||||
'name' => 'New Name',
|
||||
'email' => 'new@example.com',
|
||||
'rank_id' => null,
|
||||
]);
|
||||
|
||||
$response->assertStatus(403);
|
||||
});
|
||||
|
||||
it('forbids updating founder user when actor is not founder', function (): void {
|
||||
$admin = makeAdmin();
|
||||
$founderRole = Role::firstOrCreate(['name' => 'ROLE_FOUNDER'], ['color' => '#111111']);
|
||||
$founder = User::factory()->create();
|
||||
$founder->roles()->attach($founderRole);
|
||||
|
||||
Sanctum::actingAs($admin);
|
||||
$response = $this->patchJson("/api/users/{$founder->id}", [
|
||||
'name' => 'New Name',
|
||||
'email' => 'new@example.com',
|
||||
'rank_id' => null,
|
||||
]);
|
||||
|
||||
$response->assertStatus(403);
|
||||
});
|
||||
|
||||
it('rejects assigning founder role for non-founder admin', function (): void {
|
||||
$admin = makeAdmin();
|
||||
$target = User::factory()->create();
|
||||
Role::firstOrCreate(['name' => 'ROLE_FOUNDER'], ['color' => '#111111']);
|
||||
|
||||
Sanctum::actingAs($admin);
|
||||
$response = $this->patchJson("/api/users/{$target->id}", [
|
||||
'name' => 'New Name',
|
||||
'email' => 'new@example.com',
|
||||
'rank_id' => null,
|
||||
'roles' => ['ROLE_FOUNDER'],
|
||||
]);
|
||||
|
||||
$response->assertStatus(403);
|
||||
$response->assertJsonFragment(['message' => 'Forbidden']);
|
||||
});
|
||||
|
||||
it('rejects duplicate canonical names', function (): void {
|
||||
$admin = makeAdmin();
|
||||
User::factory()->create([
|
||||
'name' => 'Dupe',
|
||||
'name_canonical' => 'dupe',
|
||||
'email' => 'dupe@example.com',
|
||||
]);
|
||||
$target = User::factory()->create([
|
||||
'name' => 'Other',
|
||||
'name_canonical' => 'other',
|
||||
'email' => 'other@example.com',
|
||||
]);
|
||||
|
||||
Sanctum::actingAs($admin);
|
||||
$response = $this->patchJson("/api/users/{$target->id}", [
|
||||
'name' => 'Dupe',
|
||||
'email' => 'other@example.com',
|
||||
'rank_id' => null,
|
||||
]);
|
||||
|
||||
$response->assertStatus(422);
|
||||
$response->assertJsonFragment(['message' => 'Name already exists.']);
|
||||
});
|
||||
|
||||
it('normalizes roles and updates group color', function (): void {
|
||||
$admin = makeAdmin();
|
||||
$target = User::factory()->create([
|
||||
'name' => 'Target',
|
||||
'email' => 'target@example.com',
|
||||
]);
|
||||
|
||||
Role::firstOrCreate(['name' => 'ROLE_MOD'], ['color' => '#00ff00']);
|
||||
|
||||
Sanctum::actingAs($admin);
|
||||
$response = $this->patchJson("/api/users/{$target->id}", [
|
||||
'name' => 'Target',
|
||||
'email' => 'target@example.com',
|
||||
'rank_id' => null,
|
||||
'roles' => ['ROLE_MOD'],
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonFragment(['group_color' => '#00ff00']);
|
||||
});
|
||||
|
||||
it('updates user name and email as admin', function (): void {
|
||||
$admin = makeAdmin();
|
||||
$target = User::factory()->create([
|
||||
'name' => 'Old Name',
|
||||
'email' => 'old@example.com',
|
||||
'email_verified_at' => now(),
|
||||
]);
|
||||
Role::firstOrCreate(['name' => 'ROLE_MOD'], ['color' => '#00aa00']);
|
||||
|
||||
Sanctum::actingAs($admin);
|
||||
$response = $this->patchJson("/api/users/{$target->id}", [
|
||||
'name' => 'New Name',
|
||||
'email' => 'new@example.com',
|
||||
'rank_id' => null,
|
||||
'roles' => ['ROLE_MOD'],
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonFragment([
|
||||
'id' => $target->id,
|
||||
'name' => 'New Name',
|
||||
'email' => 'new@example.com',
|
||||
]);
|
||||
|
||||
$target->refresh();
|
||||
expect($target->name)->toBe('New Name');
|
||||
expect($target->email)->toBe('new@example.com');
|
||||
expect($target->email_verified_at)->toBeNull();
|
||||
});
|
||||
Reference in New Issue
Block a user