Files
speedBB/tests/Feature/UserControllerTest.php
tracer 160430e128
All checks were successful
CI/CD Pipeline / test (push) Successful in 10s
CI/CD Pipeline / deploy (push) Successful in 25s
Add extensive controller and model tests
2026-02-07 22:14:42 +01:00

288 lines
8.1 KiB
PHP

<?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();
});