test: add PHPUnit coverage for 7 critical Tier 1 services (GR-TEST-001)
199 new tests across 9 files covering auth, user lifecycle, and settings: - ApiTokenService: create/validate/rotate/revoke, timing-safe hash, tenant scope - ApiTokenEndpointService: pagination, scope filtering, tenant resolution, expiry - SsoUserLinkService: 2-phase identity lookup, provisioning, profile+avatar sync - UserAssignmentService: sync methods, assignable-role freeze, transactions - UserLifecycleAuditService: encrypted snapshots, enum normalization, retention - UserLifecycleRestoreService: full restore flow, 9 error exits, rollback - AdminSettingsService: API/lifecycle, Microsoft/telemetry, color/SMTP validation Workflow: TEST-TIER1-001 (Analyst → Planner → Executor → Code Review → Security Review → Acceptance → Finalizer — all pass) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
557
tests/Service/User/UserAssignmentServiceTest.php
Normal file
557
tests/Service/User/UserAssignmentServiceTest.php
Normal file
@@ -0,0 +1,557 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Tests\Service\User;
|
||||
|
||||
use MintyPHP\Repository\Access\UserRoleRepositoryInterface;
|
||||
use MintyPHP\Repository\Org\UserDepartmentRepositoryInterface;
|
||||
use MintyPHP\Repository\Support\DatabaseSessionRepository;
|
||||
use MintyPHP\Repository\Tenant\UserTenantRepositoryInterface;
|
||||
use MintyPHP\Repository\User\UserWriteRepositoryInterface;
|
||||
use MintyPHP\Service\Access\AssignableRoleService;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
use MintyPHP\Service\User\UserAssignmentService;
|
||||
use MintyPHP\Service\User\UserDirectoryGateway;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class UserAssignmentServiceTest extends TestCase
|
||||
{
|
||||
// ── buildAssignmentsForUser ──────────────────────────────────────────
|
||||
|
||||
public function testBuildAssignmentsForUserReturnsEmptyStructureWhenUserIdIsZero(): void
|
||||
{
|
||||
$service = $this->newService();
|
||||
$result = $service->buildAssignmentsForUser(0);
|
||||
|
||||
$this->assertSame([], $result['tenants']);
|
||||
$this->assertSame([], $result['departments']);
|
||||
$this->assertSame([], $result['roles']);
|
||||
}
|
||||
|
||||
public function testBuildAssignmentsForUserReturnsEmptyStructureWhenUserIdIsNegative(): void
|
||||
{
|
||||
$service = $this->newService();
|
||||
$result = $service->buildAssignmentsForUser(-5);
|
||||
|
||||
$this->assertSame([], $result['tenants']);
|
||||
$this->assertSame([], $result['departments']);
|
||||
$this->assertSame([], $result['roles']);
|
||||
}
|
||||
|
||||
public function testBuildAssignmentsForUserReturnsTenantsDepartmentsAndRoles(): void
|
||||
{
|
||||
$userTenantRepo = $this->createMock(UserTenantRepositoryInterface::class);
|
||||
$userTenantRepo->expects($this->once())->method('listTenantIdsByUserId')->with(10)->willReturn([1, 2]);
|
||||
|
||||
$userDeptRepo = $this->createMock(UserDepartmentRepositoryInterface::class);
|
||||
$userDeptRepo->expects($this->once())->method('listDepartmentIdsByUserId')->with(10)->willReturn([100]);
|
||||
|
||||
$userRoleRepo = $this->createMock(UserRoleRepositoryInterface::class);
|
||||
$userRoleRepo->expects($this->once())->method('listRoleIdsByUserId')->with(10)->willReturn([50]);
|
||||
|
||||
$directoryGateway = $this->createMock(UserDirectoryGateway::class);
|
||||
$directoryGateway->expects($this->once())->method('listTenantsByIds')->with([1, 2])->willReturn([
|
||||
['id' => 1, 'uuid' => 'tenant-1', 'description' => 'Tenant 1', 'status' => 'active'],
|
||||
['id' => 2, 'uuid' => 'tenant-2', 'description' => 'Tenant 2', 'status' => 'active'],
|
||||
]);
|
||||
$directoryGateway->expects($this->once())->method('listDepartmentsByIds')->with([100], true)->willReturn([
|
||||
['id' => 100, 'uuid' => 'dept-100', 'description' => 'Dept 100', 'active' => 1, 'tenant_id' => 1],
|
||||
]);
|
||||
$directoryGateway->expects($this->once())->method('listRolesByIds')->with([50])->willReturn([
|
||||
['id' => 50, 'uuid' => 'role-50', 'description' => 'Role 50', 'active' => 1],
|
||||
]);
|
||||
|
||||
$service = $this->newService(
|
||||
userTenantRepository: $userTenantRepo,
|
||||
userDepartmentRepository: $userDeptRepo,
|
||||
userRoleRepository: $userRoleRepo,
|
||||
directoryGateway: $directoryGateway
|
||||
);
|
||||
|
||||
$result = $service->buildAssignmentsForUser(10);
|
||||
|
||||
$this->assertCount(2, $result['tenants']);
|
||||
$this->assertSame(1, $result['tenants'][0]['id']);
|
||||
$this->assertSame(2, $result['tenants'][1]['id']);
|
||||
$this->assertCount(1, $result['departments']);
|
||||
$this->assertSame(100, $result['departments'][0]['id']);
|
||||
$this->assertSame('tenant-1', $result['departments'][0]['tenant_uuid']);
|
||||
$this->assertCount(1, $result['roles']);
|
||||
$this->assertSame(50, $result['roles'][0]['id']);
|
||||
}
|
||||
|
||||
// ── syncTenants ─────────────────────────────────────────────────────
|
||||
|
||||
public function testSyncTenantsNormalizesAndCallsRepository(): void
|
||||
{
|
||||
$directoryGateway = $this->createMock(UserDirectoryGateway::class);
|
||||
$directoryGateway->expects($this->once())->method('listTenantIds')->willReturn([1, 2, 3]);
|
||||
|
||||
$userTenantRepo = $this->createMock(UserTenantRepositoryInterface::class);
|
||||
$userTenantRepo->expects($this->once())
|
||||
->method('replaceForUser')
|
||||
->with(10, $this->callback(static fn (array $ids): bool => $ids === [1, 2]))
|
||||
->willReturn(true);
|
||||
|
||||
$userWriteRepo = $this->createMock(UserWriteRepositoryInterface::class);
|
||||
$userWriteRepo->expects($this->once())->method('bumpAuthzVersion')->with(10);
|
||||
|
||||
$service = $this->newService(
|
||||
userWriteRepository: $userWriteRepo,
|
||||
userTenantRepository: $userTenantRepo,
|
||||
directoryGateway: $directoryGateway
|
||||
);
|
||||
|
||||
$result = $service->syncTenants(10, [1, 2]);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testSyncTenantsDoesNotBumpAuthzWhenFlagIsFalse(): void
|
||||
{
|
||||
$directoryGateway = $this->createMock(UserDirectoryGateway::class);
|
||||
$directoryGateway->method('listTenantIds')->willReturn([1]);
|
||||
|
||||
$userTenantRepo = $this->createMock(UserTenantRepositoryInterface::class);
|
||||
$userTenantRepo->method('replaceForUser')->willReturn(true);
|
||||
|
||||
$userWriteRepo = $this->createMock(UserWriteRepositoryInterface::class);
|
||||
$userWriteRepo->expects($this->never())->method('bumpAuthzVersion');
|
||||
|
||||
$service = $this->newService(
|
||||
userWriteRepository: $userWriteRepo,
|
||||
userTenantRepository: $userTenantRepo,
|
||||
directoryGateway: $directoryGateway
|
||||
);
|
||||
|
||||
$result = $service->syncTenants(10, [1], false);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
// ── syncRoles ───────────────────────────────────────────────────────
|
||||
|
||||
public function testSyncRolesDeduplicatesAndFiltersAgainstActiveRoles(): void
|
||||
{
|
||||
$directoryGateway = $this->createMock(UserDirectoryGateway::class);
|
||||
$directoryGateway->expects($this->once())->method('listActiveRoleIds')->willReturn([1, 2, 3]);
|
||||
|
||||
$userRoleRepo = $this->createMock(UserRoleRepositoryInterface::class);
|
||||
$userRoleRepo->expects($this->once())
|
||||
->method('replaceForUser')
|
||||
->with(10, $this->callback(static fn (array $ids): bool => $ids === [1, 3]))
|
||||
->willReturn(true);
|
||||
|
||||
$permissionService = $this->createMock(PermissionService::class);
|
||||
$permissionService->expects($this->once())->method('clearUserCache')->with(10);
|
||||
|
||||
$userWriteRepo = $this->createMock(UserWriteRepositoryInterface::class);
|
||||
$userWriteRepo->expects($this->once())->method('bumpAuthzVersion')->with(10);
|
||||
|
||||
$service = $this->newService(
|
||||
userWriteRepository: $userWriteRepo,
|
||||
userRoleRepository: $userRoleRepo,
|
||||
directoryGateway: $directoryGateway,
|
||||
permissionService: $permissionService
|
||||
);
|
||||
|
||||
// Pass duplicates and an inactive role (99)
|
||||
$result = $service->syncRoles(10, [1, 3, 3, 99]);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testSyncRolesWithActorEnforcesAssignableRoleFreeze(): void
|
||||
{
|
||||
$directoryGateway = $this->createMock(UserDirectoryGateway::class);
|
||||
$directoryGateway->method('listActiveRoleIds')->willReturn([1, 2, 3, 4]);
|
||||
|
||||
$userRoleRepo = $this->createMock(UserRoleRepositoryInterface::class);
|
||||
$userRoleRepo->expects($this->once())->method('listRoleIdsByUserId')->with(10)->willReturn([2, 4]);
|
||||
$userRoleRepo->expects($this->once())
|
||||
->method('replaceForUser')
|
||||
->with(10, [4, 1]) // frozen(4) + touched(1)
|
||||
->willReturn(true);
|
||||
|
||||
$assignableRoleService = $this->createMock(AssignableRoleService::class);
|
||||
$assignableRoleService->expects($this->once())
|
||||
->method('computeEffectiveRoleIds')
|
||||
->with(99, [1, 3], [2, 4])
|
||||
->willReturn([4, 1]); // role 4 frozen, role 1 submitted & assignable
|
||||
|
||||
$permissionService = $this->createMock(PermissionService::class);
|
||||
$permissionService->expects($this->once())->method('clearUserCache')->with(10);
|
||||
|
||||
$service = $this->newService(
|
||||
userRoleRepository: $userRoleRepo,
|
||||
directoryGateway: $directoryGateway,
|
||||
permissionService: $permissionService,
|
||||
assignableRoleService: $assignableRoleService
|
||||
);
|
||||
|
||||
$result = $service->syncRoles(10, [1, 3], true, 99);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testSyncRolesClearsPermissionCacheEvenOnFailure(): void
|
||||
{
|
||||
$directoryGateway = $this->createMock(UserDirectoryGateway::class);
|
||||
$directoryGateway->method('listActiveRoleIds')->willReturn([1]);
|
||||
|
||||
$userRoleRepo = $this->createMock(UserRoleRepositoryInterface::class);
|
||||
$userRoleRepo->expects($this->once())->method('replaceForUser')->willReturn(false);
|
||||
|
||||
$permissionService = $this->createMock(PermissionService::class);
|
||||
$permissionService->expects($this->once())->method('clearUserCache')->with(10);
|
||||
|
||||
$userWriteRepo = $this->createMock(UserWriteRepositoryInterface::class);
|
||||
$userWriteRepo->expects($this->never())->method('bumpAuthzVersion');
|
||||
|
||||
$service = $this->newService(
|
||||
userWriteRepository: $userWriteRepo,
|
||||
userRoleRepository: $userRoleRepo,
|
||||
directoryGateway: $directoryGateway,
|
||||
permissionService: $permissionService
|
||||
);
|
||||
|
||||
$result = $service->syncRoles(10, [1]);
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
// ── syncDepartments ─────────────────────────────────────────────────
|
||||
|
||||
public function testSyncDepartmentsValidatesDepartmentsBelongToUserTenants(): void
|
||||
{
|
||||
$userTenantRepo = $this->createMock(UserTenantRepositoryInterface::class);
|
||||
$userTenantRepo->expects($this->once())->method('listTenantIdsByUserId')->with(10)->willReturn([1]);
|
||||
|
||||
$directoryGateway = $this->createMock(UserDirectoryGateway::class);
|
||||
$directoryGateway->expects($this->once())
|
||||
->method('listActiveDepartmentIdsByTenantIds')
|
||||
->with([1])
|
||||
->willReturn([100, 101]);
|
||||
|
||||
$userDeptRepo = $this->createMock(UserDepartmentRepositoryInterface::class);
|
||||
$userDeptRepo->expects($this->once())
|
||||
->method('replaceForUser')
|
||||
->with(10, [100]) // 200 filtered out because not in allowed list
|
||||
->willReturn(true);
|
||||
|
||||
$userWriteRepo = $this->createMock(UserWriteRepositoryInterface::class);
|
||||
$userWriteRepo->expects($this->once())->method('bumpAuthzVersion')->with(10);
|
||||
|
||||
$service = $this->newService(
|
||||
userWriteRepository: $userWriteRepo,
|
||||
userTenantRepository: $userTenantRepo,
|
||||
userDepartmentRepository: $userDeptRepo,
|
||||
directoryGateway: $directoryGateway
|
||||
);
|
||||
|
||||
$result = $service->syncDepartments(10, [100, 200]);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testSyncDepartmentsRejectsAllWhenUserHasNoTenants(): void
|
||||
{
|
||||
$userTenantRepo = $this->createMock(UserTenantRepositoryInterface::class);
|
||||
$userTenantRepo->expects($this->once())->method('listTenantIdsByUserId')->with(10)->willReturn([]);
|
||||
|
||||
$directoryGateway = $this->createMock(UserDirectoryGateway::class);
|
||||
$directoryGateway->expects($this->never())->method('listActiveDepartmentIdsByTenantIds');
|
||||
|
||||
$userDeptRepo = $this->createMock(UserDepartmentRepositoryInterface::class);
|
||||
$userDeptRepo->expects($this->once())
|
||||
->method('replaceForUser')
|
||||
->with(10, [])
|
||||
->willReturn(true);
|
||||
|
||||
$service = $this->newService(
|
||||
userTenantRepository: $userTenantRepo,
|
||||
userDepartmentRepository: $userDeptRepo,
|
||||
directoryGateway: $directoryGateway
|
||||
);
|
||||
|
||||
$result = $service->syncDepartments(10, [100]);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testSyncDepartmentsRejectsAllWhenNoActiveDepartmentsInTenants(): void
|
||||
{
|
||||
$userTenantRepo = $this->createMock(UserTenantRepositoryInterface::class);
|
||||
$userTenantRepo->method('listTenantIdsByUserId')->willReturn([1]);
|
||||
|
||||
$directoryGateway = $this->createMock(UserDirectoryGateway::class);
|
||||
$directoryGateway->method('listActiveDepartmentIdsByTenantIds')->willReturn([]);
|
||||
|
||||
$userDeptRepo = $this->createMock(UserDepartmentRepositoryInterface::class);
|
||||
$userDeptRepo->expects($this->once())
|
||||
->method('replaceForUser')
|
||||
->with(10, [])
|
||||
->willReturn(true);
|
||||
|
||||
$service = $this->newService(
|
||||
userTenantRepository: $userTenantRepo,
|
||||
userDepartmentRepository: $userDeptRepo,
|
||||
directoryGateway: $directoryGateway
|
||||
);
|
||||
|
||||
$result = $service->syncDepartments(10, [100]);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
// ── syncAllAssignments ──────────────────────────────────────────────
|
||||
|
||||
public function testSyncAllAssignmentsReturnsFalseWhenUserIdIsZero(): void
|
||||
{
|
||||
$service = $this->newService();
|
||||
$this->assertFalse($service->syncAllAssignments(0, [], [], []));
|
||||
}
|
||||
|
||||
public function testSyncAllAssignmentsCommitsTransactionOnSuccess(): void
|
||||
{
|
||||
$directoryGateway = $this->createMock(UserDirectoryGateway::class);
|
||||
$directoryGateway->method('listTenantIds')->willReturn([1]);
|
||||
$directoryGateway->method('listActiveRoleIds')->willReturn([2]);
|
||||
$directoryGateway->method('listActiveDepartmentIdsByTenantIds')->willReturn([3]);
|
||||
|
||||
$userTenantRepo = $this->createMock(UserTenantRepositoryInterface::class);
|
||||
$userTenantRepo->method('replaceForUser')->willReturn(true);
|
||||
$userTenantRepo->method('listTenantIdsByUserId')->willReturn([1]);
|
||||
|
||||
$userRoleRepo = $this->createMock(UserRoleRepositoryInterface::class);
|
||||
$userRoleRepo->method('replaceForUser')->willReturn(true);
|
||||
|
||||
$userDeptRepo = $this->createMock(UserDepartmentRepositoryInterface::class);
|
||||
$userDeptRepo->method('replaceForUser')->willReturn(true);
|
||||
|
||||
$permissionService = $this->createMock(PermissionService::class);
|
||||
|
||||
$userWriteRepo = $this->createMock(UserWriteRepositoryInterface::class);
|
||||
$userWriteRepo->expects($this->once())->method('bumpAuthzVersion')->with(10);
|
||||
|
||||
$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(
|
||||
userWriteRepository: $userWriteRepo,
|
||||
userTenantRepository: $userTenantRepo,
|
||||
userRoleRepository: $userRoleRepo,
|
||||
userDepartmentRepository: $userDeptRepo,
|
||||
directoryGateway: $directoryGateway,
|
||||
permissionService: $permissionService,
|
||||
databaseSessionRepository: $dbSession
|
||||
);
|
||||
|
||||
$result = $service->syncAllAssignments(10, [1], [2], [3]);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testSyncAllAssignmentsRollsBackOnSyncFailure(): void
|
||||
{
|
||||
$directoryGateway = $this->createMock(UserDirectoryGateway::class);
|
||||
$directoryGateway->method('listTenantIds')->willReturn([1]);
|
||||
|
||||
$userTenantRepo = $this->createMock(UserTenantRepositoryInterface::class);
|
||||
$userTenantRepo->method('replaceForUser')->willReturn(false); // tenant sync fails
|
||||
|
||||
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||||
$dbSession->expects($this->once())->method('beginTransaction');
|
||||
$dbSession->expects($this->never())->method('commitTransaction');
|
||||
$dbSession->expects($this->once())->method('rollbackTransaction');
|
||||
|
||||
$service = $this->newService(
|
||||
userTenantRepository: $userTenantRepo,
|
||||
directoryGateway: $directoryGateway,
|
||||
databaseSessionRepository: $dbSession
|
||||
);
|
||||
|
||||
$result = $service->syncAllAssignments(10, [1], [2], [3]);
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testSyncAllAssignmentsRollsBackOnException(): void
|
||||
{
|
||||
$directoryGateway = $this->createMock(UserDirectoryGateway::class);
|
||||
$directoryGateway->method('listTenantIds')
|
||||
->willThrowException(new \RuntimeException('db error'));
|
||||
|
||||
$userTenantRepo = $this->createMock(UserTenantRepositoryInterface::class);
|
||||
|
||||
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||||
$dbSession->expects($this->once())->method('beginTransaction');
|
||||
$dbSession->expects($this->never())->method('commitTransaction');
|
||||
$dbSession->expects($this->once())->method('rollbackTransaction');
|
||||
|
||||
$service = $this->newService(
|
||||
userTenantRepository: $userTenantRepo,
|
||||
directoryGateway: $directoryGateway,
|
||||
databaseSessionRepository: $dbSession
|
||||
);
|
||||
|
||||
$result = $service->syncAllAssignments(10, [1], [], []);
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
public function testSyncAllAssignmentsBumpsAuthzOnlyOnce(): void
|
||||
{
|
||||
$directoryGateway = $this->createMock(UserDirectoryGateway::class);
|
||||
$directoryGateway->method('listTenantIds')->willReturn([1]);
|
||||
$directoryGateway->method('listActiveRoleIds')->willReturn([2]);
|
||||
$directoryGateway->method('listActiveDepartmentIdsByTenantIds')->willReturn([3]);
|
||||
|
||||
$userTenantRepo = $this->createMock(UserTenantRepositoryInterface::class);
|
||||
$userTenantRepo->method('replaceForUser')->willReturn(true);
|
||||
$userTenantRepo->method('listTenantIdsByUserId')->willReturn([1]);
|
||||
|
||||
$userRoleRepo = $this->createMock(UserRoleRepositoryInterface::class);
|
||||
$userRoleRepo->method('replaceForUser')->willReturn(true);
|
||||
|
||||
$userDeptRepo = $this->createMock(UserDepartmentRepositoryInterface::class);
|
||||
$userDeptRepo->method('replaceForUser')->willReturn(true);
|
||||
|
||||
$permissionService = $this->createMock(PermissionService::class);
|
||||
|
||||
$userWriteRepo = $this->createMock(UserWriteRepositoryInterface::class);
|
||||
// bumpAuthzVersion should be called exactly once (not three times)
|
||||
$userWriteRepo->expects($this->once())->method('bumpAuthzVersion')->with(10);
|
||||
|
||||
$dbSession = $this->createMock(DatabaseSessionRepository::class);
|
||||
|
||||
$service = $this->newService(
|
||||
userWriteRepository: $userWriteRepo,
|
||||
userTenantRepository: $userTenantRepo,
|
||||
userRoleRepository: $userRoleRepo,
|
||||
userDepartmentRepository: $userDeptRepo,
|
||||
directoryGateway: $directoryGateway,
|
||||
permissionService: $permissionService,
|
||||
databaseSessionRepository: $dbSession
|
||||
);
|
||||
|
||||
$service->syncAllAssignments(10, [1], [2], [3]);
|
||||
}
|
||||
|
||||
// ── bumpAuthzVersion ────────────────────────────────────────────────
|
||||
|
||||
public function testBumpAuthzVersionReturnsEarlyWhenUserIdIsZero(): void
|
||||
{
|
||||
$userWriteRepo = $this->createMock(UserWriteRepositoryInterface::class);
|
||||
$userWriteRepo->expects($this->never())->method('bumpAuthzVersion');
|
||||
|
||||
$service = $this->newService(userWriteRepository: $userWriteRepo);
|
||||
$service->bumpAuthzVersion(0);
|
||||
}
|
||||
|
||||
public function testBumpAuthzVersionReturnsEarlyWhenUserIdIsNegative(): void
|
||||
{
|
||||
$userWriteRepo = $this->createMock(UserWriteRepositoryInterface::class);
|
||||
$userWriteRepo->expects($this->never())->method('bumpAuthzVersion');
|
||||
|
||||
$service = $this->newService(userWriteRepository: $userWriteRepo);
|
||||
$service->bumpAuthzVersion(-1);
|
||||
}
|
||||
|
||||
public function testBumpAuthzVersionCallsRepository(): void
|
||||
{
|
||||
$userWriteRepo = $this->createMock(UserWriteRepositoryInterface::class);
|
||||
$userWriteRepo->expects($this->once())->method('bumpAuthzVersion')->with(42);
|
||||
|
||||
$service = $this->newService(userWriteRepository: $userWriteRepo);
|
||||
$service->bumpAuthzVersion(42);
|
||||
}
|
||||
|
||||
// ── normalizeIdInput ────────────────────────────────────────────────
|
||||
|
||||
public function testNormalizeIdInputWithScalarInt(): void
|
||||
{
|
||||
$service = $this->newService();
|
||||
$this->assertSame([5], $service->normalizeIdInput(5));
|
||||
}
|
||||
|
||||
public function testNormalizeIdInputWithArrayOfInts(): void
|
||||
{
|
||||
$service = $this->newService();
|
||||
$this->assertSame([1, 2, 3], $service->normalizeIdInput([1, 2, 3]));
|
||||
}
|
||||
|
||||
public function testNormalizeIdInputWithCommaSeparatedString(): void
|
||||
{
|
||||
$service = $this->newService();
|
||||
$this->assertSame([1, 2, 3], $service->normalizeIdInput('1,2,3'));
|
||||
}
|
||||
|
||||
public function testNormalizeIdInputWithNestedArrays(): void
|
||||
{
|
||||
$service = $this->newService();
|
||||
$this->assertSame([1, 2, 3], $service->normalizeIdInput([[1, [2]], 3]));
|
||||
}
|
||||
|
||||
public function testNormalizeIdInputFiltersNegativeValues(): void
|
||||
{
|
||||
$service = $this->newService();
|
||||
$this->assertSame([1, 3], $service->normalizeIdInput([1, -2, 3, 0]));
|
||||
}
|
||||
|
||||
public function testNormalizeIdInputRemovesDuplicates(): void
|
||||
{
|
||||
$service = $this->newService();
|
||||
$this->assertSame([1, 2], $service->normalizeIdInput([1, 2, 1, 2]));
|
||||
}
|
||||
|
||||
public function testNormalizeIdInputHandlesEmptyString(): void
|
||||
{
|
||||
$service = $this->newService();
|
||||
$this->assertSame([], $service->normalizeIdInput(''));
|
||||
}
|
||||
|
||||
public function testNormalizeIdInputHandlesEmptyArray(): void
|
||||
{
|
||||
$service = $this->newService();
|
||||
$this->assertSame([], $service->normalizeIdInput([]));
|
||||
}
|
||||
|
||||
// ── normalizeTenantIds ──────────────────────────────────────────────
|
||||
|
||||
public function testNormalizeTenantIdsFiltersAgainstValidTenantIds(): void
|
||||
{
|
||||
$directoryGateway = $this->createMock(UserDirectoryGateway::class);
|
||||
$directoryGateway->expects($this->once())->method('listTenantIds')->willReturn([1, 2, 3]);
|
||||
|
||||
$service = $this->newService(directoryGateway: $directoryGateway);
|
||||
$result = $service->normalizeTenantIds([1, 99, 2]);
|
||||
|
||||
$this->assertSame([1, 2], $result);
|
||||
}
|
||||
|
||||
public function testNormalizeTenantIdsFiltersNegativeAndZeroIds(): void
|
||||
{
|
||||
$directoryGateway = $this->createMock(UserDirectoryGateway::class);
|
||||
$directoryGateway->method('listTenantIds')->willReturn([1, 2]);
|
||||
|
||||
$service = $this->newService(directoryGateway: $directoryGateway);
|
||||
$result = $service->normalizeTenantIds([0, -1, 1]);
|
||||
|
||||
$this->assertSame([1], $result);
|
||||
}
|
||||
|
||||
// ── Helper ──────────────────────────────────────────────────────────
|
||||
|
||||
private function newService(
|
||||
?UserWriteRepositoryInterface $userWriteRepository = null,
|
||||
?UserTenantRepositoryInterface $userTenantRepository = null,
|
||||
?UserRoleRepositoryInterface $userRoleRepository = null,
|
||||
?UserDepartmentRepositoryInterface $userDepartmentRepository = null,
|
||||
?UserDirectoryGateway $directoryGateway = null,
|
||||
?PermissionService $permissionService = null,
|
||||
?DatabaseSessionRepository $databaseSessionRepository = null,
|
||||
?AssignableRoleService $assignableRoleService = null,
|
||||
): UserAssignmentService {
|
||||
return new UserAssignmentService(
|
||||
$userWriteRepository ?? $this->createMock(UserWriteRepositoryInterface::class),
|
||||
$userTenantRepository ?? $this->createMock(UserTenantRepositoryInterface::class),
|
||||
$userRoleRepository ?? $this->createMock(UserRoleRepositoryInterface::class),
|
||||
$userDepartmentRepository ?? $this->createMock(UserDepartmentRepositoryInterface::class),
|
||||
$directoryGateway ?? $this->createMock(UserDirectoryGateway::class),
|
||||
$permissionService ?? $this->createMock(PermissionService::class),
|
||||
$databaseSessionRepository ?? $this->createMock(DatabaseSessionRepository::class),
|
||||
$assignableRoleService ?? $this->createMock(AssignableRoleService::class),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user