82 lines
4.0 KiB
PHP
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',
|
|
];
|
|
}
|
|
}
|