add tests batch 20

This commit is contained in:
root
2026-06-09 01:03:53 -04:00
parent 95efb9652e
commit 6be4875c5e
1502 changed files with 13797 additions and 11313 deletions
@@ -12,9 +12,9 @@ use Tests\TestCase;
*/
class ApiAcademicAttendanceFullSurfaceContractTest extends TestCase
{
use AssertsE2EApiResponses;
use RefreshDatabase;
use SeedsE2ETestFixtures;
use AssertsE2EApiResponses;
public function test_academic_attendance_and_grading_surfaces_have_controlled_contracts(): void
{
@@ -24,21 +24,21 @@ class ApiAcademicAttendanceFullSurfaceContractTest extends TestCase
foreach ($this->teacherGetEndpoints($world) as $uri) {
$response = $this->getJson($uri);
$this->assertNoServerError($response, 'Teacher GET '.$uri);
$this->assertStatusIn($response, [200, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], 'Teacher GET '.$uri);
$this->assertNoServerError($response, 'Teacher GET ' . $uri);
$this->assertStatusIn($response, [200, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], 'Teacher GET ' . $uri);
}
foreach ($this->teacherMutationEndpoints($world) as [$method, $uri, $payload]) {
$response = $this->json($method, $uri, $payload);
$this->assertNoServerError($response, 'Teacher '.$method.' '.$uri);
$this->assertStatusIn($response, [200, 201, 202, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], 'Teacher '.$method.' '.$uri);
$this->assertNoServerError($response, 'Teacher ' . $method . ' ' . $uri);
$this->assertStatusIn($response, [200, 201, 202, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], 'Teacher ' . $method . ' ' . $uri);
}
$this->actingAsApiAdministrator();
foreach ($this->adminAcademicEndpoints($world) as [$method, $uri, $payload]) {
$response = $this->json($method, $uri, $payload);
$this->assertNoServerError($response, 'Admin academic '.$method.' '.$uri);
$this->assertStatusIn($response, [200, 201, 202, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], 'Admin academic '.$method.' '.$uri);
$this->assertNoServerError($response, 'Admin academic ' . $method . ' ' . $uri);
$this->assertStatusIn($response, [200, 201, 202, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], 'Admin academic ' . $method . ' ' . $uri);
}
}
@@ -49,35 +49,35 @@ class ApiAcademicAttendanceFullSurfaceContractTest extends TestCase
$studentId = (int) $world['student_id'];
return [
'/api/v1/teachers/classes?class_section_id='.$classSectionId,
'/api/v1/teacher/class-view?class_section_id='.$classSectionId,
'/api/v1/teachers/classes?class_section_id=' . $classSectionId,
'/api/v1/teacher/class-view?class_section_id=' . $classSectionId,
'/api/v1/teacher/no-classes',
'/api/v1/teacher/select-semester',
'/api/v1/teacher/homework-list?class_section_id='.$classSectionId,
'/api/v1/teacher/add-homework?class_section_id='.$classSectionId,
'/api/v1/teacher/add-quiz?class_section_id='.$classSectionId,
'/api/v1/teacher/add-quiz-column-form?class_section_id='.$classSectionId,
'/api/v1/teacher/add-midterm-exam?class_section_id='.$classSectionId,
'/api/v1/teacher/add-final-exam?class_section_id='.$classSectionId,
'/api/v1/teacher/add-participation?class_section_id='.$classSectionId,
'/api/v1/teacher/add-project?class_section_id='.$classSectionId,
'/api/v1/teacher/homework-list?class_section_id=' . $classSectionId,
'/api/v1/teacher/add-homework?class_section_id=' . $classSectionId,
'/api/v1/teacher/add-quiz?class_section_id=' . $classSectionId,
'/api/v1/teacher/add-quiz-column-form?class_section_id=' . $classSectionId,
'/api/v1/teacher/add-midterm-exam?class_section_id=' . $classSectionId,
'/api/v1/teacher/add-final-exam?class_section_id=' . $classSectionId,
'/api/v1/teacher/add-participation?class_section_id=' . $classSectionId,
'/api/v1/teacher/add-project?class_section_id=' . $classSectionId,
'/api/v1/teacher/teacher-assignment',
'/api/v1/teacher/exam-drafts',
'/api/v1/teacher/drafts',
'/api/v1/teacher/inventory/books/distribute',
'/api/v1/teacher/print-requests/context',
'/api/v1/teacher/absence-vacation',
'/api/v1/teacher/showupdate-attendance?class_section_id='.$classSectionId,
'/api/v1/teacher/showupdate_attendance?class_section_id='.$classSectionId,
'/api/v1/teacher/scores?class_section_id='.$classSectionId,
'/api/v1/teacher/showupdate-attendance?class_section_id=' . $classSectionId,
'/api/v1/teacher/showupdate_attendance?class_section_id=' . $classSectionId,
'/api/v1/teacher/scores?class_section_id=' . $classSectionId,
'/api/v1/teacher/calendar',
'/api/v1/teacher/class-progress-submit?class_section_id='.$classSectionId,
'/api/v1/teacher/class-progress-history?class_section_id='.$classSectionId,
'/api/v1/teacher/class-progress-view?class_section_id='.$classSectionId,
'/api/v1/teacher/class-progress-submit?class_section_id=' . $classSectionId,
'/api/v1/teacher/class-progress-history?class_section_id=' . $classSectionId,
'/api/v1/teacher/class-progress-view?class_section_id=' . $classSectionId,
'/api/v1/teacher/competition-scores',
'/api/v1/attendance/teacher/grid?class_section_id='.$classSectionId,
'/api/v1/attendance/teacher/form?class_section_id='.$classSectionId,
'/api/v1/attendance/admin/daily?date=2025-10-05&class_section_id='.$classSectionId,
'/api/v1/attendance/teacher/grid?class_section_id=' . $classSectionId,
'/api/v1/attendance/teacher/form?class_section_id=' . $classSectionId,
'/api/v1/attendance/admin/daily?date=2025-10-05&class_section_id=' . $classSectionId,
'/api/v1/attendance/staff/month?month=2025-10',
'/api/v1/attendance/staff/admins',
'/api/v1/attendance/late-slip-logs',
@@ -87,7 +87,7 @@ class ApiAcademicAttendanceFullSurfaceContractTest extends TestCase
'/api/v1/attendance/management/dashboard',
'/api/v1/attendance-tracking/pending-violations',
'/api/v1/attendance-tracking/notified-violations',
'/api/v1/attendance-tracking/student-case/'.$studentId,
'/api/v1/attendance-tracking/student-case/' . $studentId,
'/api/v1/attendance-tracking/compose',
'/api/v1/attendance-tracking/parents-info',
'/api/v1/attendance-comment-templates',
@@ -96,15 +96,15 @@ class ApiAcademicAttendanceFullSurfaceContractTest extends TestCase
'/api/v1/attendance-templates',
'/api/v1/assignments',
'/api/v1/assignments/class-assignment-data',
'/api/v1/scores/overview?class_section_id='.$classSectionId,
'/api/v1/scores/homework?class_section_id='.$classSectionId,
'/api/v1/scores/quiz?class_section_id='.$classSectionId,
'/api/v1/scores/project?class_section_id='.$classSectionId,
'/api/v1/scores/midterm?class_section_id='.$classSectionId,
'/api/v1/scores/final?class_section_id='.$classSectionId,
'/api/v1/scores/participation?class_section_id='.$classSectionId,
'/api/v1/grading/homework-tracking?class_section_id='.$classSectionId,
'/api/v1/reports/report-cards/meta?class_section_id='.$classSectionId,
'/api/v1/scores/overview?class_section_id=' . $classSectionId,
'/api/v1/scores/homework?class_section_id=' . $classSectionId,
'/api/v1/scores/quiz?class_section_id=' . $classSectionId,
'/api/v1/scores/project?class_section_id=' . $classSectionId,
'/api/v1/scores/midterm?class_section_id=' . $classSectionId,
'/api/v1/scores/final?class_section_id=' . $classSectionId,
'/api/v1/scores/participation?class_section_id=' . $classSectionId,
'/api/v1/grading/homework-tracking?class_section_id=' . $classSectionId,
'/api/v1/reports/report-cards/meta?class_section_id=' . $classSectionId,
];
}
@@ -159,8 +159,8 @@ class ApiAcademicAttendanceFullSurfaceContractTest extends TestCase
private function adminAcademicEndpoints(array $world): array
{
return [
['GET', '/api/v1/reports/report-cards?student_id='.$world['student_id'], []],
['GET', '/api/v1/reports/stickers?student_id='.$world['student_id'], []],
['GET', '/api/v1/reports/report-cards?student_id=' . $world['student_id'], []],
['GET', '/api/v1/reports/stickers?student_id=' . $world['student_id'], []],
['GET', '/api/v1/badges/form-options', []],
['POST', '/api/v1/print-requests', ['title' => 'Contract Probe', 'copies' => 1, 'needed_by' => '2025-10-12']],
['GET', '/api/v1/print-requests/admin', []],
@@ -24,7 +24,7 @@ class ApiAccountStatusLifecycleContractTest extends FullSurfaceE2EContractCase
$response = $this->requestAs($actor, 'GET', $uri);
$this->assertControlled($response, 'GET', $uri);
$this->assertNoServerError($response, 'account status '.$status.' at GET '.$uri);
$this->assertNoServerError($response, 'account status ' . $status . ' at GET ' . $uri);
}
}
}
@@ -12,9 +12,9 @@ use Tests\TestCase;
*/
class ApiAdminOperationsFullSurfaceContractTest extends TestCase
{
use AssertsE2EApiResponses;
use RefreshDatabase;
use SeedsE2ETestFixtures;
use AssertsE2EApiResponses;
public function test_administrator_operational_surface_responds_with_controlled_contracts(): void
{
@@ -24,19 +24,19 @@ class ApiAdminOperationsFullSurfaceContractTest extends TestCase
foreach ($this->adminGetEndpoints($world) as $uri) {
$response = $this->getJson($uri);
$this->assertNoServerError($response, 'Admin GET '.$uri);
$this->assertStatusIn($response, [200, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], 'Admin GET '.$uri);
$this->assertNoServerError($response, 'Admin GET ' . $uri);
$this->assertStatusIn($response, [200, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], 'Admin GET ' . $uri);
}
foreach ($this->adminMutationEndpoints($world, $admin->id) as [$method, $uri, $payload]) {
$response = $this->json($method, $uri, $payload);
$this->assertNoServerError($response, 'Admin '.$method.' '.$uri);
$this->assertStatusIn($response, [200, 201, 202, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], 'Admin '.$method.' '.$uri);
$this->assertNoServerError($response, 'Admin ' . $method . ' ' . $uri);
$this->assertStatusIn($response, [200, 201, 202, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], 'Admin ' . $method . ' ' . $uri);
}
}
/**
* @param array<string, mixed> $world
* @param array<string, mixed> $world
* @return list<string>
*/
private function adminGetEndpoints(array $world): array
@@ -76,7 +76,7 @@ class ApiAdminOperationsFullSurfaceContractTest extends TestCase
'/api/v1/administrator/flags/students/1',
'/api/v1/administrator/flags/processed',
'/api/v1/administrator/flags/analysis',
'/api/v1/administrator/grading/homework-tracking?class_section_id='.$classSectionId,
'/api/v1/administrator/grading/homework-tracking?class_section_id=' . $classSectionId,
'/api/v1/administrator/expenses',
'/api/v1/administrator/expenses/options',
'/api/v1/administrator/expenses/1',
@@ -110,13 +110,13 @@ class ApiAdminOperationsFullSurfaceContractTest extends TestCase
'/api/v1/class-sections',
'/api/v1/classes',
'/api/v1/users',
'/api/v1/users/'.$world['parent_id'],
'/api/v1/students/'.$studentId,
'/api/v1/users/' . $world['parent_id'],
'/api/v1/students/' . $studentId,
];
}
/**
* @param array<string, mixed> $world
* @param array<string, mixed> $world
* @return list<array{0: string, 1: string, 2: array<string, mixed>}>
*/
private function adminMutationEndpoints(array $world, int $adminId): array
@@ -43,7 +43,7 @@ class ApiAttachmentUploadContentSafetyContractTest extends FullSurfaceE2EContrac
continue;
}
$path = $this->materializePath($route->uri()).'?filename=../../.env&path=../../storage/logs/laravel.log';
$path = $this->materializePath($route->uri()) . '?filename=../../.env&path=../../storage/logs/laravel.log';
$response = $this->actingAs($this->actorFor($route->uri()), 'api')->getJson($path);
$this->assertStatusIn($response, [200, 204, 400, 401, 403, 404, 409, 422], "GET {$route->uri()} path traversal probe");
@@ -21,8 +21,8 @@ class ApiAuditLogAndAdministrativeTraceabilityContractTest extends FullSurfaceE2
$this->assertNoServerError($response, "$method $uri admin mutation traceability");
$this->assertControlled($response, $method, $uri);
$this->assertStringNotContainsStringIgnoringCase('audit_log_id', $response->getContent(), $uri.' should not leak raw internal audit identifiers by accident.');
$this->assertStringNotContainsStringIgnoringCase('stack_trace', $response->getContent(), $uri.' should not leak stack traces in mutation responses.');
$this->assertStringNotContainsStringIgnoringCase('audit_log_id', $response->getContent(), $uri . ' should not leak raw internal audit identifiers by accident.');
$this->assertStringNotContainsStringIgnoringCase('stack_trace', $response->getContent(), $uri . ' should not leak stack traces in mutation responses.');
}
}
@@ -20,12 +20,12 @@ class ApiAuthenticationLifecycleDepthContractTest extends FullSurfaceE2EContract
'password' => 'password',
]);
$this->assertNoServerError($response, 'valid login at '.$path);
$this->assertNoServerError($response, 'valid login at ' . $path);
if ($response->getStatusCode() >= 200 && $response->getStatusCode() < 300) {
$this->assertTrue(
$response->json('token') !== null || $response->json('access_token') !== null || $response->json('user') !== null,
$path.' must return a usable login contract, not a mystery envelope.'
$path . ' must return a usable login contract, not a mystery envelope.'
);
}
}
@@ -36,18 +36,18 @@ class ApiAuthenticationLifecycleDepthContractTest extends FullSurfaceE2EContract
'password' => 'definitely-wrong-password',
]);
$this->assertStatusIn($response, [400, 401, 403, 419, 422], 'invalid login at '.$path);
$this->assertNoServerError($response, 'invalid login at '.$path);
$this->assertStatusIn($response, [400, 401, 403, 419, 422], 'invalid login at ' . $path);
$this->assertNoServerError($response, 'invalid login at ' . $path);
}
foreach (['/api/v1/auth/me', '/api/v1/me', '/api/user'] as $path) {
$response = $this->actingAs($this->admin, 'api')->getJson($path);
$this->assertNoServerError($response, 'authenticated identity lookup at '.$path);
$this->assertNoServerError($response, 'authenticated identity lookup at ' . $path);
if ($response->getStatusCode() === 200) {
$this->assertTrue(
$response->json('user') !== null || $response->json('id') !== null || $response->json('data') !== null,
$path.' must expose a stable identity payload.'
$path . ' must expose a stable identity payload.'
);
}
}
@@ -56,10 +56,10 @@ class ApiAuthenticationLifecycleDepthContractTest extends FullSurfaceE2EContract
$first = $this->actingAs($this->admin, 'api')->postJson($path);
$second = $this->actingAs($this->admin, 'api')->postJson($path);
$this->assertNoServerError($first, 'first logout at '.$path);
$this->assertNoServerError($second, 'repeat logout at '.$path);
$this->assertStatusIn($first, [200, 202, 204, 302, 401, 404, 419, 422], 'first logout at '.$path);
$this->assertStatusIn($second, [200, 202, 204, 302, 401, 404, 419, 422], 'repeat logout at '.$path);
$this->assertNoServerError($first, 'first logout at ' . $path);
$this->assertNoServerError($second, 'repeat logout at ' . $path);
$this->assertStatusIn($first, [200, 202, 204, 302, 401, 404, 419, 422], 'first logout at ' . $path);
$this->assertStatusIn($second, [200, 202, 204, 302, 401, 404, 419, 422], 'repeat logout at ' . $path);
}
}
@@ -75,16 +75,16 @@ class ApiAuthenticationLifecycleDepthContractTest extends FullSurfaceE2EContract
$uri = $route->uri();
$response = $this->json($method, $this->materializePath($uri), [
'email' => 'nobody.'.uniqid().'@example.test',
'email' => 'nobody.' . uniqid() . '@example.test',
'password' => 'password',
'password_confirmation' => 'not-the-same',
'token' => 'not-a-real-token',
]);
$this->assertControlled($response, $method, $uri);
$this->assertNoServerError($response, $method.' '.$uri);
$this->assertStringNotContainsStringIgnoringCase('select * from', $response->getContent(), $uri.' must not leak account lookup SQL.');
$this->assertStringNotContainsStringIgnoringCase('No query results for model', $response->getContent(), $uri.' must not leak framework model lookups.');
$this->assertNoServerError($response, $method . ' ' . $uri);
$this->assertStringNotContainsStringIgnoringCase('select * from', $response->getContent(), $uri . ' must not leak account lookup SQL.');
$this->assertStringNotContainsStringIgnoringCase('No query results for model', $response->getContent(), $uri . ' must not leak framework model lookups.');
}
}
}
@@ -22,8 +22,8 @@ class ApiAuthorizationCacheInvalidationContractTest extends FullSurfaceE2EContra
$response = $this->requestAs($this->parent, $method, $uri, $payload);
$this->assertStatusIn($response, [401, 403, 404, 405, 419, 422], 'parent privilege escalation should be blocked at '.$method.' '.$uri);
$this->assertNoServerError($response, 'permission cache escalation probe at '.$method.' '.$uri);
$this->assertStatusIn($response, [401, 403, 404, 405, 419, 422], 'parent privilege escalation should be blocked at ' . $method . ' ' . $uri);
$this->assertNoServerError($response, 'permission cache escalation probe at ' . $method . ' ' . $uri);
}
}
}
@@ -24,7 +24,7 @@ class ApiBatch11RouteRegressionNetTest extends FullSurfaceE2EContractCase
foreach ($families as $family => $needles) {
$this->assertNotEmpty(
$this->apiRoutesContainingAny($needles),
'Batch 11 expects at least one route for the '.$family.' family. If this fails, update the route catalog instead of pretending the family vanished.'
'Batch 11 expects at least one route for the ' . $family . ' family. If this fails, update the route catalog instead of pretending the family vanished.'
);
}
}
@@ -53,7 +53,7 @@ class ApiBatch11RouteRegressionNetTest extends FullSurfaceE2EContractCase
])->json($method, $this->materializePath($uri), $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNoServerError($response, 'batch 11 combined hostile probe at '.$method.' '.$uri);
$this->assertNoServerError($response, 'batch 11 combined hostile probe at ' . $method . ' ' . $uri);
}
}
}
@@ -2,9 +2,10 @@
namespace Tests\Feature\Api\V1\FullSurface;
use Illuminate\Http\UploadedFile;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
use Illuminate\Http\UploadedFile;
class ApiBatch12AttachmentLifecycleContractTest extends FullSurfaceE2EContractCase
{
public function test_attachment_upload_routes_handle_multiple_file_shapes_cleanly(): void
@@ -6,7 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14AcademicPromotionRollbackIntegrityContractTest extends FullSurfaceE2EContractCase
{
public function test_promotion_and_school_year_routes_reject_unsafe_rollback_payloads(): void
public function test_promotion_and_school_year_routes_reject_unsafe_rollback_payloads(): void
{
foreach (array_slice($this->apiRoutesContainingAny(['promotion', 'promote', 'school-year', 'semester', 'close', 'reopen', 'rollback']), 0, 45) as $route) {
$method = $this->primaryMethod($route);
@@ -22,12 +22,10 @@ class ApiBatch14AcademicPromotionRollbackIntegrityContractTest extends FullSurfa
{
foreach (array_slice($this->apiRoutesContainingAny(['promotion', 'school-year', 'close', 'reopen']), 0, 30) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) {
continue;
}
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { continue; }
foreach ([$this->teacher, $this->parent, $this->studentUser] as $actor) {
$response = $this->requestAs($actor, $method, $route->uri(), ['force' => true, 'confirmed' => true, 'school_year_id' => $this->ids['schoolYearId']]);
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method.' '.$route->uri());
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method . ' ' . $route->uri());
}
}
}
@@ -6,15 +6,13 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14AccountEnumerationAndIdentityPrivacyContractTest extends FullSurfaceE2EContractCase
{
public function test_identity_recovery_routes_do_not_reveal_whether_user_exists(): void
public function test_identity_recovery_routes_do_not_reveal_whether_user_exists(): void
{
$routes = $this->apiRoutesContainingAny(['forgot', 'password', 'reset', 'verify', 'verification', 'recovery']);
foreach (array_slice($routes, 0, 30) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH'], true)) {
continue;
}
foreach ([$this->admin->email, 'missing-user-'.uniqid().'@example.test'] as $email) {
if (! in_array($method, ['POST', 'PUT', 'PATCH'], true)) { continue; }
foreach ([$this->admin->email, 'missing-user-' . uniqid() . '@example.test'] as $email) {
$response = $this->json($method, $this->materializePath($route->uri()), ['email' => $email, 'token' => 'invalid-token', 'password' => 'NewPassword123!', 'password_confirmation' => 'NewPassword123!']);
$this->assertControlled($response, $method, $route->uri());
$body = strtolower($response->getContent());
@@ -33,7 +31,7 @@ class ApiBatch14AccountEnumerationAndIdentityPrivacyContractTest extends FullSur
$this->assertControlled($response, $method, $route->uri());
$body = strtolower($response->getContent());
foreach (['password', 'remember_token', 'two_factor', 'api_token', 'provider_token', 'secret'] as $forbidden) {
$this->assertStringNotContainsString($forbidden, $body, $route->uri().' leaked identity internals.');
$this->assertStringNotContainsString($forbidden, $body, $route->uri() . ' leaked identity internals.');
}
}
}
@@ -6,7 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14AdminDelegationApprovalWorkflowContractTest extends FullSurfaceE2EContractCase
{
public function test_approval_and_delegation_routes_reject_forged_approval_metadata(): void
public function test_approval_and_delegation_routes_reject_forged_approval_metadata(): void
{
foreach (array_slice($this->apiRoutesContainingAny(['approve', 'approval', 'delegate', 'delegation', 'authorize', 'permission', 'role', 'impersonate']), 0, 50) as $route) {
$method = $this->primaryMethod($route);
@@ -20,12 +20,10 @@ class ApiBatch14AdminDelegationApprovalWorkflowContractTest extends FullSurfaceE
{
foreach (array_slice($this->apiRoutesContainingAny(['approve', 'approval', 'delegate', 'permission', 'role']), 0, 35) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) {
continue;
}
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { continue; }
foreach ([$this->teacher, $this->parent, $this->studentUser] as $actor) {
$response = $this->requestAs($actor, $method, $route->uri(), ['approved' => true, 'permissions' => ['*'], 'role' => 'administrator', 'force' => true]);
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method.' '.$route->uri());
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method . ' ' . $route->uri());
}
}
}
@@ -6,17 +6,15 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14ApiConsumerContractCompatibilityTest extends FullSurfaceE2EContractCase
{
public function test_core_identity_contracts_remain_compatible_for_web_mobile_and_api_clients(): void
public function test_core_identity_contracts_remain_compatible_for_web_mobile_and_api_clients(): void
{
foreach (array_slice($this->apiRoutesContainingAny(['login', 'auth/me', 'me', 'frontend', 'profile']), 0, 25) as $route) {
$method = $this->primaryMethod($route);
foreach ([['Accept' => 'application/json', 'X-Client' => 'web'], ['Accept' => 'application/json', 'X-Client' => 'mobile', 'X-App-Version' => '1.0.0'], ['Accept' => 'application/vnd.alrahma.v1+json', 'X-Client' => 'api-consumer']] as $headers) {
$response = $this->withHeaders($headers)->actingAs($this->actorFor($route->uri()), 'api')->json($method, $this->materializePath($route->uri()), $this->payloadFor($method, $route->uri()));
$this->assertControlled($response, $method, $route->uri());
$this->assertJsonResponseWhenSuccessful($response, $method.' '.$route->uri());
if ($response->isSuccessful()) {
$this->assertStringNotContainsString('undefined index', strtolower($response->getContent()));
}
$this->assertJsonResponseWhenSuccessful($response, $method . ' ' . $route->uri());
if ($response->isSuccessful()) { $this->assertStringNotContainsString('undefined index', strtolower($response->getContent())); }
}
}
}
@@ -25,15 +23,13 @@ class ApiBatch14ApiConsumerContractCompatibilityTest extends FullSurfaceE2EContr
{
foreach (array_slice($this->apiRoutesContainingAny(['login', 'auth/login']), 0, 10) as $route) {
$method = $this->primaryMethod($route);
if ($method !== 'POST') {
continue;
}
if ($method !== 'POST') { continue; }
$response = $this->json($method, $this->materializePath($route->uri()), ['email' => $this->admin->email, 'password' => 'password']);
$this->assertControlled($response, $method, $route->uri());
if ($response->isSuccessful() && $this->isJsonString($response->getContent())) {
$data = $response->json();
$this->assertTrue(isset($data['token']) || isset($data['access_token']), $route->uri().' should expose token/access_token when login succeeds.');
$this->assertArrayHasKey('user', $data, $route->uri().' should expose user when login succeeds.');
$this->assertTrue(isset($data['token']) || isset($data['access_token']), $route->uri() . ' should expose token/access_token when login succeeds.');
$this->assertArrayHasKey('user', $data, $route->uri() . ' should expose user when login succeeds.');
}
}
}
@@ -6,7 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14AttendanceDeviceAndKioskAbuseContractTest extends FullSurfaceE2EContractCase
{
public function test_kiosk_scanner_and_attendance_routes_reject_forged_device_identity(): void
public function test_kiosk_scanner_and_attendance_routes_reject_forged_device_identity(): void
{
foreach (array_slice($this->apiRoutesContainingAny(['attendance', 'scanner', 'scan', 'kiosk', 'badge', 'dismissal', 'late']), 0, 55) as $route) {
$method = $this->primaryMethod($route);
@@ -22,12 +22,10 @@ class ApiBatch14AttendanceDeviceAndKioskAbuseContractTest extends FullSurfaceE2E
{
foreach (array_slice($this->apiRoutesContainingAny(['attendance', 'scan', 'dismissal', 'late']), 0, 30) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH'], true)) {
continue;
}
if (! in_array($method, ['POST', 'PUT', 'PATCH'], true)) { continue; }
foreach ([$this->studentUser, $this->parent] as $actor) {
$response = $this->requestAs($actor, $method, $route->uri(), ['student_id' => $this->ids['studentId'], 'status' => 'present', 'verified_by_device' => true, 'override_reason' => 'parent approved']);
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method.' '.$route->uri());
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method . ' ' . $route->uri());
}
}
}
@@ -6,7 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14AuditTrailTamperResistanceContractTest extends FullSurfaceE2EContractCase
{
public function test_audit_and_log_routes_reject_client_supplied_actor_and_timestamp_fields(): void
public function test_audit_and_log_routes_reject_client_supplied_actor_and_timestamp_fields(): void
{
foreach (array_slice($this->apiRoutesContainingAny(['audit', 'log', 'activity', 'history', 'trace']), 0, 35) as $route) {
$method = $this->primaryMethod($route);
@@ -22,12 +22,10 @@ class ApiBatch14AuditTrailTamperResistanceContractTest extends FullSurfaceE2ECon
{
foreach (array_slice($this->apiRoutesContainingAny(['audit', 'log', 'history', 'activity']), 0, 25) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) {
continue;
}
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { continue; }
foreach ([$this->parent, $this->teacher, $this->studentUser] as $actor) {
$response = $this->requestAs($actor, $method, $route->uri(), ['action' => 'clear', 'purge' => true, 'rewrite' => true]);
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method.' '.$route->uri());
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method . ' ' . $route->uri());
}
}
}
@@ -6,13 +6,11 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14BulkAssignmentCollisionContractTest extends FullSurfaceE2EContractCase
{
public function test_bulk_assignment_routes_handle_duplicate_and_conflicting_ids_cleanly(): void
public function test_bulk_assignment_routes_handle_duplicate_and_conflicting_ids_cleanly(): void
{
foreach (array_slice($this->apiRoutesContainingAny(['assign', 'assignment', 'enroll', 'bulk', 'batch', 'class', 'teacher', 'student']), 0, 50) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH'], true)) {
continue;
}
if (! in_array($method, ['POST', 'PUT', 'PATCH'], true)) { continue; }
$response = $this->requestAs($this->actorFor($route->uri()), $method, $route->uri(), $this->payloadFor($method, $route->uri()) + [
'student_ids' => [$this->ids['studentId'], $this->ids['studentId'], 99999999], 'teacher_ids' => [$this->teacher->id, $this->teacher->id, $this->parent->id],
'class_section_ids' => [$this->ids['classSectionId'], $this->ids['classSectionId'], 99999999], 'parent_ids' => [$this->ids['parentId'], $this->ids['parentId'], 99999999], 'force' => true, 'overwrite' => true,
@@ -29,12 +27,10 @@ class ApiBatch14BulkAssignmentCollisionContractTest extends FullSurfaceE2EContra
{
foreach (array_slice($this->apiRoutesContainingAny(['assign', 'bulk', 'batch', 'class-sections', 'teacher-class', 'enroll']), 0, 30) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) {
continue;
}
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { continue; }
foreach ([$this->parent, $this->studentUser] as $actor) {
$response = $this->requestAs($actor, $method, $route->uri(), ['student_ids' => [$this->ids['studentId']], 'teacher_ids' => [$this->teacher->id], 'class_section_ids' => [$this->ids['classSectionId']], 'all' => true, 'force' => true]);
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method.' '.$route->uri());
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method . ' ' . $route->uri());
}
}
}
@@ -6,7 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14CrossModuleInvariantRiskRegisterTest extends FullSurfaceE2EContractCase
{
public function test_batch14_route_families_have_cross_module_risk_coverage(): void
public function test_batch14_route_families_have_cross_module_risk_coverage(): void
{
$riskFamilies = [
'session-cookie-boundary' => ['auth', 'login', 'frontend'],
@@ -22,7 +22,7 @@ class ApiBatch14CrossModuleInvariantRiskRegisterTest extends FullSurfaceE2EContr
];
foreach ($riskFamilies as $name => $needles) {
$this->assertNotEmpty($this->apiRoutesContainingAny($needles), 'Batch 14 expected route coverage for '.$name);
$this->assertNotEmpty($this->apiRoutesContainingAny($needles), 'Batch 14 expected route coverage for ' . $name);
}
}
@@ -32,7 +32,7 @@ class ApiBatch14CrossModuleInvariantRiskRegisterTest extends FullSurfaceE2EContr
$mutations = array_filter($this->apiRoutesContainingAny([$family]), function ($route): bool {
return in_array($this->primaryMethod($route), ['POST', 'PUT', 'PATCH', 'DELETE'], true);
});
$this->assertNotEmpty($mutations, 'Batch 14 expected mutating route family: '.$family);
$this->assertNotEmpty($mutations, 'Batch 14 expected mutating route family: ' . $family);
}
}
}
@@ -6,15 +6,14 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14DataLifecycleArchivePurgeContractTest extends FullSurfaceE2EContractCase
{
public function test_archive_purge_and_retention_routes_require_safe_controlled_requests(): void
public function test_archive_purge_and_retention_routes_require_safe_controlled_requests(): void
{
foreach (array_slice($this->apiRoutesContainingAny(['archive', 'restore', 'purge', 'erase', 'retention', 'delete', 'destroy']), 0, 50) as $route) {
$method = $this->primaryMethod($route);
$response = $this->requestAs($this->actorFor($route->uri()), $method, $route->uri(), $this->payloadFor($method, $route->uri()) + ['ids' => [$this->ids['studentId'], 99999999, '../../.env'], 'hard_delete' => true, 'purge' => true, 'cascade' => true, 'include_audit_logs' => true, 'reason' => '', 'confirmed' => false]);
$this->assertControlled($response, $method, $route->uri());
$body = strtolower($response->getContent());
$this->assertStringNotContainsString('sqlstate', $body);
$this->assertStringNotContainsString('foreign key constraint', $body);
$this->assertStringNotContainsString('sqlstate', $body); $this->assertStringNotContainsString('foreign key constraint', $body);
}
}
@@ -22,12 +21,10 @@ class ApiBatch14DataLifecycleArchivePurgeContractTest extends FullSurfaceE2ECont
{
foreach (array_slice($this->apiRoutesContainingAny(['purge', 'erase', 'restore', 'archive', 'delete']), 0, 35) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) {
continue;
}
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { continue; }
foreach ([$this->teacher, $this->parent, $this->studentUser] as $actor) {
$response = $this->requestAs($actor, $method, $route->uri(), ['ids' => [$this->ids['studentId']], 'hard_delete' => true, 'purge' => true, 'global' => true]);
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method.' '.$route->uri());
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method . ' ' . $route->uri());
}
}
}
@@ -6,18 +6,16 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14FinanceReconciliationAndRefundAbuseContractTest extends FullSurfaceE2EContractCase
{
public function test_finance_reconciliation_routes_reject_mismatched_invoice_parent_and_payment_fields(): void
public function test_finance_reconciliation_routes_reject_mismatched_invoice_parent_and_payment_fields(): void
{
foreach (array_slice($this->apiRoutesContainingAny(['finance', 'invoice', 'payment', 'refund', 'transaction', 'ledger', 'reconcile']), 0, 55) as $route) {
$method = $this->primaryMethod($route);
$response = $this->requestAs($this->actorFor($route->uri()), $method, $route->uri(), $this->payloadFor($method, $route->uri()) + [
'invoice_id' => $this->ids['invoiceId'], 'parent_id' => 99999999, 'student_id' => 99999999, 'payment_id' => 99999999, 'refund_id' => 99999999, 'transaction_id' => 'provider-'.uniqid(), 'provider_transaction_id' => 'provider-'.uniqid(), 'amount' => -99999.99, 'net_amount' => -99999.99, 'fee_amount' => -99999.99, 'currency' => 'XXX', 'reconciled' => true, 'force_posted' => true,
'invoice_id' => $this->ids['invoiceId'], 'parent_id' => 99999999, 'student_id' => 99999999, 'payment_id' => 99999999, 'refund_id' => 99999999, 'transaction_id' => 'provider-' . uniqid(), 'provider_transaction_id' => 'provider-' . uniqid(), 'amount' => -99999.99, 'net_amount' => -99999.99, 'fee_amount' => -99999.99, 'currency' => 'XXX', 'reconciled' => true, 'force_posted' => true,
]);
$this->assertControlled($response, $method, $route->uri());
$body = strtolower($response->getContent());
$this->assertStringNotContainsString('sqlstate', $body);
$this->assertStringNotContainsString('duplicate entry', $body);
$this->assertStringNotContainsString('foreign key constraint', $body);
$this->assertStringNotContainsString('sqlstate', $body); $this->assertStringNotContainsString('duplicate entry', $body); $this->assertStringNotContainsString('foreign key constraint', $body);
}
}
@@ -25,11 +23,9 @@ class ApiBatch14FinanceReconciliationAndRefundAbuseContractTest extends FullSurf
{
foreach (array_slice($this->apiRoutesContainingAny(['refund', 'reconcile', 'ledger', 'payment']), 0, 35) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) {
continue;
}
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { continue; }
$response = $this->requestAs($this->parent, $method, $route->uri(), ['invoice_id' => $this->ids['invoiceId'], 'amount' => 100.00, 'approved' => true, 'reconciled' => true, 'refund_now' => true]);
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method.' '.$route->uri());
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method . ' ' . $route->uri());
}
}
}
@@ -6,21 +6,17 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14NotificationTemplateInjectionContractTest extends FullSurfaceE2EContractCase
{
public function test_notification_and_template_routes_reject_template_injection_payloads(): void
public function test_notification_and_template_routes_reject_template_injection_payloads(): void
{
foreach (array_slice($this->apiRoutesContainingAny(['notification', 'notify', 'message', 'email', 'whatsapp', 'template', 'broadcast']), 0, 55) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH'], true)) {
continue;
}
if (! in_array($method, ['POST', 'PUT', 'PATCH'], true)) { continue; }
$response = $this->requestAs($this->actorFor($route->uri()), $method, $route->uri(), $this->payloadFor($method, $route->uri()) + [
'subject' => '{{ config("app.key") }}', 'message' => '<script>alert(document.cookie)</script>{{ $user->password }}', 'body' => '${jndi:ldap://127.0.0.1/a}', 'template' => '@php echo env("APP_KEY"); @endphp', 'channel' => 'all', 'recipients' => ['*'], 'phone' => "+15551234567\r\nBcc: attacker@example.test", 'email' => "victim@example.test\r\nBcc: attacker@example.test",
]);
$this->assertControlled($response, $method, $route->uri());
$body = strtolower($response->getContent());
$this->assertStringNotContainsString('app_key', $body);
$this->assertStringNotContainsString('document.cookie', $body);
$this->assertStringNotContainsString('jndi:ldap', $body);
$this->assertStringNotContainsString('app_key', $body); $this->assertStringNotContainsString('document.cookie', $body); $this->assertStringNotContainsString('jndi:ldap', $body);
}
}
@@ -28,12 +24,10 @@ class ApiBatch14NotificationTemplateInjectionContractTest extends FullSurfaceE2E
{
foreach (array_slice($this->apiRoutesContainingAny(['broadcast', 'notification', 'email', 'whatsapp']), 0, 30) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH'], true)) {
continue;
}
if (! in_array($method, ['POST', 'PUT', 'PATCH'], true)) { continue; }
foreach ([$this->parent, $this->teacher, $this->studentUser] as $actor) {
$response = $this->requestAs($actor, $method, $route->uri(), ['channel' => 'all', 'recipients' => ['all_school'], 'template_id' => 1, 'force_send' => true]);
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method.' '.$route->uri());
$this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], $method . ' ' . $route->uri());
}
}
}
@@ -6,7 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14PrintExportTamperingContractTest extends FullSurfaceE2EContractCase
{
public function test_print_export_and_document_generation_routes_reject_tampered_scope_fields(): void
public function test_print_export_and_document_generation_routes_reject_tampered_scope_fields(): void
{
foreach (array_slice($this->apiRoutesContainingAny(['print', 'export', 'download', 'certificate', 'badge', 'receipt', 'transcript', 'report-card']), 0, 50) as $route) {
$method = $this->primaryMethod($route);
@@ -15,9 +15,7 @@ class ApiBatch14PrintExportTamperingContractTest extends FullSurfaceE2EContractC
]);
$this->assertControlled($response, $method, $route->uri());
$body = strtolower($response->getContent());
foreach (['app_key', 'laravel.log', 'password', 'remember_token', 'sqlstate'] as $forbidden) {
$this->assertStringNotContainsString($forbidden, $body);
}
foreach (['app_key', 'laravel.log', 'password', 'remember_token', 'sqlstate'] as $forbidden) { $this->assertStringNotContainsString($forbidden, $body); }
}
}
@@ -27,7 +25,7 @@ class ApiBatch14PrintExportTamperingContractTest extends FullSurfaceE2EContractC
$method = $this->primaryMethod($route);
foreach ([$this->parent, $this->teacher, $this->studentUser] as $actor) {
$response = $this->requestAs($actor, $method, $route->uri(), ['scope' => 'all_school', 'all_students' => true, 'include_finance' => true, 'include_guardians' => true, 'format' => 'csv']);
$this->assertStatusIn($response, [200, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], $method.' '.$route->uri());
$this->assertStatusIn($response, [200, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], $method . ' ' . $route->uri());
$this->assertStringNotContainsString('remember_token', strtolower($response->getContent()));
}
}
@@ -6,7 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14RegressionMegaSweepTest extends FullSurfaceE2EContractCase
{
public function test_batch14_mega_sweep_keeps_new_risk_payloads_under_controlled_failure(): void
public function test_batch14_mega_sweep_keeps_new_risk_payloads_under_controlled_failure(): void
{
$families = [
['auth', 'login', 'password', 'verify'], ['users', 'roles', 'permissions', 'profile'], ['students', 'parents', 'family', 'guardian'],
@@ -26,9 +26,7 @@ class ApiBatch14RegressionMegaSweepTest extends FullSurfaceE2EContractCase
$response = $this->requestAs($this->actorFor($route->uri()), $method, $route->uri(), $payload);
$this->assertControlled($response, $method, $route->uri());
$body = strtolower($response->getContent());
foreach (['sqlstate', 'app_key', 'db_password', 'remember_token', 'stack trace', 'laravel.log'] as $forbidden) {
$this->assertStringNotContainsString($forbidden, $body);
}
foreach (['sqlstate', 'app_key', 'db_password', 'remember_token', 'stack trace', 'laravel.log'] as $forbidden) { $this->assertStringNotContainsString($forbidden, $body); }
}
}
}
@@ -6,19 +6,15 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14RelationshipOwnershipMatrixExpansionTest extends FullSurfaceE2EContractCase
{
public function test_relationship_owner_override_fields_are_not_trusted_across_route_families(): void
public function test_relationship_owner_override_fields_are_not_trusted_across_route_families(): void
{
foreach (array_slice($this->apiRoutesContainingAny(['student', 'parent', 'family', 'invoice', 'attendance', 'score', 'guardian', 'authorized']), 0, 60) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) {
continue;
}
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { continue; }
foreach ([$this->parent, $this->teacher] as $actor) {
$response = $this->requestAs($actor, $method, $route->uri(), $this->payloadFor($method, $route->uri()) + ['student_ids' => [$this->ids['studentId'], 99999999], 'parent_id' => 99999999, 'family_id' => 99999999, 'guardian_id' => 99999999, 'owner_id' => $this->admin->id, 'owner_type' => 'App\\Models\\User', 'created_by' => $this->admin->id, 'scope' => 'global']);
$this->assertControlled($response, $method, $route->uri());
$body = strtolower($response->getContent());
$this->assertStringNotContainsString('sqlstate', $body);
$this->assertStringNotContainsString('remember_token', $body);
$body = strtolower($response->getContent()); $this->assertStringNotContainsString('sqlstate', $body); $this->assertStringNotContainsString('remember_token', $body);
}
}
}
@@ -28,7 +24,7 @@ class ApiBatch14RelationshipOwnershipMatrixExpansionTest extends FullSurfaceE2EC
foreach (array_slice($this->apiRoutesContainingAny(['parent', 'family', 'guardian', 'invoice', 'attendance', 'report-card']), 0, 40) as $route) {
$method = $this->primaryMethod($route);
$response = $this->requestAs($this->studentUser, $method, $route->uri(), ['student_id' => 99999999, 'parent_id' => 99999999, 'family_id' => 99999999, 'include' => 'guardians,invoices,attendance,notes']);
$this->assertStatusIn($response, [200, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], $method.' '.$route->uri());
$this->assertStatusIn($response, [200, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], $method . ' ' . $route->uri());
$this->assertStringNotContainsString('remember_token', strtolower($response->getContent()));
}
}
@@ -6,7 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14SessionCookieCsrfBoundaryContractTest extends FullSurfaceE2EContractCase
{
public function test_api_auth_routes_do_not_depend_on_browser_session_cookies(): void
public function test_api_auth_routes_do_not_depend_on_browser_session_cookies(): void
{
foreach (array_slice($this->apiRoutesContainingAny(['auth', 'login', 'logout', 'me', 'profile', 'frontend']), 0, 40) as $route) {
$method = $this->primaryMethod($route);
@@ -26,7 +26,7 @@ class ApiBatch14SessionCookieCsrfBoundaryContractTest extends FullSurfaceE2ECont
$response = $this->withCookie('laravel_session', 'fake-admin-browser-session')
->withHeaders(['X-Requested-With' => 'XMLHttpRequest', 'X-CSRF-TOKEN' => 'fake-csrf', 'X-User-Id' => (string) $this->admin->id])
->json($method, $this->materializePath($route->uri()), $this->payloadFor($method, $route->uri()));
$this->assertStatusIn($response, [401, 403, 404, 405, 419, 422], $method.' '.$route->uri());
$this->assertStatusIn($response, [401, 403, 404, 405, 419, 422], $method . ' ' . $route->uri());
$this->assertStringNotContainsString('sqlstate', strtolower($response->getContent()));
}
}
@@ -6,16 +6,14 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch14StoragePathAndSignedUrlContractTest extends FullSurfaceE2EContractCase
{
public function test_file_and_signed_url_routes_reject_path_and_disk_overrides(): void
public function test_file_and_signed_url_routes_reject_path_and_disk_overrides(): void
{
foreach (array_slice($this->apiRoutesContainingAny(['file', 'media', 'upload', 'download', 'export', 'import', 'attachment', 'document', 'signed']), 0, 55) as $route) {
$method = $this->primaryMethod($route);
$response = $this->requestAs($this->actorFor($route->uri()), $method, $route->uri(), $this->payloadFor($method, $route->uri()) + ['path' => '../../.env', 'filename' => '../../storage/logs/laravel.log', 'disk' => 'local', 'storage_disk' => 's3://private-bucket', 'url' => 'file:///etc/passwd', 'expires' => '2099-01-01', 'signature' => 'forged-signature', 'mime_type' => 'text/x-php']);
$this->assertControlled($response, $method, $route->uri());
$body = strtolower($response->getContent());
foreach (['app_key', 'db_password', 'laravel.log', '/etc/passwd', 'begin rsa private key'] as $forbidden) {
$this->assertStringNotContainsString($forbidden, $body);
}
foreach (['app_key', 'db_password', 'laravel.log', '/etc/passwd', 'begin rsa private key'] as $forbidden) { $this->assertStringNotContainsString($forbidden, $body); }
}
}
@@ -23,8 +21,8 @@ class ApiBatch14StoragePathAndSignedUrlContractTest extends FullSurfaceE2EContra
{
foreach (array_slice($this->apiRoutesContainingAny(['download', 'signed', 'public', 'media', 'file']), 0, 35) as $route) {
$method = $this->primaryMethod($route);
$response = $this->json($method, $this->materializePath($route->uri()).'?signature=forged&expires=1&path=../../.env');
$this->assertStatusIn($response, [200, 204, 302, 400, 401, 403, 404, 405, 410, 419, 422], $method.' '.$route->uri());
$response = $this->json($method, $this->materializePath($route->uri()) . '?signature=forged&expires=1&path=../../.env');
$this->assertStatusIn($response, [200, 204, 302, 400, 401, 403, 404, 405, 410, 419, 422], $method . ' ' . $route->uri());
$this->assertStringNotContainsString('app_key', strtolower($response->getContent()));
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15AdminImpersonationSessionBoundaryContractTest extends FullSurfaceE2EContractCase
{
public function test_batch15_impersonation_and_acting_as_routes_require_real_admin_context_not_payload_claims(): void
{
$routes = $this->apiRoutesContainingAny(['impersonate', 'acting-as', 'act-as', 'switch-user', 'delegate', 'behalf']);
@@ -27,4 +28,5 @@ class ApiBatch15AdminImpersonationSessionBoundaryContractTest extends FullSurfac
$this->assertStringNotContainsString('access_token', strtolower($parentResponse->getContent()));
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15AuthTokenRotationAndRevocationContractTest extends FullSurfaceE2EContractCase
{
public function test_batch15_token_rotation_revocation_and_identity_routes_do_not_accept_stale_or_forged_tokens(): void
{
$routes = $this->apiRoutesContainingAny(['auth', 'login', 'logout', 'refresh', 'token', 'me', 'profile', 'session']);
@@ -45,4 +46,5 @@ class ApiBatch15AuthTokenRotationAndRevocationContractTest extends FullSurfaceE2
}
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15BackgroundCommandAndSchedulerContractTest extends FullSurfaceE2EContractCase
{
public function test_batch15_command_scheduler_and_job_trigger_routes_are_admin_only_and_idempotent(): void
{
$routes = $this->apiRoutesContainingAny(['command', 'schedule', 'scheduler', 'cron', 'job', 'queue', 'sync', 'recalculate', 'cleanup', 'send']);
@@ -29,4 +30,5 @@ class ApiBatch15BackgroundCommandAndSchedulerContractTest extends FullSurfaceE2E
$this->assertControlled($adminResponse, $method, $uri);
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15ConsentPrivacyAndDataSubjectRightsContractTest extends FullSurfaceE2EContractCase
{
public function test_batch15_privacy_consent_erasure_and_export_routes_are_admin_or_owner_scoped(): void
{
$routes = $this->apiRoutesContainingAny(['privacy', 'consent', 'erase', 'erasure', 'delete-account', 'export-data', 'data-subject', 'retention']);
@@ -43,4 +44,5 @@ class ApiBatch15ConsentPrivacyAndDataSubjectRightsContractTest extends FullSurfa
$this->assertStringNotContainsString('db_password', strtolower($response->getContent()));
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15CrossSchoolIdentityCollisionContractTest extends FullSurfaceE2EContractCase
{
public function test_batch15_duplicate_external_identifiers_and_cross_school_aliases_fail_cleanly(): void
{
$routes = $this->apiRoutesContainingAny(['students', 'users', 'parents', 'family', 'guardian', 'enrollment', 'registration']);
@@ -30,4 +31,5 @@ class ApiBatch15CrossSchoolIdentityCollisionContractTest extends FullSurfaceE2EC
$this->assertStringNotContainsString('duplicate entry', strtolower($response->getContent()));
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15DataResidencyBackupAndRestoreContractTest extends FullSurfaceE2EContractCase
{
public function test_batch15_backup_restore_residency_and_snapshot_routes_do_not_expose_or_mutate_for_low_privilege_users(): void
{
$routes = $this->apiRoutesContainingAny(['backup', 'restore', 'snapshot', 'residency', 'tenant', 'database', 'dump', 'archive']);
@@ -31,4 +32,5 @@ class ApiBatch15DataResidencyBackupAndRestoreContractTest extends FullSurfaceE2E
}
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15DomainEventOutboxAndNotificationConsistencyContractTest extends FullSurfaceE2EContractCase
{
public function test_batch15_event_outbox_and_notification_trigger_routes_reject_forged_event_metadata(): void
{
$routes = $this->apiRoutesContainingAny(['event', 'outbox', 'notification', 'broadcast', 'message', 'email', 'whatsapp', 'webhook']);
@@ -27,4 +28,5 @@ class ApiBatch15DomainEventOutboxAndNotificationConsistencyContractTest extends
$this->assertControlled($response, $method, $uri);
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15FeatureFlagExperimentAndRolloutContractTest extends FullSurfaceE2EContractCase
{
public function test_batch15_feature_flag_and_rollout_routes_reject_low_privilege_override_payloads(): void
{
$routes = $this->apiRoutesContainingAny(['feature', 'flag', 'experiment', 'rollout', 'settings', 'configuration']);
@@ -27,4 +28,5 @@ class ApiBatch15FeatureFlagExperimentAndRolloutContractTest extends FullSurfaceE
$this->assertControlled($response, $method, $uri);
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15FormRequestAuthorizationCoverageContractTest extends FullSurfaceE2EContractCase
{
public function test_batch15_mutating_routes_do_not_allow_public_or_low_privilege_form_request_bypass(): void
{
foreach (array_slice($this->apiRoutes(), 0, 120) as $route) {
@@ -28,4 +29,5 @@ class ApiBatch15FormRequestAuthorizationCoverageContractTest extends FullSurface
$this->assertControlled($response, $method, $uri);
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15GlobalSearchAndDirectoryEnumerationContractTest extends FullSurfaceE2EContractCase
{
public function test_batch15_global_search_directory_and_lookup_routes_resist_enumeration_inputs(): void
{
$routes = $this->apiRoutesContainingAny(['search', 'lookup', 'directory', 'autocomplete', 'users', 'students', 'parents']);
@@ -26,4 +27,5 @@ class ApiBatch15GlobalSearchAndDirectoryEnumerationContractTest extends FullSurf
}
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15LocalizationContentDirectionAndEncodingContractTest extends FullSurfaceE2EContractCase
{
public function test_batch15_rtl_unicode_and_encoding_payloads_remain_controlled_across_text_routes(): void
{
$routes = $this->apiRoutesContainingAny(['message', 'contact', 'support', 'notification', 'student', 'parent', 'settings', 'certificate']);
@@ -30,4 +31,5 @@ class ApiBatch15LocalizationContentDirectionAndEncodingContractTest extends Full
$this->assertStringNotContainsString('malformed utf-8', strtolower($response->getContent()));
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15MetadataFilteringAndSparseFieldsetSecurityContractTest extends FullSurfaceE2EContractCase
{
public function test_batch15_sparse_fieldsets_metadata_and_embeds_do_not_reveal_sensitive_associations(): void
{
$routes = $this->apiRoutesContainingAny(['users', 'students', 'parents', 'family', 'finance', 'attendance', 'reports', 'dashboard']);
@@ -30,4 +31,5 @@ class ApiBatch15MetadataFilteringAndSparseFieldsetSecurityContractTest extends F
}
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15RateLimitAndAbusePatternContractTest extends FullSurfaceE2EContractCase
{
public function test_batch15_repeated_sensitive_requests_return_controlled_responses_without_leaking_internals(): void
{
$routes = $this->apiRoutesContainingAny(['login', 'password', 'verify', 'contact', 'support', 'message', 'scanner', 'badge']);
@@ -15,7 +16,7 @@ class ApiBatch15RateLimitAndAbusePatternContractTest extends FullSurfaceE2EContr
$uri = $route->uri();
for ($attempt = 1; $attempt <= 3; $attempt++) {
$response = $this->withHeader('X-Forwarded-For', '203.0.113.'.$attempt)
$response = $this->withHeader('X-Forwarded-For', '203.0.113.' . $attempt)
->requestAs($this->actorFor($uri), $method, $uri, $this->payloadFor($method, $uri) + [
'email' => 'rate-limit-'.$attempt.'@example.test',
'password' => 'wrong-password',
@@ -30,4 +31,5 @@ class ApiBatch15RateLimitAndAbusePatternContractTest extends FullSurfaceE2EContr
}
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15RegressionUltraSweepTest extends FullSurfaceE2EContractCase
{
public function test_batch15_ultra_sweep_keeps_cross_domain_hostile_payloads_under_controlled_responses(): void
{
$families = [
@@ -42,4 +43,5 @@ class ApiBatch15RegressionUltraSweepTest extends FullSurfaceE2EContractCase
}
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15ReportDrilldownAndAggregatePrivacyContractTest extends FullSurfaceE2EContractCase
{
public function test_batch15_report_drilldown_filters_do_not_allow_scope_escape_or_sensitive_field_inclusion(): void
{
$routes = $this->apiRoutesContainingAny(['report', 'analytics', 'dashboard', 'summary', 'drilldown', 'export']);
@@ -33,4 +34,5 @@ class ApiBatch15ReportDrilldownAndAggregatePrivacyContractTest extends FullSurfa
}
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15RouteParameterPoisoningExpansionTest extends FullSurfaceE2EContractCase
{
public function test_batch15_route_parameters_reject_encoded_path_script_and_type_confusion_payloads_cleanly(): void
{
$poisoned = ['..%2F..%2F.env', '%00', '<svg/onload=alert(1)>', '1 or 1=1', str_repeat('9', 80), '٠١٢٣', 'null', '[]'];
@@ -17,7 +18,7 @@ class ApiBatch15RouteParameterPoisoningExpansionTest extends FullSurfaceE2EContr
$method = $this->primaryMethod($route);
foreach (array_slice($poisoned, 0, 4) as $value) {
$path = '/'.preg_replace('/\{[^}]+\}/', $value, $route->uri());
$path = '/' . preg_replace('/\{[^}]+\}/', $value, $route->uri());
$this->actingAs($this->actorFor($route->uri()), 'api');
$response = $this->json($method, $path, $this->payloadFor($method, $route->uri()));
$this->assertControlled($response, $method, $route->uri());
@@ -25,4 +26,5 @@ class ApiBatch15RouteParameterPoisoningExpansionTest extends FullSurfaceE2EContr
}
}
}
}
@@ -6,6 +6,7 @@ use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch15StudentSafetyIncidentEscalationContractTest extends FullSurfaceE2EContractCase
{
public function test_batch15_safety_incident_escalation_routes_are_not_mutable_by_parents_or_students(): void
{
$routes = $this->apiRoutesContainingAny(['incident', 'violation', 'safety', 'alert', 'flag', 'dismissal', 'late', 'absence', 'attendance']);
@@ -28,4 +29,5 @@ class ApiBatch15StudentSafetyIncidentEscalationContractTest extends FullSurfaceE
}
}
}
}
@@ -0,0 +1,32 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16AccessibilityAndClientHintsContractTest extends FullSurfaceE2EContractCase
{
public function test_batch16_client_hints_accessibility_headers_and_locale_variants_do_not_change_authorization(): void
{
$routes = $this->apiRoutesContainingAny(['students', 'parents', 'teacher', 'dashboard', 'reports', 'profile', 'settings']);
foreach (array_slice($routes, 0, 50) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$response = $this->withHeaders([
'Accept-Language' => 'ar,en;q=0.7',
'Sec-CH-UA-Mobile' => '?1',
'X-Accessibility-Mode' => 'screen-reader',
'X-Reduced-Motion' => 'true',
'X-Client-Platform' => 'ios',
])->requestAs($this->actorFor($uri), $method, $uri, $this->payloadFor($method, $uri) + [
'locale' => 'ar',
'direction' => 'rtl',
'accessibility_mode' => 'screen-reader',
]);
$this->assertControlled($response, $method, $uri);
$this->assertStringNotContainsString('authorizationexception', strtolower($response->getContent()));
}
}
}
@@ -0,0 +1,34 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16AdaptivePublicEndpointAbuseContractTest extends FullSurfaceE2EContractCase
{
public function test_batch16_public_endpoints_ignore_identity_spoofing_and_privileged_query_flags(): void
{
$routes = $this->apiRoutesContainingAny(['public', 'captcha', 'contact', 'docs', 'health', 'scan', 'scanner', 'badge', 'policy']);
foreach (array_slice($routes, 0, 45) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$response = $this->withHeaders([
'X-User-Id' => (string) $this->admin->id,
'X-Role' => 'administrator',
'Authorization' => 'Bearer forged-public-token',
])->json($method, $this->materializePath($uri), $this->payloadFor($method, $uri) + [
'admin' => true,
'include_private' => true,
'student_id' => $this->ids['studentId'],
'parent_id' => $this->ids['parentId'],
]);
$this->assertControlled($response, $method, $uri);
$body = strtolower($response->getContent());
foreach (['remember_token', 'password', 'db_password', 'app_key'] as $forbidden) {
$this->assertStringNotContainsString($forbidden, $body);
}
}
}
}
@@ -0,0 +1,34 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16DeviceTrustAndScannerReplayContractTest extends FullSurfaceE2EContractCase
{
public function test_batch16_kiosk_scanner_and_device_routes_do_not_trust_client_asserted_device_state(): void
{
$routes = $this->apiRoutesContainingAny(['scanner', 'scan', 'kiosk', 'badge', 'device', 'attendance']);
foreach (array_slice($routes, 0, 40) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
foreach (['device-admin', 'device-admin'] as $deviceId) {
$response = $this->withHeaders([
'X-Device-Id' => $deviceId,
'X-Device-Trusted' => 'true',
'X-Kiosk-Mode' => 'admin',
'X-Scan-Time' => '2099-01-01T00:00:00Z',
])->requestAs($this->actorFor($uri), $method, $uri, $this->payloadFor($method, $uri) + [
'badge_token' => 'forged-batch16-badge',
'scan_token' => 'replayed-batch16-scan',
'device_trusted' => true,
'override_attendance' => true,
]);
$this->assertControlled($response, $method, $uri);
$this->assertStringNotContainsString('undefined index', strtolower($response->getContent()));
}
}
}
}
@@ -0,0 +1,30 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16EmergencyContactAndPickupSafetyContractTest extends FullSurfaceE2EContractCase
{
public function test_batch16_pickup_guardian_and_emergency_contact_routes_reject_wrong_family_and_expired_delegations(): void
{
$routes = $this->apiRoutesContainingAny(['emergency', 'guardian', 'authorized', 'pickup', 'family', 'whatsapp']);
foreach (array_slice($routes, 0, 45) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$response = $this->requestAs($this->parent, $method, $uri, $this->payloadFor($method, $uri) + [
'family_id' => 999999,
'student_id' => 999999,
'authorized_user_id' => 999999,
'guardian_id' => 999999,
'pickup_allowed' => true,
'expires_at' => '1990-01-01',
'verified_by_admin' => true,
]);
$this->assertControlled($response, $method, $uri);
$this->assertStringNotContainsString('foreign key constraint fails', strtolower($response->getContent()));
}
}
}
@@ -0,0 +1,32 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16EventualConsistencyAndRetryContractTest extends FullSurfaceE2EContractCase
{
public function test_batch16_retry_headers_and_client_operation_ids_are_scoped_per_actor_and_route(): void
{
$routes = $this->apiRoutesContainingAny(['create', 'store', 'send', 'dispatch', 'notify', 'payment', 'attendance', 'import', 'export']);
foreach (array_slice($routes, 0, 35) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
foreach ([$this->admin, $this->parent] as $actor) {
$response = $this->withHeaders([
'Idempotency-Key' => 'batch16-shared-retry-key',
'X-Client-Operation-Id' => 'batch16-shared-operation',
'X-Retry-Attempt' => '2',
])->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + [
'client_operation_id' => 'batch16-shared-operation',
'retry_of' => 'batch16-original-operation',
]);
$this->assertControlled($response, $method, $uri);
$this->assertStringNotContainsString('duplicate key value violates unique constraint', strtolower($response->getContent()));
}
}
}
}
@@ -0,0 +1,31 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16ImportPreviewCommitSeparationContractTest extends FullSurfaceE2EContractCase
{
public function test_batch16_import_preview_payloads_cannot_force_commit_or_skip_validation(): void
{
$routes = $this->apiRoutesContainingAny(['import', 'upload', 'preview', 'commit', 'csv', 'template', 'bulk']);
foreach (array_slice($routes, 0, 45) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$response = $this->requestAs($this->actorFor($uri), $method, $uri, $this->payloadFor($method, $uri) + [
'preview' => true,
'commit' => true,
'skip_validation' => true,
'ignore_errors' => true,
'rows' => [
['email' => 'bad', 'student_id' => 999999, 'amount' => -1],
['email' => '=cmd|\' /C calc\'!A0', 'student_id' => $this->ids['studentId']],
],
]);
$this->assertControlled($response, $method, $uri);
$this->assertStringNotContainsString('maatwebsite', strtolower($response->getContent()));
}
}
}
@@ -0,0 +1,35 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16MoneyPrecisionCurrencyContractTest extends FullSurfaceE2EContractCase
{
public function test_batch16_money_routes_reject_precision_currency_and_rounding_abuse_cleanly(): void
{
$routes = $this->apiRoutesContainingAny(['finance', 'invoice', 'payment', 'refund', 'fee', 'charge', 'discount', 'voucher', 'scholarship']);
$payloads = [
['amount' => '0.001', 'currency' => 'USD'],
['amount' => '999999999999999999.99', 'currency' => 'USD'],
['amount' => '12,34', 'currency' => 'USD'],
['amount' => '25.00', 'currency' => 'BTC'],
['amount' => '-0.01', 'currency' => 'USD'],
['amount' => 'NaN', 'currency' => 'USD'],
];
foreach (array_slice($routes, 0, 45) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
foreach ($payloads as $probe) {
$response = $this->requestAs($this->actorFor($uri), $method, $uri, $this->payloadFor($method, $uri) + $probe + [
'rounding_mode' => 'client_controls_rounding',
'override_total' => true,
'ledger_balance' => -100,
]);
$this->assertControlled($response, $method, $uri);
$this->assertStringNotContainsString('division by zero', strtolower($response->getContent()));
}
}
}
}
@@ -0,0 +1,50 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16MultiStepWorkflowCompensationContractTest extends FullSurfaceE2EContractCase
{
public function test_batch16_multi_step_school_operations_fail_safely_when_later_steps_are_invalid(): void
{
$routes = $this->apiRoutesContainingAny(['enroll', 'assign', 'promotion', 'withdraw', 'invoice', 'payment', 'attendance', 'progress']);
foreach (array_slice($routes, 0, 40) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$payload = $this->payloadFor($method, $uri) + [
'student_id' => $this->ids['studentId'],
'parent_id' => $this->ids['parentId'],
'class_section_id' => $this->ids['classSectionId'],
'next_step' => 'force_invalid_follow_up',
'rollback_on_failure' => true,
'simulate_partial_failure' => true,
'invalid_follow_up_id' => 999999991,
];
$response = $this->requestAs($this->actorFor($uri), $method, $uri, $payload);
$this->assertControlled($response, $method, $uri);
$this->assertStringNotContainsString('transaction already closed', strtolower($response->getContent()));
$this->assertStringNotContainsString('deadlock found', strtolower($response->getContent()));
}
}
public function test_batch16_compensation_flags_cannot_be_user_supplied_by_low_privilege_actors(): void
{
foreach (array_slice($this->apiRoutesContainingAny(['finance', 'attendance', 'promotion', 'school-years', 'students']), 0, 35) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$response = $this->requestAs($this->parent, $method, $uri, $this->payloadFor($method, $uri) + [
'force_commit' => true,
'skip_compensation' => true,
'rollback_on_failure' => false,
'transaction_state' => 'committed',
'audit_status' => 'approved',
]);
$this->assertControlled($response, $method, $uri);
$this->assertStringNotContainsString('skip_compensation', strtolower($response->getContent()));
}
}
}
@@ -0,0 +1,29 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16NotificationDeliverySuppressionContractTest extends FullSurfaceE2EContractCase
{
public function test_batch16_notification_suppression_and_delivery_flags_cannot_be_overridden_by_clients(): void
{
$routes = $this->apiRoutesContainingAny(['notify', 'notification', 'email', 'sms', 'whatsapp', 'broadcast', 'message', 'support']);
foreach (array_slice($routes, 0, 45) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$response = $this->requestAs($this->parent, $method, $uri, $this->payloadFor($method, $uri) + [
'force_send' => true,
'bypass_opt_out' => true,
'ignore_quiet_hours' => true,
'recipient_ids' => ['*'],
'channel' => 'all',
'from' => "attacker@example.test\r\nBcc: victim@example.test",
]);
$this->assertControlled($response, $method, $uri);
$this->assertStringNotContainsString('swift_', strtolower($response->getContent()));
}
}
}
@@ -0,0 +1,35 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16OptimisticLockingAndVersionFieldContractTest extends FullSurfaceE2EContractCase
{
public function test_batch16_version_and_lock_fields_cannot_be_forged_to_override_newer_records(): void
{
$routes = $this->apiRoutesContainingAny(['students', 'users', 'attendance', 'scores', 'finance', 'inventory', 'settings', 'profile']);
foreach (array_slice($routes, 0, 45) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH'], true)) {
continue;
}
$uri = $route->uri();
$response = $this->withHeaders([
'If-Match' => '"stale-batch16-etag"',
'If-Unmodified-Since' => 'Mon, 01 Jan 1990 00:00:00 GMT',
])->requestAs($this->actorFor($uri), $method, $uri, $this->payloadFor($method, $uri) + [
'version' => -1,
'lock_version' => -999,
'updated_at' => '1990-01-01T00:00:00Z',
'force_overwrite' => true,
'ignore_conflicts' => true,
]);
$this->assertControlled($response, $method, $uri);
$this->assertStringNotContainsString('optimisticlockexception', strtolower($response->getContent()));
}
}
}
@@ -0,0 +1,36 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16PermissionDriftDetectionContractTest extends FullSurfaceE2EContractCase
{
public function test_batch16_permission_names_and_route_families_do_not_drift_into_unguarded_mutations(): void
{
$mutatingRoutes = array_filter($this->apiRoutes(), fn ($route) => in_array($this->primaryMethod($route), ['POST', 'PUT', 'PATCH', 'DELETE'], true));
$sensitiveNeedles = ['users', 'roles', 'permissions', 'settings', 'finance', 'inventory', 'school-year', 'attendance', 'scores'];
foreach (array_slice($mutatingRoutes, 0, 100) as $route) {
$uri = $route->uri();
$isSensitive = false;
foreach ($sensitiveNeedles as $needle) {
$isSensitive = $isSensitive || str_contains($uri, $needle);
}
if (! $isSensitive) {
continue;
}
$method = $this->primaryMethod($route);
$response = $this->requestAs($this->studentUser, $method, $uri, $this->payloadFor($method, $uri) + [
'permissions' => ['*'],
'role' => 'administrator',
'can_manage_everything' => true,
]);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' should not crash during permission-drift probes.');
}
}
}
@@ -0,0 +1,31 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16ReadModelProjectionConsistencyContractTest extends FullSurfaceE2EContractCase
{
public function test_batch16_summary_dashboard_and_projection_routes_resist_privileged_include_flags(): void
{
$routes = $this->apiRoutesContainingAny(['dashboard', 'summary', 'analytics', 'report', 'overview', 'statistics', 'projection']);
foreach (array_slice($routes, 0, 50) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$response = $this->requestAs($this->teacher, $method, $uri, $this->payloadFor($method, $uri) + [
'include' => 'all,finance,parents,passwords,deleted,audit',
'scope' => 'school',
'class_section_id' => 999999,
'parent_id' => 999999,
'student_id' => 999999,
]);
$this->assertControlled($response, $method, $uri);
$body = strtolower($response->getContent());
foreach (['remember_token', 'db_password', 'app_key', 'secret'] as $forbidden) {
$this->assertStringNotContainsString($forbidden, $body);
}
}
}
}
@@ -0,0 +1,49 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16RegressionHyperSweepTest extends FullSurfaceE2EContractCase
{
public function test_batch16_hyper_sweep_applies_cross_cutting_hostile_payloads_to_high_risk_routes(): void
{
$routes = $this->apiRoutesContainingAny([
'auth', 'users', 'students', 'parents', 'family', 'guardian', 'attendance', 'scores', 'report',
'finance', 'invoice', 'payment', 'refund', 'inventory', 'import', 'export', 'scanner', 'settings', 'notification',
]);
$payload = [
'id' => '../../.env',
'user_id' => $this->admin->id,
'parent_id' => 999999,
'student_id' => 999999,
'role' => 'administrator',
'permissions' => ['*'],
'amount' => 'NaN',
'date' => '9999-99-99',
'include' => 'passwords,secrets,deleted,audit,all',
'force' => true,
'skip_validation' => true,
'bypass_authorization' => true,
'message' => '<script>alert("batch16")</script>',
];
foreach (array_slice($routes, 0, 120) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$response = $this->withHeaders([
'X-Role' => 'administrator',
'X-Forwarded-User' => (string) $this->admin->id,
'X-Client-Time' => '2099-01-01T00:00:00Z',
'Idempotency-Key' => 'batch16-hyper-sweep',
])->requestAs($this->actorFor($uri), $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'vendor/laravel', 'app_key', 'db_password', 'remember_token'] as $forbidden) {
$this->assertStringNotContainsString($forbidden, $body, $method.' '.$uri.' leaked '.$forbidden);
}
}
}
}
@@ -0,0 +1,29 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16RouteGraphInvariantRegisterTest extends FullSurfaceE2EContractCase
{
public function test_batch16_route_graph_has_contract_pressure_for_new_risk_families(): void
{
$riskFamilies = [
'workflow-compensation' => ['enroll', 'assign', 'promotion', 'withdraw'],
'eventual-consistency' => ['send', 'dispatch', 'notify', 'import', 'export'],
'optimistic-locking' => ['students', 'users', 'attendance', 'scores', 'finance'],
'money-precision' => ['finance', 'invoice', 'payment', 'refund', 'fee'],
'temporal-consistency' => ['attendance', 'calendar', 'school-year', 'semester'],
'device-trust' => ['scanner', 'scan', 'kiosk', 'badge', 'device'],
'import-preview' => ['import', 'upload', 'preview', 'commit', 'bulk'],
'notification-suppression' => ['notify', 'notification', 'email', 'whatsapp'],
];
foreach ($riskFamilies as $family => $needles) {
$this->assertNotEmpty(
$this->apiRoutesContainingAny($needles),
'Batch 16 risk family '.$family.' should map to at least one registered API route or be consciously removed from the risk register.'
);
}
}
}
@@ -0,0 +1,47 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16SchemaEvolutionDeprecationContractTest extends FullSurfaceE2EContractCase
{
public function test_batch16_deprecated_aliases_remain_controlled_and_do_not_return_incompatible_shapes(): void
{
$routes = $this->apiRoutesContainingAny(['legacy', 'alias', 'teacher', 'login', 'auth', 'v1']);
foreach (array_slice($routes, 0, 50) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$response = $this->withHeaders([
'X-Api-Version' => '2019-01-01',
'Accept' => 'application/json',
])->requestAs($this->actorFor($uri), $method, $uri, $this->payloadFor($method, $uri) + [
'legacy_client' => true,
'response_shape' => 'old',
]);
$this->assertControlled($response, $method, $uri);
$this->assertJsonResponseWhenSuccessful($response, $method.' '.$uri);
}
}
public function test_batch16_removed_or_deprecated_fields_are_not_required_for_current_clients(): void
{
foreach (array_slice($this->apiRoutes(), 0, 80) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH'], true)) {
continue;
}
$uri = $route->uri();
$payload = $this->payloadFor($method, $uri);
unset($payload['legacy_id'], $payload['old_student_id'], $payload['deprecated_role']);
$response = $this->requestAs($this->actorFor($uri), $method, $uri, $payload + [
'client_contract_version' => 'current',
]);
$this->assertControlled($response, $method, $uri);
}
}
}
@@ -0,0 +1,34 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch16TemporalConsistencyAndClockSkewContractTest extends FullSurfaceE2EContractCase
{
public function test_batch16_clock_skew_and_temporal_boundaries_do_not_bypass_business_rules(): void
{
$routes = $this->apiRoutesContainingAny(['attendance', 'calendar', 'school-year', 'promotion', 'payment', 'invoice', 'deadline', 'schedule', 'semester']);
$payloads = [
['date' => '1969-12-31', 'time' => '23:59:59'],
['date' => '2038-01-19', 'time' => '03:14:08'],
['date' => '9999-12-31', 'time' => '24:01'],
['date' => '2025-03-09', 'timezone' => 'America/New_York'],
['start_date' => '2025-12-31', 'end_date' => '2025-01-01'],
];
foreach (array_slice($routes, 0, 45) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
foreach ($payloads as $probe) {
$response = $this->withHeader('X-Client-Time', '2099-01-01T00:00:00Z')
->requestAs($this->actorFor($uri), $method, $uri, $this->payloadFor($method, $uri) + $probe + [
'client_clock_trusted' => true,
'server_time_override' => '2099-01-01T00:00:00Z',
]);
$this->assertControlled($response, $method, $uri);
$this->assertStringNotContainsString('could not parse', strtolower($response->getContent()));
}
}
}
}
@@ -0,0 +1,39 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17AcademicPrerequisiteAndDependencyContractTest extends FullSurfaceE2EContractCase
{
public function test_academic_actions_do_not_skip_required_prerequisites_or_locks(): void
{
$routes = $this->apiRoutesContainingAny(['scores', 'grading', 'report-card', 'homework', 'quiz', 'project', 'midterm', 'final', 'promotion']);
$payload = [
'student_id' => $this->ids['studentId'],
'class_section_id' => $this->ids['classSectionId'],
'score' => 100,
'finalized' => true,
'locked' => false,
'teacher_approved' => true,
'admin_approved' => true,
'skip_missing_assignments' => true,
'force_publish' => true,
'override_prerequisites' => true,
];
foreach (array_slice($routes, 0, 90) as $route) {
$method = $this->primaryMethod($route);
if ($method === 'GET') {
continue;
}
$uri = $route->uri();
$response = $this->requestAs($this->teacher, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$body = strtolower($response->getContent());
$this->assertStringNotContainsString('sqlstate', $body, $method.' '.$uri.' leaked SQL on academic dependency probe.');
}
}
}
@@ -0,0 +1,44 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17ApiContractDiffAndCriticalKeyRegisterTest extends FullSurfaceE2EContractCase
{
public function test_critical_identity_finance_attendance_and_academic_payloads_keep_contract_anchors(): void
{
$expectations = [
'auth' => ['user'],
'users' => ['id'],
'students' => ['id'],
'parents' => ['id'],
'attendance' => ['data'],
'invoice' => ['data'],
'payment' => ['data'],
'scores' => ['data'],
'report-card' => ['data'],
];
foreach ($expectations as $needle => $anchors) {
foreach (array_slice($this->apiRoutes($needle), 0, 10) as $route) {
if ($this->primaryMethod($route) !== 'GET') {
continue;
}
$uri = $route->uri();
$response = $this->probeRoute($route);
$this->assertControlled($response, 'GET', $uri);
if ($response->getStatusCode() >= 200 && $response->getStatusCode() < 300 && $this->isJsonString($response->getContent())) {
$json = $response->json();
$jsonText = strtolower(json_encode($json));
foreach ($anchors as $anchor) {
$this->assertStringContainsString($anchor, $jsonText, $uri.' successful response should preserve contract anchor '.$anchor);
}
}
}
}
}
}
@@ -0,0 +1,36 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17AttendanceTamperWindowContractTest extends FullSurfaceE2EContractCase
{
public function test_attendance_cutoff_backdate_future_and_reason_requirements_are_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['attendance', 'late', 'dismissal', 'absence', 'violation', 'incident']);
$payloads = [
['date' => '1999-01-01', 'reason' => '', 'status' => 'present', 'force' => true],
['date' => '2099-12-31', 'arrival_time' => '27:99', 'status' => 'late'],
['date' => '2025-10-05', 'dismissal_time' => '00:00', 'guardian_approved' => true, 'verified_by' => $this->admin->id],
['date' => '2025-10-05', 'status' => 'excused', 'excuse_document_id' => '../../.env'],
];
foreach (array_slice($routes, 0, 70) as $route) {
$method = $this->primaryMethod($route);
if ($method === 'GET') {
continue;
}
foreach ($payloads as $payload) {
$uri = $route->uri();
$response = $this->requestAs($this->parent, $method, $uri, $this->payloadFor($method, $uri) + $payload + [
'student_id' => $this->ids['studentId'],
'class_section_id' => $this->ids['classSectionId'],
]);
$this->assertControlled($response, $method, $uri);
}
}
}
}
@@ -0,0 +1,36 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17CommunicationRecipientExpansionContractTest extends FullSurfaceE2EContractCase
{
public function test_message_broadcast_and_notification_recipient_scope_cannot_be_expanded_by_payload(): void
{
$routes = $this->apiRoutesContainingAny(['message', 'notification', 'broadcast', 'email', 'whatsapp', 'support', 'contact']);
$payload = [
'recipient_id' => $this->admin->id,
'recipient_ids' => [$this->admin->id, $this->teacher->id, $this->parent->id, $this->studentUser->id],
'send_to_all' => true,
'all_school' => true,
'include_admins' => true,
'bcc' => 'attacker@example.test',
'reply_to' => "evil@example.test\r\nBcc: attacker@example.test",
'subject' => '<script>alert("batch17")</script>',
'message' => '{{ config("app.key") }}',
];
foreach (array_slice($routes, 0, 90) as $route) {
$method = $this->primaryMethod($route);
if ($method === 'GET') {
continue;
}
$uri = $route->uri();
$response = $this->requestAs($this->parent, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
}
}
}
@@ -0,0 +1,35 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17DataLineageAndSourceAttributionContractTest extends FullSurfaceE2EContractCase
{
public function test_client_supplied_lineage_and_audit_source_fields_are_not_authoritative(): void
{
$routes = $this->apiRoutesContainingAny(['import', 'sync', 'students', 'finance', 'attendance', 'inventory', 'messages', 'settings']);
$payload = [
'source' => 'trusted_admin_console',
'source_system' => 'production-migration',
'created_by' => $this->admin->id,
'updated_by' => $this->admin->id,
'approved_by' => $this->admin->id,
'verified_by' => $this->admin->id,
'lineage' => ['system' => 'database', 'trusted' => true, 'bypass_review' => true],
];
foreach (array_slice($routes, 0, 80) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH'], true)) {
continue;
}
$uri = $route->uri();
$response = $this->requestAs($this->parent, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed on forged lineage.');
}
}
}
@@ -0,0 +1,34 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17ExportColumnLevelSecurityContractTest extends FullSurfaceE2EContractCase
{
public function test_export_field_selection_cannot_request_secrets_or_cross_scope_columns(): void
{
$routes = $this->apiRoutesContainingAny(['export', 'download', 'report', 'print', 'receipt', 'certificate', 'transcript']);
$payload = [
'fields' => ['*', 'password', 'remember_token', 'api_token', 'ssn', 'medical_notes', 'admin_notes'],
'columns' => '*,password,remember_token,api_token,db_password,app_key',
'include' => 'users,secrets,deleted,archived,audit,all_families',
'format' => 'csv',
'scope' => 'global',
'all_school' => true,
];
foreach (array_slice($routes, 0, 90) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$response = $this->requestAs($this->parent, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$body = strtolower($response->getContent());
foreach (['app_key', 'db_password', 'remember_token', 'api_token'] as $secret) {
$this->assertStringNotContainsString($secret, $body, $method.' '.$uri.' leaked '.$secret.' through export fields.');
}
}
}
}
@@ -0,0 +1,38 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17ExternalIdAndNaturalKeyCollisionContractTest extends FullSurfaceE2EContractCase
{
public function test_external_ids_student_numbers_and_emails_are_not_cross_domain_authority(): void
{
$routes = $this->apiRoutesContainingAny(['students', 'parents', 'users', 'family', 'guardian', 'enrollment', 'finance']);
$payload = [
'external_id' => 'EXT-'.$this->ids['studentId'],
'student_number' => '9701',
'email' => $this->admin->email,
'parent_email' => $this->parent->email,
'user_email' => $this->teacher->email,
'natural_key' => '9701|'.$this->parent->email,
'sync_key' => 'administrator:'.$this->admin->id,
];
foreach (array_slice($routes, 0, 90) as $route) {
$method = $this->primaryMethod($route);
if ($method === 'GET') {
continue;
}
$uri = $route->uri();
$response = $this->requestAs($this->actorFor($uri), $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$body = strtolower($response->getContent());
$this->assertStringNotContainsString('duplicate entry', $body, $method.' '.$uri.' leaked duplicate key internals.');
$this->assertStringNotContainsString('integrity constraint', $body, $method.' '.$uri.' leaked integrity internals.');
}
}
}
@@ -0,0 +1,38 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17FinancialPeriodCloseAndAdjustmentContractTest extends FullSurfaceE2EContractCase
{
public function test_closed_period_adjustments_writeoffs_and_credit_transfers_are_guarded(): void
{
$routes = $this->apiRoutesContainingAny(['finance', 'invoice', 'payment', 'refund', 'credit', 'writeoff', 'adjustment', 'ledger']);
$payload = [
'invoice_id' => $this->ids['invoiceId'],
'parent_id' => $this->ids['parentId'],
'student_id' => $this->ids['studentId'],
'amount' => '-9999.99',
'period' => '2020-01',
'closed_period' => true,
'force_adjustment' => true,
'skip_reconciliation' => true,
'credit_to_parent_id' => 999999,
'write_off_reason' => '',
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) {
continue;
}
$uri = $route->uri();
$response = $this->requestAs($this->admin, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertStringNotContainsString('division by zero', strtolower($response->getContent()), $method.' '.$uri.' leaked math internals.');
}
}
}
@@ -0,0 +1,36 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17InventoryAssetCustodyContractTest extends FullSurfaceE2EContractCase
{
public function test_inventory_asset_custody_transfers_and_disposal_are_not_client_authorized(): void
{
$routes = $this->apiRoutesContainingAny(['inventory', 'asset', 'custody', 'supplier', 'supply', 'purchase', 'receiving']);
$payload = [
'item_id' => $this->ids['itemId'],
'supplier_id' => $this->ids['supplierId'],
'quantity' => -50,
'custodian_user_id' => $this->studentUser->id,
'assigned_to' => $this->studentUser->id,
'disposed' => true,
'write_off' => true,
'approved_by' => $this->admin->id,
'skip_stock_check' => true,
];
foreach (array_slice($routes, 0, 80) as $route) {
$method = $this->primaryMethod($route);
if ($method === 'GET') {
continue;
}
$uri = $route->uri();
$response = $this->requestAs($this->teacher, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
}
}
}
@@ -0,0 +1,28 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17MultiGuardBoundaryContractTest extends FullSurfaceE2EContractCase
{
public function test_api_guard_boundaries_are_not_crossed_by_session_or_web_identity(): void
{
$routes = $this->apiRoutesContainingAny(['admin', 'users', 'finance', 'settings', 'school-years', 'inventory']);
foreach (array_slice($routes, 0, 70) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$this->actingAs($this->parent, 'web');
$response = $this->json($method, $this->materializePath($uri), $this->payloadFor($method, $uri), [
'Accept' => 'application/json',
'X-Requested-With' => 'XMLHttpRequest',
]);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed under web guard identity.');
}
}
}
@@ -0,0 +1,39 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17PartialFailureErrorDetailRedactionContractTest extends FullSurfaceE2EContractCase
{
public function test_bulk_partial_failure_details_are_actionable_without_leaking_database_or_secret_details(): void
{
$routes = $this->apiRoutesContainingAny(['bulk', 'batch', 'import', 'assign', 'sync', 'notify', 'export']);
$payload = [
'items' => [
['id' => $this->ids['studentId'], 'student_id' => $this->ids['studentId'], 'amount' => 10],
['id' => 999999999, 'student_id' => 999999999, 'amount' => 'NaN'],
['id' => '../../.env', 'student_id' => '<script>alert(1)</script>', 'amount' => -9999],
],
'continue_on_error' => true,
'return_failures' => true,
'include_stack' => true,
];
foreach (array_slice($routes, 0, 70) as $route) {
$method = $this->primaryMethod($route);
if ($method === 'GET') {
continue;
}
$uri = $route->uri();
$response = $this->requestAs($this->admin, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'vendor/laravel', 'app_key', 'db_password'] as $forbidden) {
$this->assertStringNotContainsString($forbidden, $body, $method.' '.$uri.' leaked '.$forbidden.' in partial failure response.');
}
}
}
}
@@ -0,0 +1,31 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17PerResourceRateLimitIdentityContractTest extends FullSurfaceE2EContractCase
{
public function test_repeated_sensitive_actions_are_scoped_by_actor_resource_and_operation(): void
{
$routes = $this->apiRoutesContainingAny(['login', 'password', 'verification', 'contact', 'support', 'scanner', 'badge', 'payment', 'refund']);
foreach (array_slice($routes, 0, 50) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$payload = $this->payloadFor($method, $uri) + [
'client_operation_id' => 'batch17-repeat',
'resource_id' => $this->ids['studentId'],
'attempt' => 1,
];
$first = $this->requestAs($this->actorFor($uri), $method, $uri, $payload);
$second = $this->requestAs($this->actorFor($uri), $method, $uri, $payload + ['attempt' => 2]);
$this->assertControlled($first, $method, $uri);
$this->assertControlled($second, $method, $uri);
$this->assertNotEquals(500, $second->getStatusCode(), $method.' '.$uri.' crashed on repeated sensitive action.');
}
}
}
@@ -0,0 +1,53 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17RegressionOmegaSweepTest extends FullSurfaceE2EContractCase
{
public function test_batch17_omega_sweep_combines_headers_payloads_queries_and_route_parameters(): void
{
$routes = $this->apiRoutesContainingAny([
'auth', 'users', 'students', 'parents', 'family', 'attendance', 'scores', 'report', 'finance', 'inventory',
'message', 'notification', 'import', 'export', 'settings', 'webhook', 'scanner', 'audit', 'job', 'backup',
]);
$payload = [
'id' => '../../.env',
'ids' => [$this->ids['studentId'], 999999, '../../.env'],
'student_id' => 999999,
'parent_id' => 999999,
'teacher_id' => $this->admin->id,
'amount' => 'Infinity',
'date' => '0000-00-00',
'status' => 'administrator_approved',
'role' => 'administrator',
'permissions' => ['*'],
'force' => true,
'skip_validation' => true,
'bypass_authorization' => true,
'include' => 'all,secrets,deleted,audit,passwords',
'message' => '<img src=x onerror=alert("batch17")>',
];
foreach (array_slice($routes, 0, 140) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$response = $this->withHeaders([
'X-Internal-Request' => 'true',
'X-User-Role' => 'administrator',
'X-HTTP-Method-Override' => 'DELETE',
'Idempotency-Key' => 'batch17-omega',
'X-Request-Id' => "batch17\r\nInjected: true",
])->requestAs($this->actorFor($uri), $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'vendor/laravel', 'app_key', 'db_password', 'remember_token'] as $forbidden) {
$this->assertStringNotContainsString($forbidden, $body, $method.' '.$uri.' leaked '.$forbidden.' in batch17 omega sweep.');
}
}
}
}
@@ -0,0 +1,35 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17SearchRankingAndPermissionFilterContractTest extends FullSurfaceE2EContractCase
{
public function test_search_autocomplete_and_lookup_do_not_bypass_permission_filters(): void
{
$routes = $this->apiRoutesContainingAny(['search', 'lookup', 'autocomplete', 'directory', 'students', 'users', 'parents']);
$queries = [
['q' => '*', 'include' => 'all,deleted,secrets'],
['search' => $this->admin->email, 'scope' => 'global'],
['term' => '%', 'role' => 'administrator'],
['q' => "' OR '1'='1", 'fields' => 'password,remember_token'],
];
foreach (array_slice($routes, 0, 100) as $route) {
$uri = $route->uri();
if ($this->primaryMethod($route) !== 'GET') {
continue;
}
foreach ($queries as $query) {
$response = $this->requestAs($this->studentUser, 'GET', $uri, $query);
$this->assertControlled($response, 'GET', $uri);
$body = strtolower($response->getContent());
$this->assertStringNotContainsString('remember_token', $body, 'Search leaked remember_token for '.$uri);
$this->assertStringNotContainsString('password', $body, 'Search leaked password for '.$uri);
}
}
}
}
@@ -0,0 +1,36 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17StudentIdentityMergeSplitContractTest extends FullSurfaceE2EContractCase
{
public function test_merge_split_and_duplicate_student_identity_routes_reject_unsafe_payloads(): void
{
$routes = $this->apiRoutesContainingAny(['merge', 'split', 'duplicate', 'dedupe', 'students', 'identity', 'profile']);
$payload = [
'primary_student_id' => $this->ids['studentId'],
'secondary_student_id' => $this->ids['studentId'],
'target_student_id' => 999999,
'source_student_id' => $this->ids['studentId'],
'parent_id' => 999999,
'preserve_finance' => false,
'preserve_attendance' => false,
'force_merge' => true,
'skip_guardian_review' => true,
];
foreach (array_slice($routes, 0, 70) as $route) {
$method = $this->primaryMethod($route);
if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) {
continue;
}
$uri = $route->uri();
$response = $this->requestAs($this->parent, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
}
}
}
@@ -0,0 +1,36 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch17ZeroTrustInternalHeaderContractTest extends FullSurfaceE2EContractCase
{
public function test_internal_proxy_and_service_headers_do_not_grant_privilege(): void
{
$routes = $this->apiRoutesContainingAny([
'users', 'roles', 'permissions', 'school-years', 'finance', 'inventory', 'settings', 'audit', 'admin', 'reports',
]);
$spoofHeaders = [
'X-Internal-Request' => 'true',
'X-Service-Name' => 'trusted-admin-service',
'X-Authenticated-User' => (string) $this->admin->id,
'X-User-Role' => 'administrator',
'X-Original-URI' => '/api/v1/admin/users',
'X-Forwarded-For' => '127.0.0.1',
'X-Real-IP' => '10.0.0.1',
'X-Envoy-External-Address' => '127.0.0.1',
];
foreach (array_slice($routes, 0, 80) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$response = $this->withHeaders($spoofHeaders)->requestAs($this->studentUser, $method, $uri, $this->payloadFor($method, $uri));
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' trusted spoofed internal headers.');
}
}
}
@@ -0,0 +1,39 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18ApiKeyServiceAccountAndMachineIdentityContractTest extends FullSurfaceE2EContractCase
{
public function test_batch18_apikeyserviceaccountandmachineidentity_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['admin', 'users', 'finance', 'inventory', 'settings', 'reports', 'school-years', 'permissions']);
$payload = [
'api_key' => 'forged-admin-key',
'service_account' => 'scheduler@internal',
'machine_identity' => 'finance-worker',
'client_cert_subject' => 'CN=admin-service',
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 18 API key service account and machine identity headers probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 API key service account and machine identity headers probe.');
}
}
}
}
@@ -0,0 +1,43 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18CanonicalIdentifierImmutabilityContractTest extends FullSurfaceE2EContractCase
{
public function test_batch18_canonicalidentifierimmutability_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['students', 'users', 'parents', 'family', 'guardian', 'invoices', 'payments', 'attendance', 'scores']);
$payload = [
'id' => 999999,
'uuid' => '00000000-0000-0000-0000-000000000000',
'user_id' => 999999,
'student_id' => 999999,
'parent_id' => 999999,
'invoice_id' => 999999,
'created_at' => '1999-01-01T00:00:00Z',
'updated_at' => '2099-01-01T00:00:00Z',
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 18 canonical identifier immutability probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 canonical identifier immutability probe.');
}
}
}
}
@@ -0,0 +1,41 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18ClassCapacityWaitlistAndScheduleConflictContractTest extends FullSurfaceE2EContractCase
{
public function test_batch18_classcapacitywaitlistandscheduleconflict_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['classes', 'class-sections', 'assign', 'enrollment', 'waitlist', 'schedule', 'calendar', 'teacher']);
$payload = [
'capacity' => -1,
'force_over_capacity' => true,
'skip_waitlist' => true,
'override_schedule_conflict' => true,
'start_time' => '25:61',
'end_time' => '00:00',
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 18 class capacity waitlist and schedule conflicts probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 class capacity waitlist and schedule conflicts probe.');
}
}
}
}
@@ -0,0 +1,42 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18ConsentRevocationAndCommunicationComplianceContractTest extends FullSurfaceE2EContractCase
{
public function test_batch18_consentrevocationandcommunicationcompliance_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['notification', 'email', 'whatsapp', 'sms', 'message', 'broadcast', 'contact', 'support', 'template']);
$payload = [
'channel' => 'all',
'ignore_opt_out' => true,
'bypass_quiet_hours' => true,
'consent_revoked' => false,
'unsubscribe_token' => 'forged-token',
'force_send' => true,
'reply_to' => 'attacker@example.test\r\nBcc: all@example.test',
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 18 consent revocation and communication compliance probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 consent revocation and communication compliance probe.');
}
}
}
}
@@ -0,0 +1,31 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18CrossModuleBusinessInvariantRegisterTest extends FullSurfaceE2EContractCase
{
public function test_batch18_business_invariant_route_families_remain_present(): void
{
$families = [
'identity immutability' => ['users', 'students', 'parents'],
'role and privilege boundaries' => ['roles', 'permissions', 'admin'],
'consent and communication compliance' => ['notification', 'message', 'email', 'whatsapp'],
'export auditability' => ['export', 'download', 'print', 'report'],
'safety and medical minimization' => ['emergency', 'guardian', 'incident', 'attendance'],
'finance dispute integrity' => ['payment', 'refund', 'transaction', 'finance'],
'class capacity and schedule safety' => ['classes', 'schedule', 'calendar', 'assign'],
'webhook ordering' => ['webhook', 'callback', 'provider'],
'file quarantine safety' => ['upload', 'import', 'document', 'media'],
];
foreach ($families as $label => $needles) {
$this->assertNotEmpty(
$this->apiRoutesContainingAny($needles),
'Batch 18 invariant family missing or renamed without updating risk register: '.$label
);
}
}
}
@@ -0,0 +1,43 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18ExportWatermarkAndAuditabilityContractTest extends FullSurfaceE2EContractCase
{
public function test_batch18_exportwatermarkandauditability_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['export', 'download', 'print', 'pdf', 'report', 'certificate', 'receipt', 'transcript']);
$payload = [
'columns' => ['password', 'remember_token', 'api_token', 'ssn', 'medical_notes', 'deleted_at'],
'include_deleted' => true,
'include_archived' => true,
'scope' => 'global',
'all_school' => true,
'watermark' => '',
'generated_by' => 'Principal',
'audit_id' => 'client-forged-audit-id',
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 18 export watermark and auditability probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 export watermark and auditability probe.');
}
}
}
}
@@ -0,0 +1,42 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18FeatureEntitlementAndLicenseBoundaryContractTest extends FullSurfaceE2EContractCase
{
public function test_batch18_featureentitlementandlicenseboundary_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['feature', 'license', 'settings', 'configuration', 'billing', 'finance', 'report', 'export', 'module']);
$payload = [
'feature_enabled' => true,
'licensed_modules' => ['finance', 'reports', 'admin', 'all'],
'plan' => 'enterprise',
'trial' => false,
'entitlement' => '*',
'billing_status' => 'active',
'override_license' => true,
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 18 feature entitlement and license boundaries probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 feature entitlement and license boundaries probe.');
}
}
}
}
@@ -0,0 +1,42 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18FileVirusScanAndQuarantineContractTest extends FullSurfaceE2EContractCase
{
public function test_batch18_filevirusscanandquarantine_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['upload', 'import', 'file', 'document', 'attachment', 'media', 'certificate', 'receipt']);
$payload = [
'filename' => 'invoice.pdf.exe',
'mime_type' => 'application/pdf',
'extension' => 'pdf',
'virus_scan_status' => 'clean',
'quarantined' => false,
'storage_path' => '../../.env',
'disk' => 'local',
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 18 file virus scan and quarantine metadata probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 file virus scan and quarantine metadata probe.');
}
}
}
}
@@ -0,0 +1,43 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18FinanceChargebackDisputeContractTest extends FullSurfaceE2EContractCase
{
public function test_batch18_financechargebackdispute_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['chargeback', 'dispute', 'reversal', 'refund', 'payment', 'transaction', 'ledger', 'paypal', 'stripe']);
$payload = [
'payment_id' => 1,
'parent_id' => 999999,
'student_id' => 999999,
'amount' => '-999999.99',
'provider_status' => 'succeeded',
'provider_transaction_id' => 'replayed-provider-id',
'chargeback_won' => true,
'skip_reconciliation' => true,
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 18 finance chargeback dispute and reversal integrity probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 finance chargeback dispute and reversal integrity probe.');
}
}
}
}
@@ -0,0 +1,42 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18OrphanedRecordAndCascadeBoundaryContractTest extends FullSurfaceE2EContractCase
{
public function test_batch18_orphanedrecordandcascadeboundary_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['delete', 'destroy', 'remove', 'detach', 'unassign', 'withdraw', 'archive', 'purge']);
$payload = [
'cascade' => true,
'force' => true,
'hard_delete' => true,
'delete_children' => true,
'preserve_audit' => false,
'reason' => '',
'ids' => [1, 2, 3, 999999],
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 18 unsafe cascade and orphaned records probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 unsafe cascade and orphaned records probe.');
}
}
}
}
@@ -0,0 +1,41 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18PrivilegeBoundaryAfterRoleMutationContractTest extends FullSurfaceE2EContractCase
{
public function test_batch18_privilegeboundaryafterrolemutation_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['roles', 'permissions', 'users', 'admin', 'settings', 'finance', 'inventory', 'school-years']);
$payload = [
'role' => 'administrator',
'roles' => ['administrator', 'super-admin'],
'permissions' => ['*', 'admin.*', 'finance.refund'],
'is_admin' => true,
'can_impersonate' => true,
'effective_role' => 'administrator',
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 18 role mutation and effective privilege boundaries probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 role mutation and effective privilege boundaries probe.');
}
}
}
}
@@ -0,0 +1,52 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18RegressionFinalitySweepTest extends FullSurfaceE2EContractCase
{
public function test_batch18_combined_hostile_payload_sweep_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny([
'auth', 'users', 'roles', 'permissions', 'students', 'parents', 'family', 'guardian',
'attendance', 'incident', 'scores', 'grading', 'report', 'finance', 'payments', 'refund',
'inventory', 'classes', 'calendar', 'messages', 'notification', 'export', 'upload', 'webhook',
'settings', 'audit', 'logs', 'feature', 'license',
]);
$payload = [
'id' => 999999,
'role' => 'administrator',
'permissions' => ['*'],
'student_id' => 999999,
'parent_id' => 999999,
'include_deleted' => true,
'all_school' => true,
'force' => true,
'skip_validation' => true,
'amount' => '-999999999999.999',
'date' => '2099-99-99',
'timezone' => 'Bad/Zone',
'message' => '<script>alert("batch18")</script>',
'file' => '../../.env',
'redirect' => 'https://evil.example.test',
'callback_url' => 'http://169.254.169.254/latest/meta-data/',
'audit_id' => 'client-forged',
'watermark' => '',
];
foreach (array_slice($routes, 0, 140) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$response = $this->requestAs($this->actorFor($uri), $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'massassignmentexception'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 regression sweep.');
}
}
}
}
@@ -0,0 +1,41 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18ReportDrillthroughAndRowLevelSecurityContractTest extends FullSurfaceE2EContractCase
{
public function test_batch18_reportdrillthroughandrowlevelsecurity_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['reports', 'analytics', 'dashboard', 'summary', 'drilldown', 'export']);
$payload = [
'scope' => 'all',
'drilldown' => true,
'group_by' => 'student_id,parent_id,teacher_id,payment_method',
'include' => 'students.parents.users.payments.refunds.medical_notes',
'fields' => 'id,email,phone,balance,medical_notes,password,remember_token',
'all_school_years' => true,
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 18 report drillthrough and row-level security probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 report drillthrough and row-level security probe.');
}
}
}
}
@@ -0,0 +1,37 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18StudentMedicalAndSafetyDataMinimizationContractTest extends FullSurfaceE2EContractCase
{
public function test_batch18_studentmedicalandsafetydataminimization_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['students', 'attendance', 'incident', 'safety', 'emergency', 'guardian', 'family', 'reports']);
$payload = [
'include' => 'medical_notes,diagnosis,medication,allergy,custody_order,restraining_order,ssn',
'fields' => 'medical_notes,diagnosis,medication,allergy,custody_order,restraining_order,ssn',
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 18 student medical and safety data minimization probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 student medical and safety data minimization probe.');
}
}
}
}
@@ -0,0 +1,42 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18TemporalAuditAndTimezoneForgeryContractTest extends FullSurfaceE2EContractCase
{
public function test_batch18_temporalauditandtimezoneforgery_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['attendance', 'finance', 'payments', 'refunds', 'audit', 'logs', 'reports', 'school-years', 'promotion']);
$payload = [
'created_at' => '1970-01-01T00:00:00Z',
'updated_at' => '2099-12-31T23:59:59Z',
'occurred_at' => '2099-12-31T23:59:59Z',
'effective_at' => '1900-01-01T00:00:00Z',
'timezone' => 'Etc/GMT+25',
'client_timezone' => 'Mars/Olympus_Mons',
'server_time' => '2099-12-31T23:59:59Z',
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 18 temporal audit and timezone forgery probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 temporal audit and timezone forgery probe.');
}
}
}
}
@@ -0,0 +1,41 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18TwoPersonApprovalAndSegregationContractTest extends FullSurfaceE2EContractCase
{
public function test_batch18_twopersonapprovalandsegregation_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['approve', 'approval', 'review', 'refund', 'reimbursement', 'write-off', 'void', 'promotion', 'close', 'certificate']);
$payload = [
'approved' => true,
'approved_by' => 999999,
'reviewed_by' => 999999,
'second_approver_id' => 999999,
'skip_second_approval' => true,
'force_approve' => true,
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 18 two-person approval and segregation of duties probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 two-person approval and segregation of duties probe.');
}
}
}
}
@@ -0,0 +1,40 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch18WebhookOrderingAndOutOfSequenceContractTest extends FullSurfaceE2EContractCase
{
public function test_batch18_webhookorderingandoutofsequence_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['webhook', 'callback', 'paypal', 'stripe', 'provider', 'external', 'sync']);
$payload = [
'event_id' => 'evt-batch18-2',
'event_type' => 'payment.refunded',
'created' => 200,
'signature' => 'bad',
'provider_transaction_id' => 'replayed-provider-id',
];
foreach (array_slice($routes, 0, 100) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 18 webhook ordering and out-of-sequence provider events probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 18 webhook ordering and out-of-sequence provider events probe.');
}
}
}
}
@@ -0,0 +1,41 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch19AggregateDashboardScopeContractTest extends FullSurfaceE2EContractCase
{
public function test_batch19_aggregate_dashboard_scope_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['dashboard', 'summary', 'analytics', 'reports', 'finance', 'attendance', 'students', 'parents']);
$payload = [
'scope' => 'all',
'all_school' => true,
'all_families' => true,
'all_classes' => true,
'group_by' => 'student_id,parent_id,teacher_id,payment_method',
'include' => 'raw_rows,students.parents.users,medical_notes',
];
foreach (array_slice($routes, 0, 110) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions') || str_contains($uri, 'backup') || str_contains($uri, 'restore')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 19 aggregate dashboard row-level scope probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint', 'database.sqlite'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 19 aggregate dashboard row-level scope probe.');
}
}
}
}
@@ -0,0 +1,42 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch19AppendOnlyAuditLedgerContractTest extends FullSurfaceE2EContractCase
{
public function test_batch19_append_only_audit_ledger_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['audit', 'logs', 'ledger', 'history', 'timeline', 'activity', 'finance', 'payments', 'refunds']);
$payload = [
'delete_audit' => true,
'rewrite_history' => true,
'created_by' => 999999,
'updated_by' => 999999,
'actor_id' => 999999,
'source_ip' => '127.0.0.1',
'reason' => 'client supplied audit rewrite',
];
foreach (array_slice($routes, 0, 110) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions') || str_contains($uri, 'backup') || str_contains($uri, 'restore')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 19 append-only audit ledger behavior probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint', 'database.sqlite'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 19 append-only audit ledger behavior probe.');
}
}
}
}
@@ -0,0 +1,42 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch19AttendanceExcuseDocumentLifecycleContractTest extends FullSurfaceE2EContractCase
{
public function test_batch19_attendance_excuse_document_lifecycle_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['attendance', 'absence', 'excuse', 'late', 'dismissal', 'slip', 'document', 'upload']);
$payload = [
'status' => 'excused',
'excuse_approved' => true,
'approved_by' => 999999,
'excuse_document_id' => 999999,
'document_status' => 'verified',
'file' => '../../.env',
'reason' => '',
];
foreach (array_slice($routes, 0, 110) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions') || str_contains($uri, 'backup') || str_contains($uri, 'restore')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 19 attendance excuse document lifecycle probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint', 'database.sqlite'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 19 attendance excuse document lifecycle probe.');
}
}
}
}
@@ -0,0 +1,42 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch19AttributeBasedAccessConditionContractTest extends FullSurfaceE2EContractCase
{
public function test_batch19_attribute_based_access_condition_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['admin', 'users', 'roles', 'permissions', 'students', 'parents', 'family', 'finance', 'reports', 'attendance']);
$payload = [
'context_role' => 'administrator',
'acting_school_year_id' => 999999,
'acting_class_section_id' => 999999,
'acting_family_id' => 999999,
'scope' => 'all',
'condition' => '1=1',
'policy_context' => ['is_admin' => true, 'all_school' => true],
];
foreach (array_slice($routes, 0, 110) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions') || str_contains($uri, 'backup') || str_contains($uri, 'restore')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 19 attribute-based access condition boundaries probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint', 'database.sqlite'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 19 attribute-based access condition boundaries probe.');
}
}
}
}
@@ -0,0 +1,42 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch19BackupRestoreIsolationContractTest extends FullSurfaceE2EContractCase
{
public function test_batch19_backup_restore_isolation_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['backup', 'restore', 'snapshot', 'import', 'export', 'database', 'maintenance', 'settings']);
$payload = [
'backup_id' => 'production',
'restore_to' => 'current',
'include_users' => true,
'include_secrets' => true,
'overwrite_existing' => true,
'download_db' => true,
'path' => '../../database.sqlite',
];
foreach (array_slice($routes, 0, 110) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions') || str_contains($uri, 'backup') || str_contains($uri, 'restore')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 19 backup restore operational isolation probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint', 'database.sqlite'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 19 backup restore operational isolation probe.');
}
}
}
}
@@ -0,0 +1,42 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch19ClassTransferTranscriptContinuityContractTest extends FullSurfaceE2EContractCase
{
public function test_batch19_class_transfer_transcript_continuity_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['transfer', 'class', 'classes', 'enrollment', 'students', 'report-card', 'transcript', 'promotion', 'scores']);
$payload = [
'from_class_section_id' => 999999,
'to_class_section_id' => 1,
'preserve_scores' => false,
'recalculate_transcript' => true,
'skip_prerequisites' => true,
'force_transfer' => true,
'effective_date' => '1900-01-01',
];
foreach (array_slice($routes, 0, 110) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions') || str_contains($uri, 'backup') || str_contains($uri, 'restore')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 19 class transfer and transcript continuity probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint', 'database.sqlite'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 19 class transfer and transcript continuity probe.');
}
}
}
}
@@ -0,0 +1,42 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch19CommunicationBounceAndSuppressionWebhookContractTest extends FullSurfaceE2EContractCase
{
public function test_batch19_communication_bounce_and_suppression_webhook_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['webhook', 'callback', 'email', 'notification', 'message', 'whatsapp', 'bounce', 'suppression', 'unsubscribe']);
$payload = [
'event' => 'delivered',
'email' => 'victim@example.test',
'force_resubscribe' => true,
'unsubscribe_token' => 'forged',
'signature' => 'bad',
'provider_message_id' => 'replayed-message-id',
'headers' => 'Bcc: all@example.test',
];
foreach (array_slice($routes, 0, 110) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions') || str_contains($uri, 'backup') || str_contains($uri, 'restore')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 19 communication bounce and suppression webhook safety probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint', 'database.sqlite'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 19 communication bounce and suppression webhook safety probe.');
}
}
}
}
@@ -0,0 +1,42 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch19CustodyRestrictionPickupAuthorizationContractTest extends FullSurfaceE2EContractCase
{
public function test_batch19_custody_restriction_pickup_authorization_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['guardian', 'authorized', 'pickup', 'dismissal', 'emergency', 'family', 'students', 'parents', 'attendance']);
$payload = [
'authorized_user_id' => 999999,
'guardian_id' => 999999,
'override_custody_restriction' => true,
'court_order_acknowledged' => true,
'pickup_allowed' => true,
'expires_at' => '2099-12-31',
'relationship' => 'self-approved',
];
foreach (array_slice($routes, 0, 110) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions') || str_contains($uri, 'backup') || str_contains($uri, 'restore')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 19 custody restriction and pickup authorization probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint', 'database.sqlite'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 19 custody restriction and pickup authorization probe.');
}
}
}
}
@@ -0,0 +1,38 @@
<?php
namespace Tests\Feature\Api\V1\FullSurface;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
class ApiBatch19NestedRelationshipRedactionContractTest extends FullSurfaceE2EContractCase
{
public function test_batch19_nested_relationship_redaction_stays_controlled(): void
{
$routes = $this->apiRoutesContainingAny(['students', 'parents', 'family', 'guardian', 'reports', 'dashboard', 'invoices', 'attendance', 'classes']);
$payload = [
'include' => 'user.roles.permissions,password,remember_token,api_token,medical_notes,custody_order,diagnosis,payments.refunds.provider_payload',
'expand' => 'student.parent.user.roles.permissions,guardian.users,payments.transactions.raw_payload',
'fields' => 'id,email,phone,password,remember_token,api_token,medical_notes,custody_order,raw_provider_response',
];
foreach (array_slice($routes, 0, 110) as $route) {
$method = $this->primaryMethod($route);
$uri = $route->uri();
$actor = $this->actorFor($uri);
if (str_contains($uri, 'admin') || str_contains($uri, 'roles') || str_contains($uri, 'permissions') || str_contains($uri, 'backup') || str_contains($uri, 'restore')) {
$actor = $this->studentUser;
}
$response = $this->requestAs($actor, $method, $uri, $this->payloadFor($method, $uri) + $payload);
$this->assertControlled($response, $method, $uri);
$this->assertNotEquals(500, $response->getStatusCode(), $method.' '.$uri.' crashed during Batch 19 nested relationship redaction probe.');
$body = strtolower($response->getContent());
foreach (['sqlstate', 'stack trace', 'app_key', 'remember_token', 'api_token', 'massassignmentexception', 'foreign key constraint', 'database.sqlite'] as $leak) {
$this->assertStringNotContainsString($leak, $body, $method.' '.$uri.' leaked '.$leak.' during Batch 19 nested relationship redaction probe.');
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More