refactor: align addressbook module to MintyPHP\Module\AddressBook namespace
Migrates addressbook service classes from core namespace (MintyPHP\Service\AddressBook\*) to proper module namespace (MintyPHP\Module\AddressBook\Service\*), consistent with the bookmarks module pattern. Moved to module: - AddressBookService → modules/addressbook/lib/Module/AddressBook/Service/ - AddressBookServicesFactory → modules/addressbook/lib/Module/AddressBook/Service/ - AddressBookServiceTest → modules/addressbook/tests/Module/AddressBook/Service/ - Pages, templates, CSS, JS all in module directory All module PHP classes now live under MintyPHP\Module\<Name>\*, enforced by ModuleStructureContractTest::testModuleClassesUseModuleNamespace. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,9 +14,7 @@ use FilesystemIterator;
|
||||
* Intentionally allowed in Core (not flagged):
|
||||
* - PermissionService::ADDRESS_BOOK_VIEW (general people-visibility gate)
|
||||
* - OperationsAuthorizationPolicy / UiCapabilityMap / UserAuthorizationPolicy (authorization)
|
||||
* - AddressBookService / AddressBookServicesFactory (business logic, V1 shared-db)
|
||||
* - pages/address-book/* (page files, not hardcoded integrations)
|
||||
* - config/assets.php (asset pipeline not yet modular)
|
||||
* - modules/addressbook/* (module-owned runtime, pages/assets/providers)
|
||||
* - i18n translation files
|
||||
* - Module directory (lib/Module/, modules/)
|
||||
* - Test files
|
||||
@@ -90,10 +88,7 @@ final class NoAddressBookHardcodingTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Aside panel template must not have hardcoded People panel.
|
||||
*
|
||||
* Note: The search panel legitimately contains data-search-key="address-book"
|
||||
* for the global search results — that is NOT a hardcoded People panel.
|
||||
* Aside panel template must not have hardcoded People panel/search entries.
|
||||
*/
|
||||
public function testAsideHasNoHardcodedPeoplePanel(): void
|
||||
{
|
||||
@@ -116,6 +111,20 @@ final class NoAddressBookHardcodingTest extends TestCase
|
||||
$content,
|
||||
'Aside still defines $peopleGroups as a standalone variable'
|
||||
);
|
||||
self::assertStringNotContainsString(
|
||||
'data-search-key="address-book"',
|
||||
$content,
|
||||
'Aside still has static address-book search item markup'
|
||||
);
|
||||
}
|
||||
|
||||
public function testCoreNoLongerContainsAddressBookServiceDirectory(): void
|
||||
{
|
||||
$coreServiceDir = dirname(__DIR__, 2) . '/lib/Service/AddressBook';
|
||||
self::assertDirectoryDoesNotExist(
|
||||
$coreServiceDir,
|
||||
'Addressbook service classes should live in modules/addressbook/lib, not in Core lib/Service'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Tests\Service\AddressBook;
|
||||
|
||||
use MintyPHP\Service\Access\RoleService;
|
||||
use MintyPHP\Service\AddressBook\AddressBookService;
|
||||
use MintyPHP\Service\CustomField\UserCustomFieldValueService;
|
||||
use MintyPHP\Service\Org\DepartmentService;
|
||||
use MintyPHP\Service\Tenant\TenantScopeService;
|
||||
use MintyPHP\Service\Tenant\TenantService;
|
||||
use MintyPHP\Service\User\UserAccountService;
|
||||
use MintyPHP\Service\User\UserAssignmentService;
|
||||
use MintyPHP\Service\User\UserAvatarService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class AddressBookServiceTest extends TestCase
|
||||
{
|
||||
public function testBuildIndexContextBuildsTenantDepartmentMapForGlobalScope(): void
|
||||
{
|
||||
$userAccountService = $this->createMock(UserAccountService::class);
|
||||
$userAssignmentService = $this->createMock(UserAssignmentService::class);
|
||||
$tenantService = $this->createMock(TenantService::class);
|
||||
$departmentService = $this->createMock(DepartmentService::class);
|
||||
$roleService = $this->createMock(RoleService::class);
|
||||
$scopeGateway = $this->createMock(TenantScopeService::class);
|
||||
$customFieldGateway = $this->createMock(UserCustomFieldValueService::class);
|
||||
$avatarService = $this->createMock(UserAvatarService::class);
|
||||
|
||||
$scopeGateway
|
||||
->expects($this->once())
|
||||
->method('getUserTenantIds')
|
||||
->with(7)
|
||||
->willReturn([]);
|
||||
$tenantService
|
||||
->expects($this->once())
|
||||
->method('list')
|
||||
->willReturn([
|
||||
['id' => 1, 'uuid' => 'tenant-a', 'description' => 'Tenant A'],
|
||||
['id' => 2, 'uuid' => 'tenant-b', 'description' => 'Tenant B'],
|
||||
]);
|
||||
$scopeGateway
|
||||
->expects($this->exactly(2))
|
||||
->method('isStrict')
|
||||
->willReturn(false);
|
||||
$departmentService
|
||||
->expects($this->once())
|
||||
->method('list')
|
||||
->willReturn([
|
||||
['id' => 10, 'tenant_id' => 1, 'description' => 'Sales'],
|
||||
['id' => 20, 'tenant_id' => 2, 'description' => 'Support'],
|
||||
]);
|
||||
$departmentService
|
||||
->expects($this->never())
|
||||
->method('listByTenantIds');
|
||||
$roleService
|
||||
->expects($this->once())
|
||||
->method('listActive')
|
||||
->willReturn([]);
|
||||
|
||||
$customFieldGateway
|
||||
->expects($this->once())
|
||||
->method('extractAddressBookFilterSpec')
|
||||
->with([], 7)
|
||||
->willReturn([
|
||||
'definitions' => [],
|
||||
'query' => [],
|
||||
]);
|
||||
$service = new AddressBookService(
|
||||
$userAccountService,
|
||||
$userAssignmentService,
|
||||
$tenantService,
|
||||
$departmentService,
|
||||
$roleService,
|
||||
$scopeGateway,
|
||||
$customFieldGateway,
|
||||
$avatarService
|
||||
);
|
||||
|
||||
$context = $service->buildIndexContext(7, []);
|
||||
|
||||
$this->assertSame(
|
||||
[
|
||||
'tenant-a' => ['10'],
|
||||
'tenant-b' => ['20'],
|
||||
],
|
||||
$context['tenantDepartmentMap']
|
||||
);
|
||||
}
|
||||
}
|
||||
87
tests/Unit/Module/AddressBookAuthorizationPolicyTest.php
Normal file
87
tests/Unit/Module/AddressBookAuthorizationPolicyTest.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Tests\Unit\Module;
|
||||
|
||||
use MintyPHP\Module\AddressBook\AddressBookAuthorizationPolicy;
|
||||
use MintyPHP\Service\Access\AuthorizationPolicyInterface;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
use MintyPHP\Tests\Service\Access\AuthorizationPolicyTestSupport;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \MintyPHP\Module\AddressBook\AddressBookAuthorizationPolicy
|
||||
*/
|
||||
final class AddressBookAuthorizationPolicyTest extends TestCase
|
||||
{
|
||||
use AuthorizationPolicyTestSupport;
|
||||
|
||||
public function testImplementsInterface(): void
|
||||
{
|
||||
$policy = new AddressBookAuthorizationPolicy($this->createMock(PermissionService::class));
|
||||
self::assertInstanceOf(AuthorizationPolicyInterface::class, $policy);
|
||||
}
|
||||
|
||||
public function testSupportsOwnAbility(): void
|
||||
{
|
||||
$policy = new AddressBookAuthorizationPolicy($this->createMock(PermissionService::class));
|
||||
self::assertTrue($policy->supports(AddressBookAuthorizationPolicy::ABILITY_VIEW));
|
||||
}
|
||||
|
||||
public function testDoesNotSupportOtherAbilities(): void
|
||||
{
|
||||
$policy = new AddressBookAuthorizationPolicy($this->createMock(PermissionService::class));
|
||||
self::assertFalse($policy->supports('ops.admin.jobs.view'));
|
||||
self::assertFalse($policy->supports(''));
|
||||
self::assertFalse($policy->supports('ops.address_book.view')); // old ability string
|
||||
}
|
||||
|
||||
public function testAllowedWithPermission(): void
|
||||
{
|
||||
$permissionService = $this->permissionGatewayAllowing([
|
||||
5 => [PermissionService::ADDRESS_BOOK_VIEW],
|
||||
]);
|
||||
|
||||
$policy = new AddressBookAuthorizationPolicy($permissionService);
|
||||
$decision = $policy->authorize(AddressBookAuthorizationPolicy::ABILITY_VIEW, [
|
||||
'actor_user_id' => 5,
|
||||
]);
|
||||
|
||||
self::assertTrue($decision->isAllowed());
|
||||
}
|
||||
|
||||
public function testDeniedWithoutPermission(): void
|
||||
{
|
||||
$permissionService = $this->permissionGatewayAllowing([5 => []]);
|
||||
|
||||
$policy = new AddressBookAuthorizationPolicy($permissionService);
|
||||
$decision = $policy->authorize(AddressBookAuthorizationPolicy::ABILITY_VIEW, [
|
||||
'actor_user_id' => 5,
|
||||
]);
|
||||
|
||||
$this->assertForbiddenDecision($decision);
|
||||
}
|
||||
|
||||
public function testDeniedWithZeroActorId(): void
|
||||
{
|
||||
$permissionService = $this->createMock(PermissionService::class);
|
||||
$permissionService->expects($this->never())->method('userHas');
|
||||
|
||||
$policy = new AddressBookAuthorizationPolicy($permissionService);
|
||||
$decision = $policy->authorize(AddressBookAuthorizationPolicy::ABILITY_VIEW, [
|
||||
'actor_user_id' => 0,
|
||||
]);
|
||||
|
||||
$this->assertForbiddenDecision($decision);
|
||||
}
|
||||
|
||||
public function testDeniedWithMissingActorId(): void
|
||||
{
|
||||
$permissionService = $this->createMock(PermissionService::class);
|
||||
$permissionService->expects($this->never())->method('userHas');
|
||||
|
||||
$policy = new AddressBookAuthorizationPolicy($permissionService);
|
||||
$decision = $policy->authorize(AddressBookAuthorizationPolicy::ABILITY_VIEW, []);
|
||||
|
||||
$this->assertForbiddenDecision($decision);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user