owner = User::factory()->approved()->create(); } public function test_can_create_a_client_with_account_write_scope(): void { Passport::actingAs($this->owner, ['account:write']); $response = $this->postJson('/api/clients', [ 'name' => 'API Created Client', 'grant_type' => 'client_credentials', ]); $response->assertCreated(); $this->assertNotNull($response->json('plain_secret')); $this->assertDatabaseHas('oauth_clients', ['name' => 'API Created Client', 'owner_id' => $this->owner->id]); $this->assertDatabaseHas('audit_logs', ['action' => 'client.created']); } public function test_creating_a_client_without_account_write_scope_is_forbidden(): void { Passport::actingAs($this->owner, ['account:read']); $this->postJson('/api/clients', [ 'name' => 'Should Fail', 'grant_type' => 'client_credentials', ])->assertForbidden(); } public function test_can_list_only_own_clients_with_account_read_scope(): void { $ownClient = Client::create([ 'owner_id' => $this->owner->id, 'owner_type' => User::class, 'name' => 'Mine', 'secret' => null, 'provider' => null, 'redirect_uris' => [], 'grant_types' => ['client_credentials'], 'revoked' => false, ]); $stranger = User::factory()->approved()->create(); Client::create([ 'owner_id' => $stranger->id, 'owner_type' => User::class, 'name' => 'Not Mine', 'secret' => null, 'provider' => null, 'redirect_uris' => [], 'grant_types' => ['client_credentials'], 'revoked' => false, ]); Passport::actingAs($this->owner, ['account:read']); $response = $this->getJson('/api/clients'); $response->assertOk(); $names = collect($response->json())->pluck('name'); $this->assertTrue($names->contains('Mine')); $this->assertFalse($names->contains('Not Mine')); $this->assertTrue(collect($response->json())->every(fn ($c) => $c['id'] !== null)); $this->assertSame($ownClient->id, $response->json('0.id')); } public function test_can_update_own_client(): void { $client = Client::create([ 'owner_id' => $this->owner->id, 'owner_type' => User::class, 'name' => 'Old Name', 'secret' => null, 'provider' => null, 'redirect_uris' => ['https://example.test/callback'], 'grant_types' => ['authorization_code', 'refresh_token'], 'revoked' => false, ]); Passport::actingAs($this->owner, ['account:write']); $response = $this->putJson("/api/clients/{$client->id}", [ 'name' => 'New Name', 'redirect_uris' => ['https://example.test/new'], ]); $response->assertOk(); $response->assertJsonPath('name', 'New Name'); $this->assertSame('New Name', $client->fresh()->name); } public function test_cannot_update_another_users_client(): void { $stranger = User::factory()->approved()->create(); $client = Client::create([ 'owner_id' => $stranger->id, 'owner_type' => User::class, 'name' => 'Not Yours', 'secret' => null, 'provider' => null, 'redirect_uris' => [], 'grant_types' => ['client_credentials'], 'revoked' => false, ]); Passport::actingAs($this->owner, ['account:write']); $this->putJson("/api/clients/{$client->id}", ['name' => 'Hijacked'])->assertNotFound(); } public function test_client_credentials_token_cannot_use_self_service_client_endpoints(): void { $serviceClient = Client::create([ 'owner_id' => $this->owner->id, 'owner_type' => User::class, 'name' => 'Service Client', 'secret' => null, 'provider' => null, 'redirect_uris' => [], 'grant_types' => ['client_credentials'], 'revoked' => false, ]); Passport::actingAsClient($serviceClient, ['account:write']); $this->postJson('/api/clients', [ 'name' => 'Should Fail, No User', 'grant_type' => 'client_credentials', ])->assertStatus(422); } }