Added API endpoints for user API tokens

This commit is contained in:
Constantin Graf
2025-02-10 20:44:16 -05:00
parent d924fa74ec
commit a9d9c13846
25 changed files with 707 additions and 47 deletions

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Tests\Unit\Database;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Config;
use Tests\TestCase;
class SeederTest extends TestCase
@@ -13,6 +14,7 @@ class SeederTest extends TestCase
public function test_running_the_seeder_multiple_times_runs_successfully(): void
{
$this->setupForSeeder();
$this->artisan('db:seed')
->assertSuccessful();
$this->artisan('db:seed')
@@ -21,9 +23,16 @@ class SeederTest extends TestCase
public function test_fresh_migration_with_seeder_and_rollback_runs_successfully(): void
{
$this->setupForSeeder();
$this->artisan('db:seed')
->assertSuccessful();
$this->artisan('migrate:rollback')
->assertSuccessful();
}
private function setupForSeeder(): void
{
Config::set('passport.personal_access_client.id', '9e27f54d-5dfb-4dde-99d7-834518236c92');
Config::set('passport.personal_access_client.secret', 'EL5mXp3aF8ITjcwoOXRpbSK7zGrWhW4zTDpQXTkf');
}
}

View File

@@ -0,0 +1,186 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Endpoint\Api\V1;
use App\Http\Controllers\Api\V1\ApiTokenController;
use App\Models\Passport\Client;
use App\Models\Passport\Token;
use Illuminate\Support\Facades\Config;
use Laravel\Passport\ClientRepository;
use Laravel\Passport\Passport;
use PHPUnit\Framework\Attributes\UsesClass;
#[UsesClass(ApiTokenController::class)]
class ApiTokenEndpointTest extends ApiEndpointTestAbstract
{
public function test_index_endpoint_returns_list_api_tokens(): void
{
// Arrange
$data = $this->createUserWithPermission([]);
$client = $this->createPersonalAccessClient();
$token = Token::factory()->forUser($data->user)->forClient($client)->create();
$otherData = $this->createUserWithPermission([]);
$otherToken = Token::factory()->forUser($otherData->user)->forClient($client)->create();
Passport::actingAs($data->user);
// Act
$response = $this->getJson(route('api.v1.api-tokens.index'));
// Assert
$this->assertResponseCode($response, 200);
$response->assertExactJson([
'data' => [
[
'id' => $token->id,
'name' => $token->name,
'scopes' => $token->scopes,
'revoked' => $token->revoked,
'created_at' => $token->created_at->toIso8601ZuluString(),
'expires_at' => $token->expires_at->toIso8601ZuluString(),
],
],
]);
}
public function test_store_endpoint_creates_new_api_token(): void
{
// Arrange
$data = $this->createUserWithPermission([]);
$client = $this->createPersonalAccessClient();
Config::set('passport.personal_access_client.id', $client->id);
Config::set('passport.personal_access_client.secret', $client->secret);
Passport::actingAs($data->user);
// Act
$response = $this->withoutExceptionHandling()->postJson(route('api.v1.api-tokens.store'), [
'name' => 'Test Token',
]);
// Assert
$this->assertResponseCode($response, 200);
$response->assertJsonStructure([
'data' => [
'id',
'name',
'scopes',
'revoked',
'created_at',
'expires_at',
'access_token',
],
]);
}
public function test_revoke_endpoint_revokes_api_token(): void
{
// Arrange
$data = $this->createUserWithPermission([]);
$client = $this->createPersonalAccessClient();
$token = Token::factory()->forUser($data->user)->forClient($client)->create();
Passport::actingAs($data->user);
// Act
$response = $this->postJson(route('api.v1.api-tokens.revoke', $token->id));
// Assert
$this->assertResponseCode($response, 204);
$this->assertDatabaseHas(Token::class, [
'id' => $token->id,
'revoked' => true,
]);
}
public function test_revoke_fails_if_token_with_id_does_not_exist(): void
{
// Arrange
$data = $this->createUserWithPermission([]);
Passport::actingAs($data->user);
// Act
$response = $this->postJson(route('api.v1.api-tokens.revoke', 'not-valid'));
// Assert
$this->assertResponseCode($response, 404);
}
public function test_revoke_fails_if_the_token_does_not_belong_to_the_user(): void
{
// Arrange
$data = $this->createUserWithPermission([]);
$otherData = $this->createUserWithPermission([]);
$client = $this->createPersonalAccessClient();
$token = Token::factory()->forUser($otherData->user)->forClient($client)->create();
Passport::actingAs($data->user);
// Act
$response = $this->postJson(route('api.v1.api-tokens.revoke', $token->id));
// Assert
$this->assertResponseCode($response, 404);
$this->assertDatabaseHas(Token::class, [
'id' => $token->id,
'revoked' => false,
]);
}
public function test_destroy_endpoint_deletes_api_token(): void
{
// Arrange
$data = $this->createUserWithPermission([]);
$client = $this->createPersonalAccessClient();
$token = Token::factory()->forUser($data->user)->forClient($client)->create();
Passport::actingAs($data->user);
// Act
$response = $this->deleteJson(route('api.v1.api-tokens.destroy', $token->id));
// Assert
$this->assertResponseCode($response, 204);
$this->assertDatabaseMissing(Token::class, ['id' => $token->id]);
}
public function test_destroy_fails_if_token_with_id_does_not_exist(): void
{
// Arrange
$data = $this->createUserWithPermission([]);
Passport::actingAs($data->user);
// Act
$response = $this->deleteJson(route('api.v1.api-tokens.destroy', 'not-valid'));
// Assert
$this->assertResponseCode($response, 404);
}
public function test_destroy_fails_if_the_token_does_not_belong_to_the_user(): void
{
// Arrange
$data = $this->createUserWithPermission([]);
$otherData = $this->createUserWithPermission([]);
$client = $this->createPersonalAccessClient();
$token = Token::factory()->forUser($otherData->user)->forClient($client)->create();
Passport::actingAs($data->user);
// Act
$response = $this->deleteJson(route('api.v1.api-tokens.destroy', $token->id));
// Assert
$this->assertResponseCode($response, 404);
$this->assertDatabaseHas(Token::class, [
'id' => $token->id,
]);
}
private function createPersonalAccessClient(): Client
{
$clientRepository = new ClientRepository;
/** @var Client $client */
$client = $clientRepository->createPersonalAccessClient(
null, 'Test Personal Access Client', 'http://localhost'
);
return $client;
}
}