newService(); $result = $service->create(1, ' '); $this->assertFalse($result['ok']); $this->assertSame('name_required', $result['error']); } public function testCreateReturnsInvalidUserWhenUserIdIsZero(): void { $service = $this->newService(); $result = $service->create(0, 'My Token'); $this->assertFalse($result['ok']); $this->assertSame('invalid_user', $result['error']); } public function testCreateReturnsInvalidUserWhenUserIdIsNegative(): void { $service = $this->newService(); $result = $service->create(-5, 'My Token'); $this->assertFalse($result['ok']); $this->assertSame('invalid_user', $result['error']); } public function testCreateReturnsUserInactiveWhenUserNotFound(): void { $userRepo = $this->createMock(UserReadRepositoryInterface::class); $userRepo->expects($this->any())->method('find')->with(42)->willReturn(null); $service = $this->newService(userReadRepository: $userRepo); $result = $service->create(42, 'Token'); $this->assertFalse($result['ok']); $this->assertSame('user_inactive', $result['error']); } public function testCreateReturnsUserInactiveWhenUserIsInactive(): void { $userRepo = $this->createMock(UserReadRepositoryInterface::class); $userRepo->expects($this->any())->method('find')->with(42)->willReturn(['id' => 42, 'active' => 0]); $service = $this->newService(userReadRepository: $userRepo); $result = $service->create(42, 'Token'); $this->assertFalse($result['ok']); $this->assertSame('user_inactive', $result['error']); } public function testCreateReturnsMaxTokensReachedWhenLimitHit(): void { $userRepo = $this->createMock(UserReadRepositoryInterface::class); $userRepo->method('find')->willReturn(['id' => 1, 'active' => 1]); $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->expects($this->any())->method('countActiveForUser')->with(1)->willReturn(10); $service = $this->newService(userReadRepository: $userRepo, apiTokenRepository: $tokenRepo); $result = $service->create(1, 'Token'); $this->assertFalse($result['ok']); $this->assertSame('max_tokens_reached', $result['error']); } public function testCreateReturnsTenantNotAssignedWhenUserLacksTenant(): void { $userRepo = $this->createMock(UserReadRepositoryInterface::class); $userRepo->method('find')->willReturn(['id' => 1, 'active' => 1]); $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('countActiveForUser')->willReturn(0); $scopeGateway = $this->createMock(TenantScopeService::class); $scopeGateway->expects($this->any())->method('getUserTenantIds')->with(1)->willReturn([2, 3]); $service = $this->newService( userReadRepository: $userRepo, apiTokenRepository: $tokenRepo, scopeGateway: $scopeGateway ); $result = $service->create(1, 'Token', tenantId: 99); $this->assertFalse($result['ok']); $this->assertSame('tenant_not_assigned', $result['error']); } public function testCreateReturnsInvalidExpiresAtForPastDate(): void { $userRepo = $this->createMock(UserReadRepositoryInterface::class); $userRepo->method('find')->willReturn(['id' => 1, 'active' => 1]); $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('countActiveForUser')->willReturn(0); $settingsGateway = $this->createMock(AuthSettingsGateway::class); $settingsGateway->method('getApiTokenMaxTtlDays')->willReturn(90); $service = $this->newService( userReadRepository: $userRepo, apiTokenRepository: $tokenRepo, settingsGateway: $settingsGateway ); $result = $service->create(1, 'Token', expiresAt: '2020-01-01 00:00:00'); $this->assertFalse($result['ok']); $this->assertSame('invalid_expires_at', $result['error']); } public function testCreateReturnsCreateFailedWhenRepositoryReturnsNull(): void { $userRepo = $this->createMock(UserReadRepositoryInterface::class); $userRepo->method('find')->willReturn(['id' => 1, 'active' => 1]); $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('countActiveForUser')->willReturn(0); $tokenRepo->method('create')->willReturn(null); $settingsGateway = $this->createMock(AuthSettingsGateway::class); $settingsGateway->method('getApiTokenMaxTtlDays')->willReturn(365); $settingsGateway->method('getApiTokenDefaultTtlDays')->willReturn(30); $service = $this->newService( userReadRepository: $userRepo, apiTokenRepository: $tokenRepo, settingsGateway: $settingsGateway ); $result = $service->create(1, 'Token'); $this->assertFalse($result['ok']); $this->assertSame('create_failed', $result['error']); } public function testCreateReturnsCreateFailedWhenFindAfterInsertFails(): void { $userRepo = $this->createMock(UserReadRepositoryInterface::class); $userRepo->method('find')->willReturn(['id' => 1, 'active' => 1]); $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('countActiveForUser')->willReturn(0); $tokenRepo->method('create')->willReturn(55); $tokenRepo->expects($this->any())->method('find')->with(55)->willReturn(null); $settingsGateway = $this->createMock(AuthSettingsGateway::class); $settingsGateway->method('getApiTokenMaxTtlDays')->willReturn(365); $settingsGateway->method('getApiTokenDefaultTtlDays')->willReturn(30); $service = $this->newService( userReadRepository: $userRepo, apiTokenRepository: $tokenRepo, settingsGateway: $settingsGateway ); $result = $service->create(1, 'Token'); $this->assertFalse($result['ok']); $this->assertSame('create_failed', $result['error']); } public function testCreateSuccessReturnsTokenAndId(): void { $userRepo = $this->createMock(UserReadRepositoryInterface::class); $userRepo->method('find')->willReturn(['id' => 1, 'active' => 1]); $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('countActiveForUser')->willReturn(0); $tokenRepo->method('create')->willReturn(99); $tokenRepo->expects($this->any())->method('find')->with(99)->willReturn(['id' => 99, 'uuid' => 'abc-uuid-123']); $settingsGateway = $this->createMock(AuthSettingsGateway::class); $settingsGateway->method('getApiTokenMaxTtlDays')->willReturn(365); $settingsGateway->method('getApiTokenDefaultTtlDays')->willReturn(30); $service = $this->newService( userReadRepository: $userRepo, apiTokenRepository: $tokenRepo, settingsGateway: $settingsGateway ); $result = $service->create(1, 'My API Token'); $this->assertTrue($result['ok']); $this->assertSame(99, $result['id']); $this->assertSame('abc-uuid-123', $result['uuid']); $this->assertStringContainsString(':', $result['token']); $this->assertNotEmpty($result['expires_at']); // Selector is 24-char hex, plain token is 64-char hex [$selector, $plain] = explode(':', $result['token'], 2); $this->assertSame(24, strlen($selector)); $this->assertSame(64, strlen($plain)); } public function testCreateWithTenantSucceedsWhenUserHasTenant(): void { $userRepo = $this->createMock(UserReadRepositoryInterface::class); $userRepo->method('find')->willReturn(['id' => 1, 'active' => 1]); $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('countActiveForUser')->willReturn(0); $tokenRepo->method('create')->willReturn(100); $tokenRepo->expects($this->any())->method('find')->with(100)->willReturn(['id' => 100, 'uuid' => 'uuid-t']); $scopeGateway = $this->createMock(TenantScopeService::class); $scopeGateway->expects($this->any())->method('getUserTenantIds')->with(1)->willReturn([5, 10]); $settingsGateway = $this->createMock(AuthSettingsGateway::class); $settingsGateway->method('getApiTokenMaxTtlDays')->willReturn(365); $settingsGateway->method('getApiTokenDefaultTtlDays')->willReturn(30); $service = $this->newService( userReadRepository: $userRepo, apiTokenRepository: $tokenRepo, settingsGateway: $settingsGateway, scopeGateway: $scopeGateway ); $result = $service->create(1, 'Tenant Token', tenantId: 5); $this->assertTrue($result['ok']); } // --------------------------------------------------------------- // validate() // --------------------------------------------------------------- public function testValidateReturnsNullForEmptyToken(): void { $service = $this->newService(); $this->assertNull($service->validate('')); } public function testValidateReturnsNullForMalformedBearerWithoutColon(): void { $service = $this->newService(); $this->assertNull($service->validate('no-colon-here')); } public function testValidateReturnsNullWhenSelectorIsEmpty(): void { $service = $this->newService(); $this->assertNull($service->validate(':plaintoken')); } public function testValidateReturnsNullWhenPlainTokenIsEmpty(): void { $service = $this->newService(); $this->assertNull($service->validate('selector:')); } public function testValidateReturnsNullWhenSelectorNotFoundPerformsDummyHash(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->expects($this->any())->method('findBySelector')->with('abc123')->willReturn(null); $service = $this->newService(apiTokenRepository: $tokenRepo); $this->assertNull($service->validate('abc123:sometoken')); } public function testValidateReturnsNullWhenTokenIsRevoked(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('findBySelector')->willReturn([ 'id' => 1, 'user_id' => 5, 'revoked_at' => '2024-01-01 00:00:00', 'expires_at' => null, 'token_hash' => hash('sha256', 'secret'), ]); $service = $this->newService(apiTokenRepository: $tokenRepo); $this->assertNull($service->validate('sel:secret')); } public function testValidateReturnsNullWhenTokenIsExpired(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('findBySelector')->willReturn([ 'id' => 1, 'user_id' => 5, 'revoked_at' => null, 'expires_at' => '2020-01-01 00:00:00', 'token_hash' => hash('sha256', 'secret'), ]); $service = $this->newService(apiTokenRepository: $tokenRepo); $this->assertNull($service->validate('sel:secret')); } public function testValidateReturnsNullWhenHashDoesNotMatch(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('findBySelector')->willReturn([ 'id' => 1, 'user_id' => 5, 'revoked_at' => null, 'expires_at' => gmdate('Y-m-d H:i:s', time() + 3600), 'token_hash' => hash('sha256', 'correct-secret'), ]); $service = $this->newService(apiTokenRepository: $tokenRepo); $this->assertNull($service->validate('sel:wrong-secret')); } public function testValidateReturnsNullWhenUserIsInactive(): void { $plainToken = 'mytesttoken123'; $tokenHash = hash('sha256', $plainToken); $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('findBySelector')->willReturn([ 'id' => 1, 'user_id' => 5, 'revoked_at' => null, 'expires_at' => gmdate('Y-m-d H:i:s', time() + 3600), 'token_hash' => $tokenHash, ]); $userRepo = $this->createMock(UserReadRepositoryInterface::class); $userRepo->expects($this->any())->method('find')->with(5)->willReturn(['id' => 5, 'active' => 0]); $service = $this->newService(userReadRepository: $userRepo, apiTokenRepository: $tokenRepo); $this->assertNull($service->validate('sel:' . $plainToken)); } public function testValidateReturnsNullWhenUserNotFound(): void { $plainToken = 'mytesttoken456'; $tokenHash = hash('sha256', $plainToken); $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('findBySelector')->willReturn([ 'id' => 1, 'user_id' => 5, 'revoked_at' => null, 'expires_at' => gmdate('Y-m-d H:i:s', time() + 3600), 'token_hash' => $tokenHash, ]); $userRepo = $this->createMock(UserReadRepositoryInterface::class); $userRepo->expects($this->any())->method('find')->with(5)->willReturn(null); $service = $this->newService(userReadRepository: $userRepo, apiTokenRepository: $tokenRepo); $this->assertNull($service->validate('sel:' . $plainToken)); } public function testValidateSuccessReturnsUserAndTokenAndUpdatesLastUsed(): void { $plainToken = 'validplaintoken789'; $tokenHash = hash('sha256', $plainToken); $tokenRecord = [ 'id' => 10, 'user_id' => 7, 'revoked_at' => null, 'expires_at' => gmdate('Y-m-d H:i:s', time() + 86400), 'token_hash' => $tokenHash, 'tenant_id' => 3, ]; $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->expects($this->any())->method('findBySelector')->with('mysel')->willReturn($tokenRecord); $tokenRepo->expects($this->once()) ->method('updateLastUsed') ->with(10, '192.168.1.1'); $userRecord = ['id' => 7, 'active' => 1, 'email' => 'user@test.com']; $userRepo = $this->createMock(UserReadRepositoryInterface::class); $userRepo->expects($this->any())->method('find')->with(7)->willReturn($userRecord); $requestRuntime = $this->makeRequestRuntime('192.168.1.1'); $service = $this->newService( userReadRepository: $userRepo, apiTokenRepository: $tokenRepo, requestRuntime: $requestRuntime ); $result = $service->validate('mysel:' . $plainToken); $this->assertNotNull($result); $this->assertSame($userRecord, $result['user']); $this->assertSame($tokenRecord, $result['token_record']); $this->assertSame(3, $result['tenant_id']); } public function testValidateSuccessWithNullTenantId(): void { $plainToken = 'notenant123'; $tokenHash = hash('sha256', $plainToken); $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('findBySelector')->willReturn([ 'id' => 11, 'user_id' => 8, 'revoked_at' => null, 'expires_at' => gmdate('Y-m-d H:i:s', time() + 86400), 'token_hash' => $tokenHash, 'tenant_id' => null, ]); $tokenRepo->expects($this->once())->method('updateLastUsed'); $userRepo = $this->createMock(UserReadRepositoryInterface::class); $userRepo->expects($this->any())->method('find')->with(8)->willReturn(['id' => 8, 'active' => 1]); $service = $this->newService( userReadRepository: $userRepo, apiTokenRepository: $tokenRepo, requestRuntime: $this->makeRequestRuntime('10.0.0.1') ); $result = $service->validate('sel:' . $plainToken); $this->assertNotNull($result); $this->assertNull($result['tenant_id']); } // --------------------------------------------------------------- // rotateCurrentToken() // --------------------------------------------------------------- public function testRotateReturnsNotFoundWhenUserIdIsZero(): void { $service = $this->newService(); $result = $service->rotateCurrentToken(0, 1); $this->assertFalse($result['ok']); $this->assertSame('current_token_not_found', $result['error']); } public function testRotateReturnsNotFoundWhenTokenIdIsZero(): void { $service = $this->newService(); $result = $service->rotateCurrentToken(1, 0); $this->assertFalse($result['ok']); $this->assertSame('current_token_not_found', $result['error']); } public function testRotateReturnsNotFoundWhenTokenDoesNotExist(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('find')->willReturn(null); $service = $this->newService(apiTokenRepository: $tokenRepo); $result = $service->rotateCurrentToken(1, 99); $this->assertFalse($result['ok']); $this->assertSame('current_token_not_found', $result['error']); } public function testRotateReturnsNotFoundWhenTokenBelongsToDifferentUser(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('find')->willReturn(['id' => 5, 'user_id' => 99, 'revoked_at' => null, 'expires_at' => null]); $service = $this->newService(apiTokenRepository: $tokenRepo); $result = $service->rotateCurrentToken(1, 5); $this->assertFalse($result['ok']); $this->assertSame('current_token_not_found', $result['error']); } public function testRotateReturnsInvalidWhenTokenIsRevoked(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('find')->willReturn([ 'id' => 5, 'user_id' => 1, 'revoked_at' => '2024-01-01 00:00:00', 'expires_at' => null, 'name' => 'Test', ]); $service = $this->newService(apiTokenRepository: $tokenRepo); $result = $service->rotateCurrentToken(1, 5); $this->assertFalse($result['ok']); $this->assertSame('current_token_invalid', $result['error']); } public function testRotateReturnsInvalidWhenTokenIsExpired(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('find')->willReturn([ 'id' => 5, 'user_id' => 1, 'revoked_at' => null, 'expires_at' => '2020-01-01 00:00:00', 'name' => 'Test', ]); $service = $this->newService(apiTokenRepository: $tokenRepo); $result = $service->rotateCurrentToken(1, 5); $this->assertFalse($result['ok']); $this->assertSame('current_token_invalid', $result['error']); } public function testRotateSuccessRevokesOldAndCreatesNew(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('find')->willReturnCallback(function (int $id) { if ($id === 5) { return [ 'id' => 5, 'user_id' => 1, 'revoked_at' => null, 'expires_at' => gmdate('Y-m-d H:i:s', time() + 86400), 'name' => 'Rotate Me', 'tenant_id' => 2, ]; } // The newly created token return ['id' => $id, 'uuid' => 'new-uuid']; }); $tokenRepo->method('countActiveForUserExcludingId')->willReturn(0); $tokenRepo->method('countActiveForUser')->willReturn(0); $tokenRepo->expects($this->any())->method('revoke')->with(5)->willReturn(true); $tokenRepo->method('create')->willReturn(50); $userRepo = $this->createMock(UserReadRepositoryInterface::class); $userRepo->method('find')->willReturn(['id' => 1, 'active' => 1]); $scopeGateway = $this->createMock(TenantScopeService::class); $scopeGateway->method('getUserTenantIds')->willReturn([2]); $settingsGateway = $this->createMock(AuthSettingsGateway::class); $settingsGateway->method('getApiTokenMaxTtlDays')->willReturn(365); $settingsGateway->method('getApiTokenDefaultTtlDays')->willReturn(30); $dbSession = $this->createMock(DatabaseSessionRepository::class); $dbSession->expects($this->once())->method('beginTransaction'); $dbSession->expects($this->once())->method('commitTransaction'); $dbSession->expects($this->never())->method('rollbackTransaction'); $service = $this->newService( userReadRepository: $userRepo, apiTokenRepository: $tokenRepo, settingsGateway: $settingsGateway, scopeGateway: $scopeGateway, databaseSessionRepository: $dbSession ); $result = $service->rotateCurrentToken(1, 5); $this->assertTrue($result['ok']); $this->assertStringContainsString(':', $result['token']); $this->assertSame(2, $result['tenant_id']); } public function testRotateRollsBackWhenRevokeFails(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('find')->willReturn([ 'id' => 5, 'user_id' => 1, 'revoked_at' => null, 'expires_at' => gmdate('Y-m-d H:i:s', time() + 86400), 'name' => 'Test', 'tenant_id' => null, ]); $tokenRepo->method('countActiveForUserExcludingId')->willReturn(0); $tokenRepo->method('revoke')->willReturn(false); $dbSession = $this->createMock(DatabaseSessionRepository::class); $dbSession->expects($this->once())->method('beginTransaction'); $dbSession->expects($this->once())->method('rollbackTransaction'); $dbSession->expects($this->never())->method('commitTransaction'); $service = $this->newService( apiTokenRepository: $tokenRepo, databaseSessionRepository: $dbSession ); $result = $service->rotateCurrentToken(1, 5); $this->assertFalse($result['ok']); $this->assertSame('rotate_failed', $result['error']); } public function testRotateRollsBackOnException(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('find')->willReturn([ 'id' => 5, 'user_id' => 1, 'revoked_at' => null, 'expires_at' => gmdate('Y-m-d H:i:s', time() + 86400), 'name' => 'Test', 'tenant_id' => null, ]); $tokenRepo->method('countActiveForUserExcludingId')->willReturn(0); $tokenRepo->method('revoke')->willThrowException(new \RuntimeException('DB down')); $dbSession = $this->createMock(DatabaseSessionRepository::class); $dbSession->expects($this->once())->method('beginTransaction'); $dbSession->expects($this->once())->method('rollbackTransaction'); $service = $this->newService( apiTokenRepository: $tokenRepo, databaseSessionRepository: $dbSession ); $result = $service->rotateCurrentToken(1, 5); $this->assertFalse($result['ok']); $this->assertSame('rotate_failed', $result['error']); } // --------------------------------------------------------------- // revoke() // --------------------------------------------------------------- public function testRevokeReturnsNotFoundWhenTokenDoesNotExist(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('find')->willReturn(null); $service = $this->newService(apiTokenRepository: $tokenRepo); $result = $service->revoke(999); $this->assertFalse($result['ok']); $this->assertSame(404, $result['status']); $this->assertSame('not_found', $result['error']); } public function testRevokeSuccessReturnsOk(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->expects($this->any())->method('find')->with(10)->willReturn(['id' => 10]); $tokenRepo->expects($this->once())->method('revoke')->with(10); $service = $this->newService(apiTokenRepository: $tokenRepo); $result = $service->revoke(10); $this->assertTrue($result['ok']); } // --------------------------------------------------------------- // revokeAllForUser() // --------------------------------------------------------------- public function testRevokeAllForUserReturnsCount(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->expects($this->once()) ->method('revokeAllForUser') ->with(5, null) ->willReturn(3); $service = $this->newService(apiTokenRepository: $tokenRepo); $result = $service->revokeAllForUser(5); $this->assertTrue($result['ok']); $this->assertSame(3, $result['count']); } public function testRevokeAllForUserWithTenantReturnsCount(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->expects($this->once()) ->method('revokeAllForUser') ->with(5, 2) ->willReturn(1); $service = $this->newService(apiTokenRepository: $tokenRepo); $result = $service->revokeAllForUser(5, 2); $this->assertTrue($result['ok']); $this->assertSame(1, $result['count']); } // --------------------------------------------------------------- // listForUser() // --------------------------------------------------------------- public function testListForUserReturnsArray(): void { $expected = [ ['id' => 1, 'name' => 'Token A'], ['id' => 2, 'name' => 'Token B'], ]; $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->expects($this->once()) ->method('listByUserId') ->with(7) ->willReturn($expected); $service = $this->newService(apiTokenRepository: $tokenRepo); $this->assertSame($expected, $service->listForUser(7)); } public function testListForUserReturnsEmptyArray(): void { $tokenRepo = $this->createMock(ApiTokenRepositoryInterface::class); $tokenRepo->method('listByUserId')->willReturn([]); $service = $this->newService(apiTokenRepository: $tokenRepo); $this->assertSame([], $service->listForUser(1)); } // --------------------------------------------------------------- // Helpers // --------------------------------------------------------------- /** * RequestRuntimeInterface has a method named "method()" which PHPUnit 12 * cannot mock (reserved name). Use a simple anonymous-class stub instead. */ private function makeRequestRuntime(string $ip = '127.0.0.1'): RequestRuntimeInterface { return new class ($ip) implements RequestRuntimeInterface { public function __construct(private readonly string $stubIp) { } public function method(): string { return 'GET'; } public function path(): string { return '/'; } public function ip(): string { return $this->stubIp; } public function userAgent(): string { return 'test'; } public function queryParams(): array { return []; } public function isSecure(): bool { return false; } }; } private function newService( ?UserReadRepositoryInterface $userReadRepository = null, ?ApiTokenRepositoryInterface $apiTokenRepository = null, ?AuthSettingsGateway $settingsGateway = null, ?TenantScopeService $scopeGateway = null, ?DatabaseSessionRepository $databaseSessionRepository = null, ?RequestRuntimeInterface $requestRuntime = null ): ApiTokenService { return new ApiTokenService( $userReadRepository ?? $this->createMock(UserReadRepositoryInterface::class), $apiTokenRepository ?? $this->createMock(ApiTokenRepositoryInterface::class), $settingsGateway ?? $this->createMock(AuthSettingsGateway::class), $scopeGateway ?? $this->createMock(TenantScopeService::class), $databaseSessionRepository ?? $this->createMock(DatabaseSessionRepository::class), $requestRuntime ?? $this->makeRequestRuntime() ); } }