343 lines
11 KiB
PHP
343 lines
11 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Tests\Service\Org;
|
|
|
|
use MintyPHP\Repository\Org\DepartmentRepositoryInterface;
|
|
use MintyPHP\Repository\Org\UserDepartmentRepositoryInterface;
|
|
use MintyPHP\Service\Audit\SystemAuditService;
|
|
use MintyPHP\Service\Directory\DirectorySettingsGateway;
|
|
use MintyPHP\Service\Org\DepartmentService;
|
|
use MintyPHP\Service\Tenant\TenantScopeService;
|
|
use MintyPHP\Service\User\UserServicesFactory;
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class DepartmentServiceTest extends TestCase
|
|
{
|
|
private UserServicesFactory&MockObject $userServicesFactory;
|
|
private DepartmentRepositoryInterface&MockObject $departmentRepository;
|
|
private DirectorySettingsGateway&MockObject $settingsGateway;
|
|
private TenantScopeService&MockObject $scopeGateway;
|
|
private SystemAuditService&MockObject $systemAuditService;
|
|
private DepartmentService $service;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->userServicesFactory = $this->createMock(UserServicesFactory::class);
|
|
$this->departmentRepository = $this->createMock(DepartmentRepositoryInterface::class);
|
|
$this->settingsGateway = $this->createMock(DirectorySettingsGateway::class);
|
|
$this->scopeGateway = $this->createMock(TenantScopeService::class);
|
|
$this->systemAuditService = $this->createMock(SystemAuditService::class);
|
|
|
|
$this->service = new DepartmentService(
|
|
$this->userServicesFactory,
|
|
$this->departmentRepository,
|
|
$this->settingsGateway,
|
|
$this->scopeGateway,
|
|
$this->systemAuditService
|
|
);
|
|
}
|
|
|
|
public function testListPagedRemovesTenantFilterForGlobalAccessUser(): void
|
|
{
|
|
$this->scopeGateway
|
|
->expects($this->once())
|
|
->method('hasGlobalAccess')
|
|
->with(5)
|
|
->willReturn(true);
|
|
|
|
$this->departmentRepository
|
|
->expects($this->once())
|
|
->method('listPaged')
|
|
->with($this->callback(function (array $options): bool {
|
|
return !isset($options['tenantUserId']) && !isset($options['tenantIds']);
|
|
}))
|
|
->willReturn([['id' => 1]]);
|
|
|
|
$result = $this->service->listPaged([
|
|
'tenantUserId' => 5,
|
|
'tenantIds' => [1, 2],
|
|
'page' => 1,
|
|
]);
|
|
|
|
$this->assertSame([['id' => 1]], $result);
|
|
}
|
|
|
|
public function testListPagedKeepsTenantFilterForNonGlobalUser(): void
|
|
{
|
|
$this->scopeGateway
|
|
->expects($this->once())
|
|
->method('hasGlobalAccess')
|
|
->with(5)
|
|
->willReturn(false);
|
|
|
|
$this->departmentRepository
|
|
->expects($this->once())
|
|
->method('listPaged')
|
|
->with($this->callback(function (array $options): bool {
|
|
return isset($options['tenantUserId']) && $options['tenantUserId'] === 5;
|
|
}))
|
|
->willReturn([]);
|
|
|
|
$this->service->listPaged(['tenantUserId' => 5, 'tenantIds' => [1]]);
|
|
}
|
|
|
|
public function testGroupActiveByTenantIdsGroupsAndSorts(): void
|
|
{
|
|
$this->departmentRepository
|
|
->expects($this->once())
|
|
->method('listByTenantIds')
|
|
->with([1, 2])
|
|
->willReturn([
|
|
['id' => 10, 'tenant_id' => 1, 'description' => 'Zebra'],
|
|
['id' => 11, 'tenant_id' => 1, 'description' => 'Alpha'],
|
|
['id' => 20, 'tenant_id' => 2, 'description' => 'Beta'],
|
|
]);
|
|
|
|
$result = $this->service->groupActiveByTenantIds([1, 2]);
|
|
|
|
$this->assertCount(2, $result);
|
|
$this->assertArrayHasKey(1, $result);
|
|
$this->assertArrayHasKey(2, $result);
|
|
|
|
// Tenant 1 departments sorted by description: Alpha before Zebra
|
|
$this->assertSame('Alpha', $result[1][0]['description']);
|
|
$this->assertSame('Zebra', $result[1][1]['description']);
|
|
|
|
// Tenant 2 has one department
|
|
$this->assertCount(1, $result[2]);
|
|
$this->assertSame('Beta', $result[2][0]['description']);
|
|
}
|
|
|
|
#[DataProvider('groupActiveByTenantIdsInputProvider')]
|
|
public function testGroupActiveByTenantIdsNormalizesInput(array $tenantIds, array $expectedRepositoryIds): void
|
|
{
|
|
$this->departmentRepository
|
|
->expects($expectedRepositoryIds ? $this->once() : $this->never())
|
|
->method('listByTenantIds')
|
|
->with($expectedRepositoryIds)
|
|
->willReturn([]);
|
|
|
|
$this->assertSame([], $this->service->groupActiveByTenantIds($tenantIds));
|
|
}
|
|
|
|
public function testCreateFromAdminSucceeds(): void
|
|
{
|
|
$input = [
|
|
'description' => 'Sales',
|
|
'tenant_id' => 1,
|
|
'code' => '',
|
|
'cost_center' => '',
|
|
'active' => 1,
|
|
];
|
|
|
|
$this->departmentRepository
|
|
->method('existsByCode')
|
|
->willReturn(false);
|
|
|
|
$this->departmentRepository
|
|
->expects($this->once())
|
|
->method('create')
|
|
->willReturn(42);
|
|
|
|
$this->departmentRepository
|
|
->expects($this->once())
|
|
->method('find')
|
|
->with(42)
|
|
->willReturn(['id' => 42, 'uuid' => 'dept-uuid-123', 'description' => 'Sales']);
|
|
|
|
$this->systemAuditService
|
|
->expects($this->once())
|
|
->method('record')
|
|
->with('admin.departments.create', 'success', $this->anything());
|
|
|
|
$result = $this->service->createFromAdmin($input, 7);
|
|
|
|
$this->assertTrue($result['ok']);
|
|
$this->assertSame(42, $result['id']);
|
|
$this->assertSame('dept-uuid-123', $result['uuid']);
|
|
}
|
|
|
|
public function testCreateFromAdminSetsDefaultDepartmentWhenRequested(): void
|
|
{
|
|
$input = [
|
|
'description' => 'Default Dept',
|
|
'tenant_id' => 1,
|
|
'code' => '',
|
|
'cost_center' => '',
|
|
'active' => 1,
|
|
'is_default' => true,
|
|
];
|
|
|
|
$this->departmentRepository->method('existsByCode')->willReturn(false);
|
|
$this->departmentRepository->method('create')->willReturn(10);
|
|
$this->departmentRepository->method('find')->willReturn(['id' => 10, 'uuid' => 'u1']);
|
|
|
|
$this->settingsGateway
|
|
->expects($this->once())
|
|
->method('setDefaultDepartmentId')
|
|
->with(10);
|
|
|
|
$result = $this->service->createFromAdmin($input, 1);
|
|
|
|
$this->assertTrue($result['ok']);
|
|
}
|
|
|
|
#[DataProvider('invalidDepartmentInputProvider')]
|
|
public function testCreateFromAdminRejectsInvalidInput(array $input): void
|
|
{
|
|
$this->departmentRepository
|
|
->expects($this->never())
|
|
->method('create');
|
|
|
|
$result = $this->service->createFromAdmin($input);
|
|
|
|
$this->assertFalse($result['ok']);
|
|
$this->assertNotEmpty($result['errors']);
|
|
}
|
|
|
|
public function testDeleteByUuidSucceeds(): void
|
|
{
|
|
$department = ['id' => 5, 'uuid' => 'abc-123', 'description' => 'HR'];
|
|
|
|
$this->departmentRepository
|
|
->expects($this->once())
|
|
->method('findByUuid')
|
|
->with('abc-123')
|
|
->willReturn($department);
|
|
|
|
$this->departmentRepository
|
|
->expects($this->once())
|
|
->method('delete')
|
|
->with(5)
|
|
->willReturn(true);
|
|
|
|
$this->systemAuditService
|
|
->expects($this->once())
|
|
->method('record')
|
|
->with('admin.departments.delete', 'success', $this->anything());
|
|
|
|
$result = $this->service->deleteByUuid('abc-123');
|
|
|
|
$this->assertTrue($result['ok']);
|
|
$this->assertSame($department, $result['department']);
|
|
}
|
|
|
|
public function testDeleteByUuidReturnsNotFoundForMissing(): void
|
|
{
|
|
$this->departmentRepository
|
|
->expects($this->once())
|
|
->method('findByUuid')
|
|
->with('nonexistent')
|
|
->willReturn(null);
|
|
|
|
$this->departmentRepository
|
|
->expects($this->never())
|
|
->method('delete');
|
|
|
|
$result = $this->service->deleteByUuid('nonexistent');
|
|
|
|
$this->assertFalse($result['ok']);
|
|
$this->assertSame(404, $result['status']);
|
|
$this->assertSame('not_found', $result['error']);
|
|
}
|
|
|
|
public function testDeleteByUuidReturnsNotFoundForEmptyUuid(): void
|
|
{
|
|
$this->departmentRepository
|
|
->expects($this->never())
|
|
->method('findByUuid');
|
|
|
|
$result = $this->service->deleteByUuid(' ');
|
|
|
|
$this->assertFalse($result['ok']);
|
|
$this->assertSame(404, $result['status']);
|
|
}
|
|
|
|
public function testDeleteByUuidReturnsDeleteFailedOnRepositoryFailure(): void
|
|
{
|
|
$this->departmentRepository
|
|
->expects($this->once())
|
|
->method('findByUuid')
|
|
->with('abc-999')
|
|
->willReturn(['id' => 9, 'uuid' => 'abc-999', 'description' => 'Ops']);
|
|
|
|
$this->departmentRepository
|
|
->expects($this->once())
|
|
->method('delete')
|
|
->with(9)
|
|
->willReturn(false);
|
|
|
|
$this->systemAuditService
|
|
->expects($this->never())
|
|
->method('record');
|
|
|
|
$result = $this->service->deleteByUuid('abc-999');
|
|
|
|
$this->assertFalse($result['ok']);
|
|
$this->assertSame(500, $result['status']);
|
|
$this->assertSame('delete_failed', $result['error']);
|
|
}
|
|
|
|
public function testSyncTenantsUsesFirstId(): void
|
|
{
|
|
$this->departmentRepository
|
|
->expects($this->once())
|
|
->method('setTenant')
|
|
->with(10, 3)
|
|
->willReturn(true);
|
|
|
|
$userDeptRepo = $this->createMock(UserDepartmentRepositoryInterface::class);
|
|
$userDeptRepo->expects($this->any())->method('removeInvalidForDepartment')->with(10)->willReturn(2);
|
|
|
|
$this->userServicesFactory
|
|
->expects($this->once())
|
|
->method('createUserDepartmentRepository')
|
|
->willReturn($userDeptRepo);
|
|
|
|
$result = $this->service->syncTenants(10, [3, 5, 7]);
|
|
|
|
$this->assertSame(2, $result);
|
|
}
|
|
|
|
public function testSyncTenantsRejectsEmptyArray(): void
|
|
{
|
|
$this->departmentRepository
|
|
->expects($this->never())
|
|
->method('setTenant');
|
|
|
|
$result = $this->service->syncTenants(10, []);
|
|
|
|
$this->assertSame(-1, $result);
|
|
}
|
|
|
|
public static function groupActiveByTenantIdsInputProvider(): array
|
|
{
|
|
return [
|
|
'invalid ids are ignored completely' => [[0, -1, -5], []],
|
|
'duplicate ids are reduced before querying' => [[1, 1, 1], [1]],
|
|
];
|
|
}
|
|
|
|
public static function invalidDepartmentInputProvider(): array
|
|
{
|
|
return [
|
|
'missing description' => [[
|
|
'description' => '',
|
|
'tenant_id' => 1,
|
|
'code' => '',
|
|
'cost_center' => '',
|
|
'active' => 1,
|
|
]],
|
|
'missing tenant id' => [[
|
|
'description' => 'Sales',
|
|
'tenant_id' => 0,
|
|
'code' => '',
|
|
'cost_center' => '',
|
|
'active' => 1,
|
|
]],
|
|
];
|
|
}
|
|
}
|