Fix Pint formatting
This commit is contained in:
@@ -11,19 +11,19 @@ trait AssertsE2EApiResponses
|
||||
$this->assertLessThan(
|
||||
500,
|
||||
$response->getStatusCode(),
|
||||
$context . ' should return a controlled response, not a 5xx crash. Body: ' . $response->getContent()
|
||||
$context.' should return a controlled response, not a 5xx crash. Body: '.$response->getContent()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, int> $statuses
|
||||
* @param array<int, int> $statuses
|
||||
*/
|
||||
protected function assertStatusIn(TestResponse $response, array $statuses, string $context): void
|
||||
{
|
||||
$this->assertContains(
|
||||
$response->getStatusCode(),
|
||||
$statuses,
|
||||
$context . ' returned unexpected status ' . $response->getStatusCode() . '. Body: ' . $response->getContent()
|
||||
$context.' returned unexpected status '.$response->getStatusCode().'. Body: '.$response->getContent()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ trait CreatesApiTestUsers
|
||||
'status' => 'Active',
|
||||
'is_verified' => 1,
|
||||
'is_suspended' => 0,
|
||||
'email' => $roleName . '-api-test-' . str_replace('.', '', uniqid('', true)) . '@example.test',
|
||||
'email' => $roleName.'-api-test-'.str_replace('.', '', uniqid('', true)).'@example.test',
|
||||
], $overrides));
|
||||
|
||||
$role = $roles[$roleName] ?? Role::query()->where('name', $roleName)->firstOrFail();
|
||||
|
||||
@@ -22,4 +22,4 @@ trait SeedsAdministratorApiConfig
|
||||
['config_value' => $semester]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ trait SeedsE2ETestFixtures
|
||||
use CreatesApiTestUsers;
|
||||
|
||||
protected const E2E_SCHOOL_YEAR = '2025-2026';
|
||||
|
||||
protected const E2E_PREV_SCHOOL_YEAR = '2024-2025';
|
||||
|
||||
protected const E2E_SEMESTER = 'Fall';
|
||||
|
||||
protected function seedE2EConfiguration(): void
|
||||
@@ -108,18 +110,18 @@ trait SeedsE2ETestFixtures
|
||||
$teacher = $this->createApiUserWithRole('teacher', [
|
||||
'firstname' => 'Tara',
|
||||
'lastname' => 'Teacher',
|
||||
'email' => 'e2e.teacher.' . uniqid('', true) . '@example.test',
|
||||
'email' => 'e2e.teacher.'.uniqid('', true).'@example.test',
|
||||
]);
|
||||
$this->seedTeacherClassAssignment($teacher->id, $classSectionId);
|
||||
|
||||
$parent = $this->createApiUserWithRole('parent', [
|
||||
'firstname' => 'Parent',
|
||||
'lastname' => 'Fixture',
|
||||
'email' => 'e2e.parent.' . uniqid('', true) . '@example.test',
|
||||
'email' => 'e2e.parent.'.uniqid('', true).'@example.test',
|
||||
]);
|
||||
|
||||
$studentId = DB::table('students')->insertGetId([
|
||||
'school_id' => 'S-E2E-' . random_int(1000, 9999),
|
||||
'school_id' => 'S-E2E-'.random_int(1000, 9999),
|
||||
'firstname' => 'Kai',
|
||||
'lastname' => 'Student',
|
||||
'dob' => '2014-09-01',
|
||||
@@ -221,7 +223,7 @@ trait SeedsE2ETestFixtures
|
||||
protected function postEnrollmentStatuses(array $studentStatusMap): TestResponse
|
||||
{
|
||||
return $this->post(
|
||||
'/api/v1/administrator/enrollment-withdrawal/update-statuses?' .
|
||||
'/api/v1/administrator/enrollment-withdrawal/update-statuses?'.
|
||||
http_build_query(['enrollment_status' => $studentStatusMap]),
|
||||
[]
|
||||
);
|
||||
@@ -231,7 +233,7 @@ trait SeedsE2ETestFixtures
|
||||
{
|
||||
return DB::table('invoices')->insertGetId(array_merge([
|
||||
'parent_id' => $parentId,
|
||||
'invoice_number' => 'INV-E2E-' . random_int(1000, 9999),
|
||||
'invoice_number' => 'INV-E2E-'.random_int(1000, 9999),
|
||||
'total_amount' => 500.00,
|
||||
'balance' => 500.00,
|
||||
'paid_amount' => 0,
|
||||
@@ -250,7 +252,7 @@ trait SeedsE2ETestFixtures
|
||||
{
|
||||
return DB::table('invoices')->insertGetId([
|
||||
'parent_id' => $parentId,
|
||||
'invoice_number' => 'INV-PRIOR-' . random_int(1000, 9999),
|
||||
'invoice_number' => 'INV-PRIOR-'.random_int(1000, 9999),
|
||||
'total_amount' => 200.00,
|
||||
'balance' => 150.00,
|
||||
'paid_amount' => 50.00,
|
||||
@@ -268,7 +270,7 @@ trait SeedsE2ETestFixtures
|
||||
protected function seedDiscountVoucher(): int
|
||||
{
|
||||
return DB::table('discount_vouchers')->insertGetId([
|
||||
'code' => 'E2E10-' . random_int(100, 999),
|
||||
'code' => 'E2E10-'.random_int(100, 999),
|
||||
'discount_type' => 'fixed',
|
||||
'discount_value' => 50,
|
||||
'max_uses' => 100,
|
||||
|
||||
@@ -13,7 +13,6 @@ class ApiAuthenticationAndAuthorizationTest extends TestCase
|
||||
use CreatesApiTestUsers;
|
||||
use RefreshDatabase;
|
||||
|
||||
|
||||
public function test_api_login_returns_token_and_user_object(): void
|
||||
{
|
||||
$user = $this->createApiUserWithRole('teacher', [
|
||||
@@ -103,7 +102,7 @@ class ApiAuthenticationAndAuthorizationTest extends TestCase
|
||||
|
||||
foreach ($this->sampleProtectedRoutes() as $route) {
|
||||
$method = $this->primaryMethod($route);
|
||||
$uri = '/' . $this->uriWithPlaceholders($route->uri());
|
||||
$uri = '/'.$this->uriWithPlaceholders($route->uri());
|
||||
|
||||
$response = $this->json($method, $uri);
|
||||
$status = $response->getStatusCode();
|
||||
@@ -113,7 +112,7 @@ class ApiAuthenticationAndAuthorizationTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertSame([], $failures, "Anonymous requests reached unexpected responses:\n" . implode("\n", $failures));
|
||||
$this->assertSame([], $failures, "Anonymous requests reached unexpected responses:\n".implode("\n", $failures));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,7 +47,6 @@ class ApiPreferencesFeatureTest extends TestCase
|
||||
->assertJsonPath('status', true);
|
||||
}
|
||||
|
||||
|
||||
public function test_non_admin_cannot_list_or_manage_another_users_preferences(): void
|
||||
{
|
||||
$owner = $this->createApiUserWithRole('teacher');
|
||||
|
||||
@@ -36,7 +36,7 @@ class ApiPublicEndpointsTest extends TestCase
|
||||
public function test_public_frontend_content_endpoints_do_not_server_error(): void
|
||||
{
|
||||
foreach (['/', 'facility', 'team', 'call-to-action', 'testimonial', 'not-found'] as $path) {
|
||||
$uri = '/api/v1/frontend' . ($path === '/' ? '' : '/' . $path);
|
||||
$uri = '/api/v1/frontend'.($path === '/' ? '' : '/'.$path);
|
||||
$response = $this->getJson($uri);
|
||||
|
||||
$this->assertLessThan(500, $response->getStatusCode(), "$uri returned a server error");
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace Tests\Feature\Api;
|
||||
|
||||
use App\Models\Permission;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\Concerns\CreatesApiTestUsers;
|
||||
|
||||
@@ -24,11 +24,11 @@ class ApiRouteContractTest extends TestCase
|
||||
|
||||
[$class, $method] = explode('@', $action, 2);
|
||||
if (! class_exists($class) || ! method_exists($class, $method)) {
|
||||
$missing[] = implode('|', $route->methods()) . ' ' . $route->uri() . ' -> ' . $action;
|
||||
$missing[] = implode('|', $route->methods()).' '.$route->uri().' -> '.$action;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertSame([], $missing, "These API routes reference missing controllers or methods:\n" . implode("\n", $missing));
|
||||
$this->assertSame([], $missing, "These API routes reference missing controllers or methods:\n".implode("\n", $missing));
|
||||
}
|
||||
|
||||
public function test_api_routes_do_not_duplicate_method_and_uri_pairs(): void
|
||||
@@ -42,7 +42,7 @@ class ApiRouteContractTest extends TestCase
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = $method . ' ' . $route->uri();
|
||||
$key = $method.' '.$route->uri();
|
||||
if (isset($seen[$key])) {
|
||||
$duplicates[] = $key;
|
||||
}
|
||||
@@ -90,11 +90,11 @@ class ApiRouteContractTest extends TestCase
|
||||
});
|
||||
|
||||
if (! $hasProtection) {
|
||||
$unprotected[] = implode('|', $methods) . ' ' . $route->uri();
|
||||
$unprotected[] = implode('|', $methods).' '.$route->uri();
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertSame([], $unprotected, "Mutating API routes without an obvious auth/throttle middleware:\n" . implode("\n", $unprotected));
|
||||
$this->assertSame([], $unprotected, "Mutating API routes without an obvious auth/throttle middleware:\n".implode("\n", $unprotected));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,14 +26,14 @@ class ApiUseCaseCoverageMatrixTest extends TestCase
|
||||
$uri = $route->uri();
|
||||
|
||||
if ($this->domainFor($uri) === null) {
|
||||
$unmapped[] = implode('|', array_diff($route->methods(), ['HEAD'])) . ' ' . $uri;
|
||||
$unmapped[] = implode('|', array_diff($route->methods(), ['HEAD'])).' '.$uri;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertSame(
|
||||
[],
|
||||
$unmapped,
|
||||
"API routes without an explicit use-case domain:\n" . implode("\n", $unmapped) .
|
||||
"API routes without an explicit use-case domain:\n".implode("\n", $unmapped).
|
||||
"\n\nAdd the route to the matrix and then add/extend an E2E workflow test for that domain."
|
||||
);
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class ApiUseCaseCoverageMatrixTest extends TestCase
|
||||
$this->assertSame(
|
||||
[],
|
||||
$missing,
|
||||
"Required use-case domains with no matching API route coverage:\n" . implode("\n", $missing)
|
||||
"Required use-case domains with no matching API route coverage:\n".implode("\n", $missing)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class ApiUseCaseCoverageMatrixTest extends TestCase
|
||||
|
||||
foreach ($this->protectedRoutes() as $route) {
|
||||
$method = $this->primaryMethod($route);
|
||||
$uri = '/' . $this->uriWithSafePlaceholders($route->uri());
|
||||
$uri = '/'.$this->uriWithSafePlaceholders($route->uri());
|
||||
|
||||
$response = $this->json($method, $uri);
|
||||
$status = $response->getStatusCode();
|
||||
@@ -76,7 +76,7 @@ class ApiUseCaseCoverageMatrixTest extends TestCase
|
||||
$this->assertSame(
|
||||
[],
|
||||
$failures,
|
||||
"Anonymous requests reached protected API routes unexpectedly:\n" . implode("\n", $failures)
|
||||
"Anonymous requests reached protected API routes unexpectedly:\n".implode("\n", $failures)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace Tests\Feature\Api;
|
||||
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
@@ -121,4 +121,4 @@ class AdministratorAbsenceControllerTest extends TestCase
|
||||
'dates' => ['2026-03-08'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,4 +128,4 @@ class AdministratorEnrollmentControllerTest extends TestCase
|
||||
'message' => 'Enrollment statuses updated and notifications sent.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +263,7 @@ class AdministratorEnrollmentUpdateStatusesApiTest extends TestCase
|
||||
private function postUpdateStatuses(array $enrollmentStatus)
|
||||
{
|
||||
$query = http_build_query(['enrollment_status' => $enrollmentStatus]);
|
||||
return $this->post('/api/v1/administrator/enrollment-withdrawal/update-statuses?' . $query, []);
|
||||
|
||||
return $this->post('/api/v1/administrator/enrollment-withdrawal/update-statuses?'.$query, []);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,4 +157,4 @@ class AdministratorNotificationControllerTest extends TestCase
|
||||
'message' => 'Print notification recipients updated.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace Tests\Feature\Api\Administrator;
|
||||
|
||||
use App\Http\Middleware\EnsurePrintRequestsAdminAccess;
|
||||
use App\Models\User;
|
||||
use App\Services\Administrator\TeacherSubmissionNotificationService;
|
||||
use App\Services\Administrator\TeacherSubmissionReportService;
|
||||
use App\Http\Middleware\EnsurePrintRequestsAdminAccess;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Mockery;
|
||||
|
||||
@@ -281,7 +281,7 @@ class AdministratorTeacherSubmissionReportApiTest extends TestCase
|
||||
'class_section_id' => 12,
|
||||
'admin_id' => 901,
|
||||
'notification_category' => 'teacher_submissions',
|
||||
'message' => 'Reminder ' . $i,
|
||||
'message' => 'Reminder '.$i,
|
||||
'status' => 'sent',
|
||||
'school_year' => '2025-2026',
|
||||
'semester' => 'Fall',
|
||||
|
||||
@@ -80,7 +80,7 @@ class EmergencyContactControllerTest extends TestCase
|
||||
]);
|
||||
|
||||
Sanctum::actingAs($admin, [], 'api');
|
||||
$response = $this->patch('/api/v1/administrator/emergency-contacts/' . $contact->id, [
|
||||
$response = $this->patch('/api/v1/administrator/emergency-contacts/'.$contact->id, [
|
||||
'parent_id' => $parent->id,
|
||||
'name' => 'New Name',
|
||||
'cellphone' => '5555555555',
|
||||
@@ -116,7 +116,7 @@ class EmergencyContactControllerTest extends TestCase
|
||||
]);
|
||||
|
||||
Sanctum::actingAs($admin, [], 'api');
|
||||
$response = $this->delete('/api/v1/administrator/emergency-contacts/' . $contact->id, [
|
||||
$response = $this->delete('/api/v1/administrator/emergency-contacts/'.$contact->id, [
|
||||
'parent_id' => $parent->id,
|
||||
], [
|
||||
'Accept' => 'application/json',
|
||||
@@ -145,7 +145,7 @@ class EmergencyContactControllerTest extends TestCase
|
||||
]);
|
||||
|
||||
Sanctum::actingAs($admin, [], 'api');
|
||||
$response = $this->get('/api/v1/administrator/emergency-contacts/' . $contact->id . '?parent_id=' . $parent->id, [
|
||||
$response = $this->get('/api/v1/administrator/emergency-contacts/'.$contact->id.'?parent_id='.$parent->id, [
|
||||
'Accept' => 'application/json',
|
||||
]);
|
||||
|
||||
@@ -171,7 +171,7 @@ class EmergencyContactControllerTest extends TestCase
|
||||
]);
|
||||
|
||||
Sanctum::actingAs($admin, [], 'api');
|
||||
$response = $this->patch('/api/v1/administrator/emergency-contacts/' . $contact->id, [
|
||||
$response = $this->patch('/api/v1/administrator/emergency-contacts/'.$contact->id, [
|
||||
'parent_id' => $otherParent->id,
|
||||
'name' => 'New Name',
|
||||
'cellphone' => '9999999999',
|
||||
@@ -205,7 +205,7 @@ class EmergencyContactControllerTest extends TestCase
|
||||
]);
|
||||
|
||||
Sanctum::actingAs($admin, [], 'api');
|
||||
$response = $this->delete('/api/v1/administrator/emergency-contacts/' . $contact->id, [
|
||||
$response = $this->delete('/api/v1/administrator/emergency-contacts/'.$contact->id, [
|
||||
'parent_id' => $otherParent->id,
|
||||
], [
|
||||
'Accept' => 'application/json',
|
||||
|
||||
@@ -16,11 +16,17 @@ class AssignmentApiControllerTest extends TestCase
|
||||
use RefreshDatabase;
|
||||
|
||||
protected User $user;
|
||||
|
||||
protected User $teacherMain;
|
||||
|
||||
protected User $teacherAssistant;
|
||||
|
||||
protected ClassSection $sectionA;
|
||||
|
||||
protected ClassSection $sectionB;
|
||||
|
||||
protected Student $studentOne;
|
||||
|
||||
protected Student $studentTwo;
|
||||
|
||||
protected function setUp(): void
|
||||
@@ -418,4 +424,4 @@ class AssignmentApiControllerTest extends TestCase
|
||||
|
||||
$this->assertSame(['Main Teacher'], $mainTeachers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
namespace Tests\Feature\Api;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\ClassSection;
|
||||
use App\Models\Configuration;
|
||||
use App\Models\Student;
|
||||
use App\Models\StudentClass;
|
||||
use App\Models\TeacherClass;
|
||||
use App\Models\ClassSection;
|
||||
use App\Models\Configuration;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
@@ -48,36 +48,36 @@ class AssignmentApiTest extends TestCase
|
||||
|
||||
$teacher = User::factory()->create([
|
||||
'firstname' => 'Ahmad',
|
||||
'lastname' => 'Ali',
|
||||
'lastname' => 'Ali',
|
||||
]);
|
||||
|
||||
TeacherClass::query()->create([
|
||||
'teacher_id' => $teacher->id,
|
||||
'class_section_id' => $section->class_section_id,
|
||||
'position' => 'main',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'description' => 'Main teacher assignment',
|
||||
'teacher_id' => $teacher->id,
|
||||
'class_section_id' => $section->class_section_id,
|
||||
'position' => 'main',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'description' => 'Main teacher assignment',
|
||||
]);
|
||||
|
||||
$student = Student::factory()->create([
|
||||
'firstname' => 'Sara',
|
||||
'lastname' => 'Yousef',
|
||||
'is_active' => 1,
|
||||
'gender' => 'F',
|
||||
'firstname' => 'Sara',
|
||||
'lastname' => 'Yousef',
|
||||
'is_active' => 1,
|
||||
'gender' => 'F',
|
||||
'registration_grade' => '5',
|
||||
'photo_consent' => 1,
|
||||
'tuition_paid' => 1,
|
||||
'school_id' => 'SCH-1001',
|
||||
'photo_consent' => 1,
|
||||
'tuition_paid' => 1,
|
||||
'school_id' => 'SCH-1001',
|
||||
]);
|
||||
|
||||
StudentClass::query()->create([
|
||||
'student_id' => $student->id,
|
||||
'class_section_id' => $section->class_section_id,
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'description' => 'Student assigned',
|
||||
'is_active' => 1, // remove if your table doesn’t have this column
|
||||
'student_id' => $student->id,
|
||||
'class_section_id' => $section->class_section_id,
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'description' => 'Student assigned',
|
||||
'is_active' => 1, // remove if your table doesn’t have this column
|
||||
]);
|
||||
|
||||
$response = $this->getJson('/api/assignments');
|
||||
@@ -96,7 +96,7 @@ class AssignmentApiTest extends TestCase
|
||||
'semester',
|
||||
'school_year',
|
||||
'description',
|
||||
]
|
||||
],
|
||||
],
|
||||
'schoolYears',
|
||||
'selectedYear',
|
||||
@@ -122,19 +122,19 @@ class AssignmentApiTest extends TestCase
|
||||
$teacher = User::factory()->create(['firstname' => 'Test', 'lastname' => 'Teacher']);
|
||||
|
||||
TeacherClass::query()->create([
|
||||
'teacher_id' => $teacher->id,
|
||||
'teacher_id' => $teacher->id,
|
||||
'class_section_id' => $section1->class_section_id,
|
||||
'position' => 'main',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2024-2025',
|
||||
'position' => 'main',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2024-2025',
|
||||
]);
|
||||
|
||||
TeacherClass::query()->create([
|
||||
'teacher_id' => $teacher->id,
|
||||
'teacher_id' => $teacher->id,
|
||||
'class_section_id' => $section2->class_section_id,
|
||||
'position' => 'main',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'position' => 'main',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
]);
|
||||
|
||||
$response = $this->getJson('/api/assignments?school_year=2025-2026');
|
||||
@@ -158,11 +158,11 @@ class AssignmentApiTest extends TestCase
|
||||
$section = ClassSection::factory()->create();
|
||||
|
||||
$payload = [
|
||||
'student_id' => $student->id,
|
||||
'student_id' => $student->id,
|
||||
'class_section_id' => $section->class_section_id,
|
||||
'semester' => 'Spring',
|
||||
'school_year' => '2025-2026',
|
||||
'description' => 'Moved after placement review',
|
||||
'semester' => 'Spring',
|
||||
'school_year' => '2025-2026',
|
||||
'description' => 'Moved after placement review',
|
||||
];
|
||||
|
||||
$response = $this->postJson('/api/assignments', $payload);
|
||||
@@ -171,10 +171,10 @@ class AssignmentApiTest extends TestCase
|
||||
->assertJsonPath('message', 'Assignment saved successfully.');
|
||||
|
||||
$this->assertDatabaseHas('student_class', [
|
||||
'student_id' => $student->id,
|
||||
'student_id' => $student->id,
|
||||
'class_section_id' => $section->class_section_id,
|
||||
'semester' => 'Spring',
|
||||
'school_year' => '2025-2026',
|
||||
'semester' => 'Spring',
|
||||
'school_year' => '2025-2026',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -203,19 +203,19 @@ class AssignmentApiTest extends TestCase
|
||||
|
||||
// First create
|
||||
$this->postJson('/api/assignments', [
|
||||
'student_id' => $student->id,
|
||||
'student_id' => $student->id,
|
||||
'class_section_id' => $sectionA->class_section_id,
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
])->assertCreated();
|
||||
|
||||
// Second call same unique key should update (per service updateOrCreate)
|
||||
$this->postJson('/api/assignments', [
|
||||
'student_id' => $student->id,
|
||||
'student_id' => $student->id,
|
||||
'class_section_id' => $sectionA->class_section_id,
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'description' => 'Reassigned',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'description' => 'Reassigned',
|
||||
])->assertCreated();
|
||||
|
||||
$this->assertEquals(
|
||||
@@ -228,10 +228,10 @@ class AssignmentApiTest extends TestCase
|
||||
);
|
||||
|
||||
$this->assertDatabaseHas('student_class', [
|
||||
'student_id' => $student->id,
|
||||
'student_id' => $student->id,
|
||||
'class_section_id' => $sectionA->class_section_id,
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ class LateSlipLogsControllerTest extends TestCase
|
||||
$user = User::query()->create([
|
||||
'firstname' => 'Test',
|
||||
'lastname' => 'User',
|
||||
'email' => $roleName . '@example.com',
|
||||
'email' => $roleName.'@example.com',
|
||||
'cellphone' => '5555555555',
|
||||
'address_street' => '123 Main',
|
||||
'city' => 'City',
|
||||
|
||||
@@ -85,10 +85,10 @@ class AttendanceCommentTemplateApiTest extends TestCase
|
||||
$mock->shouldReceive('create')
|
||||
->once()
|
||||
->with(Mockery::on(function ($data) use ($payload) {
|
||||
return (int)$data['min_score'] === 80
|
||||
&& (int)$data['max_score'] === 100
|
||||
return (int) $data['min_score'] === 80
|
||||
&& (int) $data['max_score'] === 100
|
||||
&& $data['template_text'] === $payload['template_text']
|
||||
&& (bool)$data['is_active'] === true;
|
||||
&& (bool) $data['is_active'] === true;
|
||||
}))
|
||||
->andReturn([
|
||||
'id' => 10,
|
||||
@@ -159,7 +159,7 @@ class AttendanceCommentTemplateApiTest extends TestCase
|
||||
->once()
|
||||
->with(7, Mockery::on(function ($data) {
|
||||
return $data['template_text'] === 'Updated comment text'
|
||||
&& (bool)$data['is_active'] === false;
|
||||
&& (bool) $data['is_active'] === false;
|
||||
}))
|
||||
->andReturn([
|
||||
'id' => 7,
|
||||
@@ -221,7 +221,7 @@ class AttendanceCommentTemplateApiTest extends TestCase
|
||||
'max_score' => 49,
|
||||
'template_text' => 'Low attendance',
|
||||
'is_active' => true,
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$this->app->instance(AttendanceCommentTemplateService::class, $mock);
|
||||
@@ -232,4 +232,4 @@ class AttendanceCommentTemplateApiTest extends TestCase
|
||||
->assertJsonCount(1, 'templates')
|
||||
->assertJsonPath('templates.0.id', 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Tests\Feature\Api\V1\AttendanceTracking;
|
||||
|
||||
use App\Services\AttendanceTracking\AttendanceTrackingService;
|
||||
use Mockery;
|
||||
use Mockery\MockInterface;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AttendanceTrackingControllerTest extends TestCase
|
||||
@@ -14,7 +15,7 @@ class AttendanceTrackingControllerTest extends TestCase
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
protected function mockService(): \Mockery\MockInterface
|
||||
protected function mockService(): MockInterface
|
||||
{
|
||||
$service = Mockery::mock(AttendanceTrackingService::class);
|
||||
$this->app->instance(AttendanceTrackingService::class, $service);
|
||||
|
||||
@@ -159,7 +159,7 @@ class IpBanControllerTest extends TestCase
|
||||
$user = User::query()->create([
|
||||
'firstname' => 'Test',
|
||||
'lastname' => 'User',
|
||||
'email' => $roleName . '@example.com',
|
||||
'email' => $roleName.'@example.com',
|
||||
'cellphone' => '5555555555',
|
||||
'address_street' => '123 Main',
|
||||
'city' => 'City',
|
||||
|
||||
@@ -234,4 +234,4 @@ class BadgeControllerTest extends TestCase
|
||||
$this->postJson('/api/badges/log-print', ['user_ids' => [1]])->assertStatus(401);
|
||||
$this->postJson('/api/badges/pdf', ['user_ids' => [1]])->assertStatus(401);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ class BroadcastEmailControllerTest extends TestCase
|
||||
{
|
||||
$this->seedParentData();
|
||||
|
||||
$fake = new class extends EmailService {
|
||||
$fake = new class extends EmailService
|
||||
{
|
||||
public array $sent = [];
|
||||
|
||||
public function send(
|
||||
@@ -41,9 +42,9 @@ class BroadcastEmailControllerTest extends TestCase
|
||||
string $fromKey = 'general',
|
||||
array $cc = [],
|
||||
array $bcc = []
|
||||
): bool
|
||||
{
|
||||
): bool {
|
||||
$this->sent[] = compact('to', 'subject', 'html', 'fromKey', 'cc', 'bcc');
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -92,7 +92,7 @@ class ClassProgressControllerTest extends TestCase
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->getJson('/api/v1/class-progress/' . $report->id);
|
||||
$response = $this->getJson('/api/v1/class-progress/'.$report->id);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('status', true);
|
||||
@@ -110,7 +110,7 @@ class ClassProgressControllerTest extends TestCase
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->patchJson('/api/v1/class-progress/' . $report->id, [
|
||||
$response = $this->patchJson('/api/v1/class-progress/'.$report->id, [
|
||||
'status' => 'behind',
|
||||
]);
|
||||
|
||||
@@ -132,7 +132,7 @@ class ClassProgressControllerTest extends TestCase
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->deleteJson('/api/v1/class-progress/' . $report->id);
|
||||
$response = $this->deleteJson('/api/v1/class-progress/'.$report->id);
|
||||
|
||||
$response->assertOk();
|
||||
$this->assertDatabaseMissing('class_progress_reports', [
|
||||
@@ -152,7 +152,7 @@ class ClassProgressControllerTest extends TestCase
|
||||
|
||||
Sanctum::actingAs($other);
|
||||
|
||||
$response = $this->getJson('/api/v1/class-progress/' . $report->id);
|
||||
$response = $this->getJson('/api/v1/class-progress/'.$report->id);
|
||||
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
@@ -187,7 +187,7 @@ class ClassProgressControllerTest extends TestCase
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->get('/api/v1/class-progress/attachments/' . $attachment->id);
|
||||
$response = $this->get('/api/v1/class-progress/attachments/'.$attachment->id);
|
||||
|
||||
$response->assertOk();
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ class ClassSectionControllerTest extends TestCase
|
||||
'firstname' => 'Test',
|
||||
'lastname' => 'User',
|
||||
'cellphone' => '5555555555',
|
||||
'email' => $roleName . '@example.com',
|
||||
'email' => $roleName.'@example.com',
|
||||
'address_street' => '123 Main',
|
||||
'city' => 'City',
|
||||
'state' => 'ST',
|
||||
|
||||
@@ -50,7 +50,8 @@ class CommunicationControllerTest extends TestCase
|
||||
{
|
||||
$this->seedCommunicationData();
|
||||
|
||||
$fake = new class extends EmailService {
|
||||
$fake = new class extends EmailService
|
||||
{
|
||||
public array $sent = [];
|
||||
|
||||
public function send(
|
||||
@@ -62,6 +63,7 @@ class CommunicationControllerTest extends TestCase
|
||||
array $bcc = []
|
||||
): bool {
|
||||
$this->sent[] = compact('to', 'subject', 'html', 'fromKey', 'cc', 'bcc');
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ class FamilyAdminControllerTest extends TestCase
|
||||
$this->seedFamilyGuardian($familyId, $user->id);
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
$response = $this->getJson('/api/v1/family-admin?student_id=' . $studentId);
|
||||
$response = $this->getJson('/api/v1/family-admin?student_id='.$studentId);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('status', true);
|
||||
@@ -58,7 +58,7 @@ class FamilyAdminControllerTest extends TestCase
|
||||
$this->seedPayment($user->id);
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
$response = $this->getJson('/api/v1/family-admin/card?family_id=' . $familyId);
|
||||
$response = $this->getJson('/api/v1/family-admin/card?family_id='.$familyId);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('status', true);
|
||||
@@ -123,7 +123,7 @@ class FamilyAdminControllerTest extends TestCase
|
||||
'firstname' => 'Parent',
|
||||
'lastname' => 'User',
|
||||
'cellphone' => '9999999999',
|
||||
'email' => 'parent' . $id . '@example.com',
|
||||
'email' => 'parent'.$id.'@example.com',
|
||||
'address_street' => '123 Street',
|
||||
'city' => 'City',
|
||||
'state' => 'ST',
|
||||
@@ -163,7 +163,7 @@ class FamilyAdminControllerTest extends TestCase
|
||||
{
|
||||
return DB::table('families')->insertGetId([
|
||||
'family_code' => $code,
|
||||
'household_name' => 'Family ' . $code,
|
||||
'household_name' => 'Family '.$code,
|
||||
'is_active' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
|
||||
@@ -20,7 +20,7 @@ class FamilyControllerTest extends TestCase
|
||||
$this->seedFamilyStudent($familyId, $studentId);
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
$response = $this->getJson('/api/v1/families/by-student/' . $studentId);
|
||||
$response = $this->getJson('/api/v1/families/by-student/'.$studentId);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('status', true);
|
||||
@@ -34,7 +34,7 @@ class FamilyControllerTest extends TestCase
|
||||
$this->seedFamilyGuardian($familyId, $user->id);
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
$response = $this->getJson('/api/v1/families/' . $familyId . '/guardians');
|
||||
$response = $this->getJson('/api/v1/families/'.$familyId.'/guardians');
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('status', true);
|
||||
@@ -292,7 +292,7 @@ class FamilyControllerTest extends TestCase
|
||||
{
|
||||
return DB::table('families')->insertGetId([
|
||||
'family_code' => $code,
|
||||
'household_name' => 'Family ' . $code,
|
||||
'household_name' => 'Family '.$code,
|
||||
'is_active' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
|
||||
@@ -28,7 +28,7 @@ class EventChargeControllerTest extends TestCase
|
||||
{
|
||||
$charge = $this->makeCharge();
|
||||
|
||||
$this->getJson('/api/v1/finance/event-charges/' . $charge->id)
|
||||
$this->getJson('/api/v1/finance/event-charges/'.$charge->id)
|
||||
->assertStatus(401);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class EventChargeControllerTest extends TestCase
|
||||
$this->actingAsApiAdministrator();
|
||||
$charge = $this->makeCharge();
|
||||
|
||||
$this->getJson('/api/v1/finance/event-charges/' . $charge->id)
|
||||
$this->getJson('/api/v1/finance/event-charges/'.$charge->id)
|
||||
->assertOk()
|
||||
->assertJsonPath('ok', true)
|
||||
->assertJsonPath('event_charge.id', $charge->id)
|
||||
@@ -57,7 +57,7 @@ class EventChargeControllerTest extends TestCase
|
||||
$this->actingAsApiAdministrator();
|
||||
$charge = $this->makeCharge();
|
||||
|
||||
$this->putJson('/api/v1/finance/event-charges/' . $charge->id, [
|
||||
$this->putJson('/api/v1/finance/event-charges/'.$charge->id, [
|
||||
'event_name' => 'Museum Visit',
|
||||
'amount' => 40.50,
|
||||
])
|
||||
@@ -76,7 +76,7 @@ class EventChargeControllerTest extends TestCase
|
||||
$this->actingAsApiAdministrator();
|
||||
$charge = $this->makeCharge(['charged' => 0]);
|
||||
|
||||
$this->postJson('/api/v1/finance/event-charges/' . $charge->id . '/approve')
|
||||
$this->postJson('/api/v1/finance/event-charges/'.$charge->id.'/approve')
|
||||
->assertOk()
|
||||
->assertJsonPath('ok', true);
|
||||
|
||||
@@ -91,7 +91,7 @@ class EventChargeControllerTest extends TestCase
|
||||
$this->actingAsApiAdministrator();
|
||||
$charge = $this->makeCharge(['charged' => 1]);
|
||||
|
||||
$this->postJson('/api/v1/finance/event-charges/' . $charge->id . '/void')
|
||||
$this->postJson('/api/v1/finance/event-charges/'.$charge->id.'/void')
|
||||
->assertOk()
|
||||
->assertJsonPath('ok', true);
|
||||
|
||||
@@ -106,7 +106,7 @@ class EventChargeControllerTest extends TestCase
|
||||
$this->actingAsApiAdministrator();
|
||||
$charge = $this->makeCharge(['charged' => 1]);
|
||||
|
||||
$this->deleteJson('/api/v1/finance/event-charges/' . $charge->id)
|
||||
$this->deleteJson('/api/v1/finance/event-charges/'.$charge->id)
|
||||
->assertOk()
|
||||
->assertJsonPath('ok', true);
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class FrontendControllerTest extends TestCase
|
||||
$user = User::query()->create([
|
||||
'firstname' => 'Test',
|
||||
'lastname' => 'User',
|
||||
'email' => $roleName . '@example.com',
|
||||
'email' => $roleName.'@example.com',
|
||||
'cellphone' => '5555555555',
|
||||
'address_street' => '123 Main',
|
||||
'city' => 'City',
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace Tests\Feature\Api\V1\Frontend;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ class LandingPageControllerTest extends TestCase
|
||||
$user = User::query()->create([
|
||||
'firstname' => 'Test',
|
||||
'lastname' => 'User',
|
||||
'email' => $roleName . '@example.com',
|
||||
'email' => $roleName.'@example.com',
|
||||
'cellphone' => '5555555555',
|
||||
'address_street' => '123 Main',
|
||||
'city' => 'City',
|
||||
|
||||
@@ -15,7 +15,7 @@ class PageControllerTest extends TestCase
|
||||
{
|
||||
$dir = public_path('html');
|
||||
File::ensureDirectoryExists($dir);
|
||||
File::put($dir . '/privacy_policy.html', '<h1>Privacy</h1>');
|
||||
File::put($dir.'/privacy_policy.html', '<h1>Privacy</h1>');
|
||||
|
||||
$response = $this->getJson('/api/v1/pages/privacy');
|
||||
|
||||
@@ -28,7 +28,7 @@ class PageControllerTest extends TestCase
|
||||
{
|
||||
$dir = public_path('html');
|
||||
File::ensureDirectoryExists($dir);
|
||||
File::delete($dir . '/help_center.html');
|
||||
File::delete($dir.'/help_center.html');
|
||||
|
||||
$response = $this->getJson('/api/v1/pages/help');
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
+2
-2
@@ -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,8 @@
|
||||
|
||||
namespace Tests\Feature\Api\V1\FullSurface;
|
||||
|
||||
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
|
||||
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
|
||||
|
||||
class ApiBatch12AttachmentLifecycleContractTest extends FullSurfaceE2EContractCase
|
||||
{
|
||||
|
||||
+5
-3
@@ -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,10 +22,12 @@ public function test_promotion_and_school_year_routes_reject_unsafe_rollback_pay
|
||||
{
|
||||
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
-4
@@ -6,13 +6,15 @@ 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());
|
||||
@@ -31,7 +33,7 @@ public function test_identity_recovery_routes_do_not_reveal_whether_user_exists(
|
||||
$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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -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,10 +20,12 @@ public function test_approval_and_delegation_routes_reject_forged_approval_metad
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-6
@@ -6,15 +6,17 @@ 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,13 +25,15 @@ public function test_core_identity_contracts_remain_compatible_for_web_mobile_an
|
||||
{
|
||||
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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -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,10 +22,12 @@ public function test_kiosk_scanner_and_attendance_routes_reject_forged_device_id
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -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,10 +22,12 @@ public function test_audit_and_log_routes_reject_client_supplied_actor_and_times
|
||||
{
|
||||
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,11 +6,13 @@ 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,
|
||||
@@ -27,10 +29,12 @@ public function test_bulk_assignment_routes_handle_duplicate_and_conflicting_ids
|
||||
{
|
||||
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 @@ public function test_batch14_route_families_have_cross_module_risk_coverage(): v
|
||||
];
|
||||
|
||||
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 @@ public function test_batch14_route_families_have_cross_module_risk_coverage(): v
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-4
@@ -6,14 +6,15 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +22,12 @@ public function test_archive_purge_and_retention_routes_require_safe_controlled_
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-5
@@ -6,16 +6,18 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +25,11 @@ public function test_finance_reconciliation_routes_reject_mismatched_invoice_par
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-5
@@ -6,17 +6,21 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +28,12 @@ public function test_notification_and_template_routes_reject_template_injection_
|
||||
{
|
||||
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,7 +15,9 @@ public function test_print_export_and_document_generation_routes_reject_tampered
|
||||
]);
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +27,7 @@ public function test_print_export_and_document_generation_routes_reject_tampered
|
||||
$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,7 +26,9 @@ public function test_batch14_mega_sweep_keeps_new_risk_payloads_under_controlled
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-4
@@ -6,15 +6,19 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +28,7 @@ public function test_relationship_owner_override_fields_are_not_trusted_across_r
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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 @@ public function test_api_auth_routes_do_not_depend_on_browser_session_cookies():
|
||||
$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,14 +6,16 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +23,8 @@ public function test_file_and_signed_url_routes_reject_path_and_disk_overrides()
|
||||
{
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ 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']);
|
||||
@@ -28,5 +27,4 @@ class ApiBatch15AdminImpersonationSessionBoundaryContractTest extends FullSurfac
|
||||
$this->assertStringNotContainsString('access_token', strtolower($parentResponse->getContent()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ 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']);
|
||||
@@ -46,5 +45,4 @@ class ApiBatch15AuthTokenRotationAndRevocationContractTest extends FullSurfaceE2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ 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']);
|
||||
@@ -30,5 +29,4 @@ class ApiBatch15BackgroundCommandAndSchedulerContractTest extends FullSurfaceE2E
|
||||
$this->assertControlled($adminResponse, $method, $uri);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ 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']);
|
||||
@@ -44,5 +43,4 @@ class ApiBatch15ConsentPrivacyAndDataSubjectRightsContractTest extends FullSurfa
|
||||
$this->assertStringNotContainsString('db_password', strtolower($response->getContent()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ 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']);
|
||||
@@ -31,5 +30,4 @@ class ApiBatch15CrossSchoolIdentityCollisionContractTest extends FullSurfaceE2EC
|
||||
$this->assertStringNotContainsString('duplicate entry', strtolower($response->getContent()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ 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']);
|
||||
@@ -32,5 +31,4 @@ class ApiBatch15DataResidencyBackupAndRestoreContractTest extends FullSurfaceE2E
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ 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']);
|
||||
@@ -28,5 +27,4 @@ class ApiBatch15DomainEventOutboxAndNotificationConsistencyContractTest extends
|
||||
$this->assertControlled($response, $method, $uri);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ 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']);
|
||||
@@ -28,5 +27,4 @@ class ApiBatch15FeatureFlagExperimentAndRolloutContractTest extends FullSurfaceE
|
||||
$this->assertControlled($response, $method, $uri);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ 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) {
|
||||
@@ -29,5 +28,4 @@ class ApiBatch15FormRequestAuthorizationCoverageContractTest extends FullSurface
|
||||
$this->assertControlled($response, $method, $uri);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ 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']);
|
||||
@@ -27,5 +26,4 @@ class ApiBatch15GlobalSearchAndDirectoryEnumerationContractTest extends FullSurf
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ 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']);
|
||||
@@ -31,5 +30,4 @@ class ApiBatch15LocalizationContentDirectionAndEncodingContractTest extends Full
|
||||
$this->assertStringNotContainsString('malformed utf-8', strtolower($response->getContent()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ 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']);
|
||||
@@ -31,5 +30,4 @@ class ApiBatch15MetadataFilteringAndSparseFieldsetSecurityContractTest extends F
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ 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']);
|
||||
@@ -16,7 +15,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',
|
||||
@@ -31,5 +30,4 @@ class ApiBatch15RateLimitAndAbusePatternContractTest extends FullSurfaceE2EContr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ 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 = [
|
||||
@@ -43,5 +42,4 @@ class ApiBatch15RegressionUltraSweepTest extends FullSurfaceE2EContractCase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ 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']);
|
||||
@@ -34,5 +33,4 @@ class ApiBatch15ReportDrilldownAndAggregatePrivacyContractTest extends FullSurfa
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ 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', '[]'];
|
||||
@@ -18,7 +17,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());
|
||||
@@ -26,5 +25,4 @@ class ApiBatch15RouteParameterPoisoningExpansionTest extends FullSurfaceE2EContr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ 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']);
|
||||
@@ -29,5 +28,4 @@ class ApiBatch15StudentSafetyIncidentEscalationContractTest extends FullSurfaceE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
-1
@@ -4,7 +4,6 @@ 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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user