Files
breadcrumb-the-shire/tests/Architecture/AuthzAdminMasterDataContractTest.php

83 lines
5.3 KiB
PHP
Raw Permalink Normal View History

2026-03-19 18:25:19 +01:00
<?php
namespace MintyPHP\Tests\Architecture;
use PHPUnit\Framework\TestCase;
class AuthzAdminMasterDataContractTest extends TestCase
{
use ProjectFileAssertionSupport;
public function testAdminDepartmentsEditAndDeleteUseCentralPolicies(): void
{
$indexContent = $this->readProjectFile('pages/admin/departments/index().php');
$this->assertStringContainsString('AuthorizationService::class', $indexContent);
$this->assertStringContainsString('DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_VIEW', $indexContent);
$dataContent = $this->readProjectFile('pages/admin/departments/data().php');
$this->assertStringContainsString('Guard::requireAbilityDecisionOrForbidden(DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_VIEW);', $dataContent);
$this->assertStringContainsString('DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_VIEW', $dataContent);
$createContent = $this->readProjectFile('pages/admin/departments/create().php');
refactor(departments-create): migrate to actionCreateContext (step 9) First production use of actionCreateContext — the second aggregator introduced in step 1 and unit-tested at the building-block level, but not yet exercised against a real caller. Helper file stays 0-diff for the sixth consecutive migration. The migration uncovers one real API gap and resolves it at the policy layer rather than at the helper: * actionCreateContext always calls actionEnforceCanViewPage. The Departments create-decision was the only Departments authorize branch that did not emit can_view_page (View and EditContext both did). Adding 'can_view_page' => true to the create-capabilities map is tautological — every actor that survives the deny() guards at lines 69-70 and 75-76 can by definition see the page. No new forbidden path is created. View, Create, and EditContext now share the same capability shape. Three drift decisions reproduced where applicable: * notFoundFlashScopeKey is N/A (no model lookup in create flow). * t() consistency: all three Flash::success('Department created', …) calls now flow through t(). * Defensive scope consumption: $canManageAllTenants reads $tenantScope['scope'] === 'all', mirroring the edit-action pattern. The GET tenant filter rewrites from is_array($allowedTenantIds) to the three-way scope-tuple form. AuthzAdminMasterDataContractTest gets a single-line assertion update (AuthorizationService::class → actionCreateContext() pattern). The aggregator wraps the same authorize call internally, so this is a pattern-rename, not a semantic shift. ActionContextCsrfPairingContractTest now covers six callers (five edits + departments-create) and stays green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 14:44:09 +02:00
$this->assertStringContainsString('actionCreateContext(', $createContent);
2026-03-19 18:25:19 +01:00
$this->assertStringContainsString('DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_CREATE', $createContent);
$editContent = $this->readProjectFile('pages/admin/departments/edit($id).php');
$this->assertStringContainsString('AuthorizationService::class', $editContent);
$this->assertStringContainsString('DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_EDIT_CONTEXT', $editContent);
$this->assertStringContainsString('DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_EDIT_SUBMIT', $editContent);
$deleteContent = $this->readProjectFile('pages/admin/departments/delete($id).php');
$this->assertStringContainsString('AuthorizationService::class', $deleteContent);
$this->assertStringContainsString('DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_DELETE', $deleteContent);
}
public function testAdminRolesActionsUseCentralPolicies(): void
{
$indexContent = $this->readProjectFile('pages/admin/roles/index().php');
$this->assertStringContainsString('AuthorizationService::class', $indexContent);
$this->assertStringContainsString('RoleAuthorizationPolicy::ABILITY_ADMIN_ROLES_VIEW', $indexContent);
$dataContent = $this->readProjectFile('pages/admin/roles/data().php');
$this->assertStringContainsString('Guard::requireAbilityOrForbidden(RoleAuthorizationPolicy::ABILITY_ADMIN_ROLES_VIEW);', $dataContent);
$this->assertStringContainsString('RoleAuthorizationPolicy::ABILITY_ADMIN_ROLES_VIEW', $dataContent);
$createContent = $this->readProjectFile('pages/admin/roles/create().php');
refactor(creates-batch): migrate roles/permissions/tenants-create (step 10) Cluster-7 batch-replay of the departments-create pilot (step 9). All three remaining create actions follow the same shape with minor domain-specific variations. Each migration touches one action and one policy: * roles-create + RoleAuthorizationPolicy::authorizeAdminRolesCreate — policy previously returned bare allow() with no capabilities; now emits ['can_view_page' => true]. Action passes viewAuthFlags: []. * permissions-create + PermissionAuthorizationPolicy::authorizeAdminPermissionsCreate — same pattern as roles-create. * tenants-create + TenantAuthorizationPolicy::authorizeAdminTenantsCreate — policy already emitted can_manage_sso + can_manage_custom_fields; can_view_page is added as the first capability. Action passes viewAuthFlags: ['can_manage_sso', 'can_manage_custom_fields'] and materializes both booleans from the aggregator capabilities. All three policy updates are tautological — every actor that survives the deny() branches in each policy can by definition see the page. View, Create, and EditContext now share a consistent capability shape across all four core master-data domains (departments, roles, permissions, tenants). Three drift decisions reproduced: * notFoundFlashScopeKey is N/A (no model lookup in create flows). * t() consistency: Flash::success('Role created' / 'Permission created' / 'Tenant created') now flow through t(). * Defensive scope consumption: $canManageAllTenants reads $tenantScope['scope'] === 'all' as a resilient hook even where the policy emits no manage-all flag (roles/permissions are global, tenants-create has no filter logic). Inline comments document the intentional non-consumption of $tenantScope['ids']. Two contract-test pattern updates (AuthzAdminMasterDataContractTest + AuthzAdminTenantsContractTest) shift the assertion targets from AuthorizationService::class to actionCreateContext( — semantically equivalent because the aggregator wraps the same authorize call internally. ActionContextCsrfPairingContractTest now covers nine callers (five edits + four creates) and stays green. Helper file core/Support/helpers/action_context.php is 0-diff for the seventh consecutive migration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 15:00:05 +02:00
$this->assertStringContainsString('actionCreateContext(', $createContent);
2026-03-19 18:25:19 +01:00
$this->assertStringContainsString('RoleAuthorizationPolicy::ABILITY_ADMIN_ROLES_CREATE', $createContent);
$editContent = $this->readProjectFile('pages/admin/roles/edit($id).php');
$this->assertStringContainsString('AuthorizationService::class', $editContent);
$this->assertStringContainsString('RoleAuthorizationPolicy::ABILITY_ADMIN_ROLES_EDIT_CONTEXT', $editContent);
$this->assertStringContainsString('RoleAuthorizationPolicy::ABILITY_ADMIN_ROLES_EDIT_SUBMIT', $editContent);
$deleteContent = $this->readProjectFile('pages/admin/roles/delete($id).php');
$this->assertStringContainsString('AuthorizationService::class', $deleteContent);
$this->assertStringContainsString('RoleAuthorizationPolicy::ABILITY_ADMIN_ROLES_DELETE', $deleteContent);
}
public function testAdminPermissionsActionsUseCentralPolicies(): void
{
$indexContent = $this->readProjectFile('pages/admin/permissions/index().php');
$this->assertStringContainsString('AuthorizationService::class', $indexContent);
$this->assertStringContainsString('PermissionAuthorizationPolicy::ABILITY_ADMIN_PERMISSIONS_VIEW', $indexContent);
$dataContent = $this->readProjectFile('pages/admin/permissions/data().php');
$this->assertStringContainsString('Guard::requireAbilityOrForbidden(PermissionAuthorizationPolicy::ABILITY_ADMIN_PERMISSIONS_VIEW);', $dataContent);
$this->assertStringContainsString('PermissionAuthorizationPolicy::ABILITY_ADMIN_PERMISSIONS_VIEW', $dataContent);
$createContent = $this->readProjectFile('pages/admin/permissions/create().php');
refactor(creates-batch): migrate roles/permissions/tenants-create (step 10) Cluster-7 batch-replay of the departments-create pilot (step 9). All three remaining create actions follow the same shape with minor domain-specific variations. Each migration touches one action and one policy: * roles-create + RoleAuthorizationPolicy::authorizeAdminRolesCreate — policy previously returned bare allow() with no capabilities; now emits ['can_view_page' => true]. Action passes viewAuthFlags: []. * permissions-create + PermissionAuthorizationPolicy::authorizeAdminPermissionsCreate — same pattern as roles-create. * tenants-create + TenantAuthorizationPolicy::authorizeAdminTenantsCreate — policy already emitted can_manage_sso + can_manage_custom_fields; can_view_page is added as the first capability. Action passes viewAuthFlags: ['can_manage_sso', 'can_manage_custom_fields'] and materializes both booleans from the aggregator capabilities. All three policy updates are tautological — every actor that survives the deny() branches in each policy can by definition see the page. View, Create, and EditContext now share a consistent capability shape across all four core master-data domains (departments, roles, permissions, tenants). Three drift decisions reproduced: * notFoundFlashScopeKey is N/A (no model lookup in create flows). * t() consistency: Flash::success('Role created' / 'Permission created' / 'Tenant created') now flow through t(). * Defensive scope consumption: $canManageAllTenants reads $tenantScope['scope'] === 'all' as a resilient hook even where the policy emits no manage-all flag (roles/permissions are global, tenants-create has no filter logic). Inline comments document the intentional non-consumption of $tenantScope['ids']. Two contract-test pattern updates (AuthzAdminMasterDataContractTest + AuthzAdminTenantsContractTest) shift the assertion targets from AuthorizationService::class to actionCreateContext( — semantically equivalent because the aggregator wraps the same authorize call internally. ActionContextCsrfPairingContractTest now covers nine callers (five edits + four creates) and stays green. Helper file core/Support/helpers/action_context.php is 0-diff for the seventh consecutive migration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 15:00:05 +02:00
$this->assertStringContainsString('actionCreateContext(', $createContent);
2026-03-19 18:25:19 +01:00
$this->assertStringContainsString('PermissionAuthorizationPolicy::ABILITY_ADMIN_PERMISSIONS_CREATE', $createContent);
$editContent = $this->readProjectFile('pages/admin/permissions/edit($id).php');
$this->assertStringContainsString('AuthorizationService::class', $editContent);
$this->assertStringContainsString('PermissionAuthorizationPolicy::ABILITY_ADMIN_PERMISSIONS_EDIT_CONTEXT', $editContent);
$this->assertStringContainsString('PermissionAuthorizationPolicy::ABILITY_ADMIN_PERMISSIONS_EDIT_SUBMIT', $editContent);
$deleteContent = $this->readProjectFile('pages/admin/permissions/delete($id).php');
$this->assertStringContainsString('AuthorizationService::class', $deleteContent);
$this->assertStringContainsString('PermissionAuthorizationPolicy::ABILITY_ADMIN_PERMISSIONS_DELETE', $deleteContent);
}
}