288 lines
8.1 KiB
PHP
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();
|
|
});
|