add test batches
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Api\V1\FullSurface;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
|
||||
|
||||
class ApiOwnerBoundaryAndPrivacyExpansionTest extends FullSurfaceE2EContractCase
|
||||
{
|
||||
public function test_parent_family_endpoints_do_not_expose_other_parent_records(): void
|
||||
{
|
||||
$other = $this->seedOtherFamily();
|
||||
|
||||
foreach ($this->otherFamilyPaths($other['student_id'], $other['parent_id']) as $path) {
|
||||
$response = $this->requestAs($this->parent, 'GET', $path);
|
||||
|
||||
$this->assertStatusIn($response, [200, 302, 401, 403, 404, 405, 419, 422], "parent privacy $path");
|
||||
$this->assertNoServerError($response, "parent privacy $path");
|
||||
|
||||
if ($response->getStatusCode() === 200 && $this->isJsonString($response->getContent())) {
|
||||
$body = json_encode($response->json(), JSON_THROW_ON_ERROR);
|
||||
$this->assertStringNotContainsString((string) $other['student_id'], $body, "$path exposed another family student id.");
|
||||
$this->assertStringNotContainsString($other['email'], $body, "$path exposed another family email.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function test_teacher_class_endpoints_do_not_expose_unassigned_class_records(): void
|
||||
{
|
||||
$other = $this->seedTeacherClassWithStudent(9802);
|
||||
$unassignedTeacher = $this->createApiUserWithRole('teacher');
|
||||
|
||||
foreach ($this->teacherClassPaths((int) $other['class_section_id'], (int) $other['student_id']) as $path) {
|
||||
$response = $this->requestAs($unassignedTeacher, 'GET', $path);
|
||||
|
||||
$this->assertStatusIn($response, [200, 302, 401, 403, 404, 405, 419, 422], "teacher class isolation $path");
|
||||
$this->assertNoServerError($response, "teacher class isolation $path");
|
||||
|
||||
if ($response->getStatusCode() === 200 && $this->isJsonString($response->getContent())) {
|
||||
$body = json_encode($response->json(), JSON_THROW_ON_ERROR);
|
||||
$this->assertStringNotContainsString((string) $other['student_id'], $body, "$path exposed unassigned student id.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function test_serialized_users_do_not_leak_sensitive_auth_material(): void
|
||||
{
|
||||
foreach ($this->identityAndDirectoryPaths() as $path) {
|
||||
$response = $this->requestAs($this->admin, 'GET', $path);
|
||||
|
||||
$this->assertStatusIn($response, [200, 204, 302, 401, 403, 404, 405, 419, 422], "sensitive serialization $path");
|
||||
$this->assertNoServerError($response, "sensitive serialization $path");
|
||||
|
||||
if ($response->getStatusCode() === 200) {
|
||||
$content = strtolower($response->getContent());
|
||||
$this->assertStringNotContainsString('password_hash', $content, "$path leaked password_hash.");
|
||||
$this->assertStringNotContainsString('remember_token', $content, "$path leaked remember_token.");
|
||||
$this->assertStringNotContainsString('api_token', $content, "$path leaked api_token.");
|
||||
$this->assertStringNotContainsString('two_factor_secret', $content, "$path leaked two_factor_secret.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array{parent_id:int, student_id:int, email:string} */
|
||||
private function seedOtherFamily(): array
|
||||
{
|
||||
$otherParent = $this->createApiUserWithRole('parent', [
|
||||
'email' => 'other.family.' . uniqid() . '@example.test',
|
||||
'firstname' => 'Other',
|
||||
'lastname' => 'Family',
|
||||
]);
|
||||
|
||||
$studentId = DB::table('students')->insertGetId([
|
||||
'school_id' => 'S-OTHER-' . random_int(1000, 9999),
|
||||
'firstname' => 'Other',
|
||||
'lastname' => 'Student',
|
||||
'dob' => '2016-01-01',
|
||||
'age' => 9,
|
||||
'gender' => 'Female',
|
||||
'parent_id' => $otherParent->id,
|
||||
'year_of_registration' => '2025',
|
||||
'school_year' => self::E2E_SCHOOL_YEAR,
|
||||
'semester' => self::E2E_SEMESTER,
|
||||
'is_active' => 1,
|
||||
]);
|
||||
|
||||
return ['parent_id' => $otherParent->id, 'student_id' => $studentId, 'email' => $otherParent->email];
|
||||
}
|
||||
|
||||
/** @return list<string> */
|
||||
private function otherFamilyPaths(int $studentId, int $parentId): array
|
||||
{
|
||||
return [
|
||||
"api/v1/parents/students/$studentId",
|
||||
"api/v1/parents/students/$studentId/attendance",
|
||||
"api/v1/parents/students/$studentId/invoices",
|
||||
"api/v1/parents/$parentId/profile",
|
||||
"api/v1/families/$parentId",
|
||||
"api/v1/emergency-contacts/student/$studentId",
|
||||
];
|
||||
}
|
||||
|
||||
/** @return list<string> */
|
||||
private function teacherClassPaths(int $classSectionId, int $studentId): array
|
||||
{
|
||||
return [
|
||||
"api/v1/teacher/classes/$classSectionId/roster",
|
||||
"api/v1/teacher/classes/$classSectionId/progress",
|
||||
"api/v1/teacher/students/$studentId/scores",
|
||||
"api/v1/teacher/classes/$classSectionId/attendance",
|
||||
"api/v1/class-progress/$classSectionId",
|
||||
];
|
||||
}
|
||||
|
||||
/** @return list<string> */
|
||||
private function identityAndDirectoryPaths(): array
|
||||
{
|
||||
return [
|
||||
'api/v1/auth/me',
|
||||
'api/v1/users',
|
||||
'api/v1/parents/profile',
|
||||
'api/v1/teachers/classes',
|
||||
'api/v1/students',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user