Files
breadcrumb-the-shire/tests/Service/User/UserTenantContextServiceTest.php

295 lines
12 KiB
PHP
Raw Permalink Normal View History

<?php
namespace MintyPHP\Tests\Service\User;
use MintyPHP\Repository\Tenant\UserTenantRepositoryInterface;
use MintyPHP\Repository\User\UserReadRepositoryInterface;
use MintyPHP\Repository\User\UserWriteRepositoryInterface;
use MintyPHP\Service\Tenant\TenantScopeService;
use MintyPHP\Service\User\UserDirectoryGateway;
use MintyPHP\Service\User\UserTenantContextService;
use PHPUnit\Framework\TestCase;
final class UserTenantContextServiceTest extends TestCase
{
public function testGetCurrentTenantIdReturnsNullWhenUserMissing(): void
{
$userRead = $this->createMock(UserReadRepositoryInterface::class);
$userRead->method('find')->willReturn(null);
$service = $this->makeService(userRead: $userRead);
$this->assertNull($service->getCurrentTenantId(42));
}
public function testGetCurrentTenantIdReturnsNullWhenUserHasNoAccessibleTenants(): void
{
$userRead = $this->createMock(UserReadRepositoryInterface::class);
$userRead->method('find')->willReturn(['current_tenant_id' => 5, 'primary_tenant_id' => 5]);
$scope = $this->createMock(TenantScopeService::class);
$scope->method('getUserTenantIds')->willReturn([]);
$service = $this->makeService(userRead: $userRead, scope: $scope);
$this->assertNull($service->getCurrentTenantId(42));
}
public function testGetCurrentTenantIdPrefersCurrentWhenInAccessibleSet(): void
{
$userRead = $this->createMock(UserReadRepositoryInterface::class);
$userRead->method('find')->willReturn([
'current_tenant_id' => 7,
'primary_tenant_id' => 3,
]);
$scope = $this->createMock(TenantScopeService::class);
$scope->method('getUserTenantIds')->willReturn([3, 7, 9]);
$service = $this->makeService(userRead: $userRead, scope: $scope);
$this->assertSame(7, $service->getCurrentTenantId(42));
}
public function testGetCurrentTenantIdFallsBackToPrimaryWhenCurrentNotAccessible(): void
{
$userRead = $this->createMock(UserReadRepositoryInterface::class);
$userRead->method('find')->willReturn([
'current_tenant_id' => 999,
'primary_tenant_id' => 3,
]);
$scope = $this->createMock(TenantScopeService::class);
$scope->method('getUserTenantIds')->willReturn([3, 9]);
$service = $this->makeService(userRead: $userRead, scope: $scope);
$this->assertSame(3, $service->getCurrentTenantId(42));
}
public function testGetCurrentTenantIdFallsBackToFirstAccessibleWhenNeitherCurrentNorPrimaryMatch(): void
{
$userRead = $this->createMock(UserReadRepositoryInterface::class);
$userRead->method('find')->willReturn([
'current_tenant_id' => 0,
'primary_tenant_id' => 999,
]);
$scope = $this->createMock(TenantScopeService::class);
$scope->method('getUserTenantIds')->willReturn([9, 11]);
$service = $this->makeService(userRead: $userRead, scope: $scope);
$this->assertSame(9, $service->getCurrentTenantId(42));
}
public function testGetCurrentTenantReturnsNullWhenNoCurrentTenantResolvable(): void
{
$userRead = $this->createMock(UserReadRepositoryInterface::class);
$userRead->method('find')->willReturn(null);
$directory = $this->createMock(UserDirectoryGateway::class);
$directory->expects($this->never())->method('findTenant');
$service = $this->makeService(userRead: $userRead, directory: $directory);
$this->assertNull($service->getCurrentTenant(42));
}
public function testGetCurrentTenantLoadsTenantRecord(): void
{
$userRead = $this->createMock(UserReadRepositoryInterface::class);
$userRead->method('find')->willReturn(['current_tenant_id' => 7, 'primary_tenant_id' => 7]);
$scope = $this->createMock(TenantScopeService::class);
$scope->method('getUserTenantIds')->willReturn([7]);
$directory = $this->createMock(UserDirectoryGateway::class);
$directory->expects($this->once())
->method('findTenant')
->with(7)
->willReturn(['id' => 7, 'description' => 'Acme']);
$service = $this->makeService(userRead: $userRead, scope: $scope, directory: $directory);
$this->assertSame(['id' => 7, 'description' => 'Acme'], $service->getCurrentTenant(42));
}
public function testGetAvailableTenantsReturnsEmptyWhenNoneAccessible(): void
{
$scope = $this->createMock(TenantScopeService::class);
$scope->method('getUserTenantIds')->willReturn([]);
$service = $this->makeService(scope: $scope);
$this->assertSame([], $service->getAvailableTenants(42));
}
public function testGetAvailableTenantsFiltersInactiveAndSortsByDescription(): void
{
$scope = $this->createMock(TenantScopeService::class);
$scope->method('getUserTenantIds')->willReturn([1, 2, 3]);
$directory = $this->createMock(UserDirectoryGateway::class);
$directory->method('findTenant')->willReturnCallback(static function (int $id): ?array {
return match ($id) {
1 => ['id' => 1, 'description' => 'Charlie', 'status' => 'active'],
2 => ['id' => 2, 'description' => 'Alpha', 'status' => 'inactive'],
3 => ['id' => 3, 'description' => 'Bravo', 'status' => 'active'],
default => null,
};
});
$service = $this->makeService(scope: $scope, directory: $directory);
$result = $service->getAvailableTenants(42);
$this->assertCount(2, $result);
$this->assertSame('Bravo', $result[0]['description']);
$this->assertSame('Charlie', $result[1]['description']);
}
public function testGetAssignedActiveTenantsReturnsEmptyForInvalidUserId(): void
{
$service = $this->makeService();
$this->assertSame([], $service->getAssignedActiveTenants(0));
$this->assertSame([], $service->getAssignedActiveTenants(-1));
}
public function testGetAssignedActiveTenantsFiltersInactiveAndPreservesAssignedOrder(): void
{
$userTenant = $this->createMock(UserTenantRepositoryInterface::class);
$userTenant->method('listTenantIdsByUserId')->willReturn([3, 1, 2, 3, 0]);
$directory = $this->createMock(UserDirectoryGateway::class);
$directory->expects($this->once())
->method('listActiveTenantIdsByIds')
->with([3, 1, 2])
->willReturn([1, 3]);
$directory->expects($this->once())
->method('listTenantsByIds')
->with([1, 3])
->willReturn([
['id' => 3, 'description' => 'Zulu'],
['id' => 1, 'description' => 'Alpha'],
]);
$service = $this->makeService(userTenant: $userTenant, directory: $directory);
$result = $service->getAssignedActiveTenants(42);
$this->assertCount(2, $result);
$this->assertSame(1, $result[0]['id']);
$this->assertSame(3, $result[1]['id']);
}
public function testGetAssignedActiveTenantsReturnsEmptyWhenNoneActive(): void
{
$userTenant = $this->createMock(UserTenantRepositoryInterface::class);
$userTenant->method('listTenantIdsByUserId')->willReturn([1, 2]);
$directory = $this->createMock(UserDirectoryGateway::class);
$directory->method('listActiveTenantIdsByIds')->willReturn([]);
$directory->expects($this->never())->method('listTenantsByIds');
$service = $this->makeService(userTenant: $userTenant, directory: $directory);
$this->assertSame([], $service->getAssignedActiveTenants(42));
}
public function testSetCurrentTenantRejectsInvalidTenantId(): void
{
$service = $this->makeService();
$this->assertSame(['ok' => false, 'error' => 'invalid_tenant'], $service->setCurrentTenant(42, 0));
$this->assertSame(['ok' => false, 'error' => 'invalid_tenant'], $service->setCurrentTenant(42, -1));
}
public function testSetCurrentTenantRejectsUnassignedTenant(): void
{
$userTenant = $this->createMock(UserTenantRepositoryInterface::class);
$userTenant->method('listTenantIdsByUserId')->willReturn([1, 2]);
$service = $this->makeService(userTenant: $userTenant);
$this->assertSame(['ok' => false, 'error' => 'tenant_not_assigned'], $service->setCurrentTenant(42, 999));
}
public function testSetCurrentTenantRejectsMissingTenant(): void
{
$userTenant = $this->createMock(UserTenantRepositoryInterface::class);
$userTenant->method('listTenantIdsByUserId')->willReturn([7]);
$directory = $this->createMock(UserDirectoryGateway::class);
$directory->method('findTenant')->willReturn(null);
$service = $this->makeService(userTenant: $userTenant, directory: $directory);
$this->assertSame(['ok' => false, 'error' => 'tenant_not_found'], $service->setCurrentTenant(42, 7));
}
public function testSetCurrentTenantRejectsInactiveTenant(): void
{
$userTenant = $this->createMock(UserTenantRepositoryInterface::class);
$userTenant->method('listTenantIdsByUserId')->willReturn([7]);
$directory = $this->createMock(UserDirectoryGateway::class);
$directory->method('findTenant')->willReturn(['id' => 7, 'status' => 'inactive']);
$service = $this->makeService(userTenant: $userTenant, directory: $directory);
$this->assertSame(['ok' => false, 'error' => 'tenant_inactive'], $service->setCurrentTenant(42, 7));
}
public function testSetCurrentTenantReturnsUpdateFailedWhenPersistFails(): void
{
$userTenant = $this->createMock(UserTenantRepositoryInterface::class);
$userTenant->method('listTenantIdsByUserId')->willReturn([7]);
$directory = $this->createMock(UserDirectoryGateway::class);
$directory->method('findTenant')->willReturn(['id' => 7, 'status' => 'active']);
$userWrite = $this->createMock(UserWriteRepositoryInterface::class);
$userWrite->method('setCurrentTenant')->willReturn(false);
$service = $this->makeService(userTenant: $userTenant, userWrite: $userWrite, directory: $directory);
$this->assertSame(['ok' => false, 'error' => 'update_failed'], $service->setCurrentTenant(42, 7));
}
public function testSetCurrentTenantPersistsAndReturnsTenantOnSuccess(): void
{
$userTenant = $this->createMock(UserTenantRepositoryInterface::class);
$userTenant->method('listTenantIdsByUserId')->willReturn([7]);
$directory = $this->createMock(UserDirectoryGateway::class);
$tenant = ['id' => 7, 'status' => 'active', 'description' => 'Acme'];
$directory->method('findTenant')->willReturn($tenant);
$userWrite = $this->createMock(UserWriteRepositoryInterface::class);
$userWrite->expects($this->once())
->method('setCurrentTenant')
->with(42, 7)
->willReturn(true);
$service = $this->makeService(userTenant: $userTenant, userWrite: $userWrite, directory: $directory);
$this->assertSame(['ok' => true, 'tenant' => $tenant], $service->setCurrentTenant(42, 7));
}
private function makeService(
?UserReadRepositoryInterface $userRead = null,
?UserWriteRepositoryInterface $userWrite = null,
?UserTenantRepositoryInterface $userTenant = null,
?TenantScopeService $scope = null,
?UserDirectoryGateway $directory = null,
): UserTenantContextService {
return new UserTenantContextService(
$userRead ?? $this->createMock(UserReadRepositoryInterface::class),
$userWrite ?? $this->createMock(UserWriteRepositoryInterface::class),
$userTenant ?? $this->createMock(UserTenantRepositoryInterface::class),
$scope ?? $this->createMock(TenantScopeService::class),
$directory ?? $this->createMock(UserDirectoryGateway::class),
);
}
}