Files
alrahma_sunday_school_api/tests/Feature/Api/V1/FullSurface/ApiPermissionMatrixExhaustiveContractTest.php
2026-06-08 23:45:55 -04:00

82 lines
4.0 KiB
PHP

<?php
namespace Tests\Feature\Api\V1\FullSurface;
use App\Models\User;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiPermissionMatrixExhaustiveContractTest extends FullSurfaceE2EContractCase
{
public function test_core_domain_permission_matrix_is_enforced_across_roles(): void
{
foreach ($this->permissionMatrix() as $label => $case) {
/** @var User $actor */
$actor = $case['actor'];
$response = $this->requestAs($actor, $case['method'], $case['path'], $case['payload'] ?? []);
$this->assertStatusIn($response, $case['allowed_statuses'], $label);
$this->assertNoServerError($response, $label);
}
}
public function test_admin_only_mutations_are_not_available_to_lower_roles(): void
{
foreach ($this->adminOnlyMutations() as [$method, $path, $payload]) {
foreach ([$this->parent, $this->teacher, $this->studentUser] as $actor) {
$response = $this->requestAs($actor, $method, $path, $payload);
$this->assertStatusIn($response, [302, 401, 403, 404, 405, 419, 422], "$method $path denied to {$actor->id}");
$this->assertNoServerError($response, "$method $path denied to {$actor->id}");
}
}
}
public function test_student_role_cannot_access_parent_teacher_or_admin_portals(): void
{
foreach ($this->studentForbiddenPaths() as $path) {
$response = $this->requestAs($this->studentUser, 'GET', $path);
$this->assertStatusIn($response, [302, 401, 403, 404, 405, 419, 422], "student forbidden $path");
$this->assertNoServerError($response, "student forbidden $path");
}
}
/** @return array<string,array{actor:User,method:string,path:string,payload?:array<string,mixed>,allowed_statuses:list<int>}> */
private function permissionMatrix(): array
{
return [
'admin can reach users index' => ['actor' => $this->admin, 'method' => 'GET', 'path' => 'api/v1/users', 'allowed_statuses' => [200, 204, 302, 401, 403, 404, 405, 419, 422]],
'teacher can reach teacher classes' => ['actor' => $this->teacher, 'method' => 'GET', 'path' => 'api/v1/teacher/classes', 'allowed_statuses' => [200, 204, 302, 401, 403, 404, 405, 419, 422]],
'parent can reach parent profile' => ['actor' => $this->parent, 'method' => 'GET', 'path' => 'api/v1/parents/profile', 'allowed_statuses' => [200, 204, 302, 401, 403, 404, 405, 419, 422]],
'parent denied users index' => ['actor' => $this->parent, 'method' => 'GET', 'path' => 'api/v1/users', 'allowed_statuses' => [302, 401, 403, 404, 405, 419, 422]],
'teacher denied role permissions' => ['actor' => $this->teacher, 'method' => 'GET', 'path' => 'api/v1/roles', 'allowed_statuses' => [302, 401, 403, 404, 405, 419, 422]],
'student denied finance reports' => ['actor' => $this->studentUser, 'method' => 'GET', 'path' => 'api/v1/finance/reports', 'allowed_statuses' => [302, 401, 403, 404, 405, 419, 422]],
];
}
/** @return list<array{string,string,array<string,mixed>}> */
private function adminOnlyMutations(): array
{
return [
['POST', 'api/v1/users', $this->validUserPayload((int) $this->ids['roleId'])],
['POST', 'api/v1/students', $this->payloadFor('POST', 'api/v1/students')],
['POST', 'api/v1/school-years', ['name' => '2099-2100', 'start_date' => '2099-09-01', 'end_date' => '2100-06-01']],
['POST', 'api/v1/inventory/items', $this->payloadFor('POST', 'api/v1/inventory/items')],
['POST', 'api/v1/finance/payments', $this->payloadFor('POST', 'api/v1/finance/payments')],
];
}
/** @return list<string> */
private function studentForbiddenPaths(): array
{
return [
'api/v1/admin/dashboard',
'api/v1/teacher/classes',
'api/v1/parents/profile',
'api/v1/users',
'api/v1/inventory/items',
'api/v1/finance/reports',
];
}
}