new funcionality
This commit is contained in:
207
tests/Feature/Admin/UserCrudTest.php
Normal file
207
tests/Feature/Admin/UserCrudTest.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Admin;
|
||||
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class UserCrudTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private User $admin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$this->admin = User::factory()->approved()->create();
|
||||
$this->admin->assignRole('admin');
|
||||
}
|
||||
|
||||
public function test_admin_can_list_approved_users(): void
|
||||
{
|
||||
$other = User::factory()->approved()->create();
|
||||
User::factory()->create(['approved_at' => null]);
|
||||
|
||||
$response = $this->actingAs($this->admin)->getJson('/web-api/admin/users');
|
||||
|
||||
$response->assertOk();
|
||||
$emails = collect($response->json())->pluck('email');
|
||||
$this->assertTrue($emails->contains($this->admin->email));
|
||||
$this->assertTrue($emails->contains($other->email));
|
||||
}
|
||||
|
||||
public function test_non_admin_cannot_list_users(): void
|
||||
{
|
||||
$user = User::factory()->approved()->create();
|
||||
|
||||
$this->actingAs($user)->getJson('/web-api/admin/users')->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_admin_can_create_a_user(): void
|
||||
{
|
||||
$response = $this->actingAs($this->admin)->postJson('/web-api/admin/users', [
|
||||
'name' => 'New Person',
|
||||
'email' => 'newperson@example.com',
|
||||
'password' => 'password123',
|
||||
'role' => 'user',
|
||||
]);
|
||||
|
||||
$response->assertCreated();
|
||||
$response->assertJsonPath('email', 'newperson@example.com');
|
||||
$response->assertJsonPath('roles.0', 'user');
|
||||
|
||||
$created = User::where('email', 'newperson@example.com')->firstOrFail();
|
||||
$this->assertNotNull($created->approved_at, 'un usuario creado por un admin queda aprobado automáticamente');
|
||||
$this->assertDatabaseHas('audit_logs', ['action' => 'user.created', 'subject_id' => (string) $created->id]);
|
||||
}
|
||||
|
||||
public function test_create_validates_unique_email_and_known_role(): void
|
||||
{
|
||||
$this->actingAs($this->admin)->postJson('/web-api/admin/users', [
|
||||
'name' => 'Dup',
|
||||
'email' => $this->admin->email,
|
||||
'password' => 'password123',
|
||||
'role' => 'user',
|
||||
])->assertUnprocessable()->assertJsonValidationErrors('email');
|
||||
|
||||
$this->actingAs($this->admin)->postJson('/web-api/admin/users', [
|
||||
'name' => 'Bad Role',
|
||||
'email' => 'badrole@example.com',
|
||||
'password' => 'password123',
|
||||
'role' => 'superadmin',
|
||||
])->assertUnprocessable()->assertJsonValidationErrors('role');
|
||||
}
|
||||
|
||||
public function test_admin_can_update_a_users_name_email_role_and_active_state(): void
|
||||
{
|
||||
$target = User::factory()->approved()->create(['is_active' => true]);
|
||||
$target->assignRole('user');
|
||||
|
||||
$response = $this->actingAs($this->admin)->putJson("/web-api/admin/users/{$target->id}", [
|
||||
'name' => 'Renamed',
|
||||
'email' => 'renamed@example.com',
|
||||
'is_active' => false,
|
||||
'role' => 'admin',
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('name', 'Renamed');
|
||||
$response->assertJsonPath('is_active', false);
|
||||
$response->assertJsonPath('roles.0', 'admin');
|
||||
|
||||
$this->assertDatabaseHas('audit_logs', ['action' => 'user.updated', 'subject_id' => (string) $target->id]);
|
||||
}
|
||||
|
||||
public function test_deactivating_a_user_dispatches_the_user_deactivated_event(): void
|
||||
{
|
||||
\Illuminate\Support\Facades\Event::fake([\App\Events\UserDeactivated::class]);
|
||||
|
||||
$target = User::factory()->approved()->create(['is_active' => true]);
|
||||
$target->assignRole('user');
|
||||
|
||||
$this->actingAs($this->admin)->putJson("/web-api/admin/users/{$target->id}", [
|
||||
'name' => $target->name,
|
||||
'email' => $target->email,
|
||||
'is_active' => false,
|
||||
'role' => 'user',
|
||||
])->assertOk();
|
||||
|
||||
\Illuminate\Support\Facades\Event::assertDispatched(\App\Events\UserDeactivated::class);
|
||||
}
|
||||
|
||||
public function test_admin_cannot_remove_their_own_admin_role(): void
|
||||
{
|
||||
$response = $this->actingAs($this->admin)->putJson("/web-api/admin/users/{$this->admin->id}", [
|
||||
'name' => $this->admin->name,
|
||||
'email' => $this->admin->email,
|
||||
'role' => 'user',
|
||||
]);
|
||||
|
||||
$response->assertStatus(422);
|
||||
$this->assertTrue($this->admin->fresh()->hasRole('admin'));
|
||||
}
|
||||
|
||||
public function test_admin_cannot_delete_themselves(): void
|
||||
{
|
||||
$this->actingAs($this->admin)
|
||||
->deleteJson("/web-api/admin/users/{$this->admin->id}")
|
||||
->assertStatus(422);
|
||||
|
||||
$this->assertNull($this->admin->fresh()->deleted_at);
|
||||
}
|
||||
|
||||
public function test_cannot_delete_the_last_admin(): void
|
||||
{
|
||||
$secondAdmin = User::factory()->approved()->create();
|
||||
$secondAdmin->assignRole('admin');
|
||||
|
||||
// Con dos admins, borrar uno debe andar.
|
||||
$this->actingAs($this->admin)
|
||||
->deleteJson("/web-api/admin/users/{$secondAdmin->id}")
|
||||
->assertNoContent();
|
||||
|
||||
// Ahora $this->admin es el único admin — ni siquiera otro usuario con
|
||||
// permiso users.delete (pero sin rol admin) puede borrarlo.
|
||||
$viewer = User::factory()->approved()->create();
|
||||
$viewer->givePermissionTo('users.delete');
|
||||
$this->actingAs($viewer)
|
||||
->deleteJson("/web-api/admin/users/{$this->admin->id}")
|
||||
->assertStatus(422);
|
||||
$this->assertNull($this->admin->fresh()->deleted_at);
|
||||
}
|
||||
|
||||
public function test_admin_can_soft_delete_restore_and_force_delete_a_user(): void
|
||||
{
|
||||
$target = User::factory()->approved()->create();
|
||||
|
||||
$this->actingAs($this->admin)
|
||||
->deleteJson("/web-api/admin/users/{$target->id}")
|
||||
->assertNoContent();
|
||||
|
||||
$this->assertSoftDeleted('users', ['id' => $target->id]);
|
||||
$this->assertDatabaseHas('audit_logs', ['action' => 'user.deleted', 'subject_id' => (string) $target->id]);
|
||||
|
||||
$this->actingAs($this->admin)
|
||||
->getJson('/web-api/admin/users/trashed')
|
||||
->assertOk()
|
||||
->assertJsonFragment(['email' => $target->email]);
|
||||
|
||||
$this->actingAs($this->admin)
|
||||
->postJson("/web-api/admin/users/{$target->id}/restore")
|
||||
->assertOk();
|
||||
$this->assertDatabaseHas('users', ['id' => $target->id, 'deleted_at' => null]);
|
||||
$this->assertDatabaseHas('audit_logs', ['action' => 'user.restored', 'subject_id' => (string) $target->id]);
|
||||
|
||||
$this->actingAs($this->admin)->deleteJson("/web-api/admin/users/{$target->id}")->assertNoContent();
|
||||
$this->actingAs($this->admin)
|
||||
->deleteJson("/web-api/admin/users/{$target->id}/force")
|
||||
->assertNoContent();
|
||||
|
||||
$this->assertDatabaseMissing('users', ['id' => $target->id]);
|
||||
$this->assertDatabaseHas('audit_logs', ['action' => 'user.force_deleted', 'subject_id' => (string) $target->id]);
|
||||
}
|
||||
|
||||
public function test_admin_can_list_and_approve_pending_registrations(): void
|
||||
{
|
||||
$pending = User::factory()->create(['approved_at' => null]);
|
||||
|
||||
$this->actingAs($this->admin)
|
||||
->getJson('/web-api/admin/users/pending')
|
||||
->assertOk()
|
||||
->assertJsonFragment(['email' => $pending->email]);
|
||||
|
||||
$this->actingAs($this->admin)
|
||||
->postJson("/web-api/admin/users/{$pending->id}/approve")
|
||||
->assertOk()
|
||||
->assertJsonPath('email', $pending->email);
|
||||
|
||||
$this->assertNotNull($pending->fresh()->approved_at);
|
||||
$this->assertDatabaseHas('audit_logs', ['action' => 'user.approved', 'subject_id' => (string) $pending->id]);
|
||||
}
|
||||
}
|
||||
136
tests/Feature/Admin/UserSessionTest.php
Normal file
136
tests/Feature/Admin/UserSessionTest.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Admin;
|
||||
|
||||
use App\Models\User;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Passport\Client;
|
||||
use Laravel\Passport\Token;
|
||||
use Tests\TestCase;
|
||||
|
||||
class UserSessionTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private User $admin;
|
||||
private User $target;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->seed(RolesAndPermissionsSeeder::class);
|
||||
|
||||
$this->admin = User::factory()->approved()->create();
|
||||
$this->admin->assignRole('admin');
|
||||
|
||||
$this->target = User::factory()->approved()->create();
|
||||
}
|
||||
|
||||
public function test_admin_can_list_a_users_active_sessions(): void
|
||||
{
|
||||
$token = $this->createActiveToken($this->target, 'Demo App');
|
||||
|
||||
$response = $this->actingAs($this->admin)->getJson("/web-api/admin/users/{$this->target->id}/sessions");
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonFragment(['id' => $token->id, 'client_name' => 'Demo App']);
|
||||
}
|
||||
|
||||
public function test_revoked_and_expired_tokens_are_not_listed(): void
|
||||
{
|
||||
$this->createActiveToken($this->target, 'Revoked App', ['revoked' => true]);
|
||||
$this->createActiveToken($this->target, 'Expired App', ['expires_at' => now()->subDay()]);
|
||||
$active = $this->createActiveToken($this->target, 'Active App');
|
||||
|
||||
$response = $this->actingAs($this->admin)->getJson("/web-api/admin/users/{$this->target->id}/sessions");
|
||||
|
||||
$response->assertOk()->assertJsonCount(1);
|
||||
$response->assertJsonFragment(['id' => $active->id]);
|
||||
}
|
||||
|
||||
public function test_non_admin_cannot_list_sessions(): void
|
||||
{
|
||||
$user = User::factory()->approved()->create();
|
||||
|
||||
$this->actingAs($user)
|
||||
->getJson("/web-api/admin/users/{$this->target->id}/sessions")
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_admin_can_revoke_a_single_session(): void
|
||||
{
|
||||
$token = $this->createActiveToken($this->target);
|
||||
|
||||
$response = $this->actingAs($this->admin)
|
||||
->deleteJson("/web-api/admin/users/{$this->target->id}/sessions/{$token->id}");
|
||||
|
||||
$response->assertNoContent();
|
||||
$this->assertTrue($token->fresh()->revoked);
|
||||
$this->assertDatabaseHas('audit_logs', [
|
||||
'action' => 'user.session_revoked',
|
||||
'subject_id' => (string) $this->target->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_revoking_nonexistent_session_returns_404(): void
|
||||
{
|
||||
$this->actingAs($this->admin)
|
||||
->deleteJson('/web-api/admin/users/'.$this->target->id.'/sessions/'.Str::random(80))
|
||||
->assertNotFound();
|
||||
}
|
||||
|
||||
public function test_admin_can_revoke_all_sessions(): void
|
||||
{
|
||||
$this->createActiveToken($this->target, 'App One');
|
||||
$this->createActiveToken($this->target, 'App Two');
|
||||
|
||||
$response = $this->actingAs($this->admin)
|
||||
->deleteJson("/web-api/admin/users/{$this->target->id}/sessions");
|
||||
|
||||
$response->assertNoContent();
|
||||
$this->assertSame(0, Token::where('user_id', $this->target->id)->where('revoked', false)->count());
|
||||
$this->assertDatabaseHas('audit_logs', [
|
||||
'action' => 'user.sessions_revoked_all',
|
||||
'subject_id' => (string) $this->target->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_non_admin_cannot_revoke_sessions(): void
|
||||
{
|
||||
$user = User::factory()->approved()->create();
|
||||
$token = $this->createActiveToken($this->target);
|
||||
|
||||
$this->actingAs($user)
|
||||
->deleteJson("/web-api/admin/users/{$this->target->id}/sessions/{$token->id}")
|
||||
->assertForbidden();
|
||||
|
||||
$this->assertFalse($token->fresh()->revoked);
|
||||
}
|
||||
|
||||
private function createActiveToken(User $user, string $clientName = 'Test Client', array $overrides = []): Token
|
||||
{
|
||||
$client = Client::create([
|
||||
'name' => $clientName,
|
||||
'secret' => null,
|
||||
'provider' => null,
|
||||
'redirect_uris' => [],
|
||||
'grant_types' => ['authorization_code', 'refresh_token'],
|
||||
'revoked' => false,
|
||||
]);
|
||||
|
||||
return Token::create(array_merge([
|
||||
'id' => Str::random(80),
|
||||
'user_id' => $user->id,
|
||||
'client_id' => $client->id,
|
||||
'name' => null,
|
||||
'scopes' => ['users:read'],
|
||||
'revoked' => false,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
'expires_at' => now()->addDays(15),
|
||||
], $overrides));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user