Harden published curriculum and student flows
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
// SQLite memory fixture: no production DB, no network, no money or student writes.
|
||||
// MySQL/HTTP concurrency and real-school roster verification remain pending.
|
||||
require_once dirname(__DIR__) . '/app/Core/Database.php';
|
||||
require_once dirname(__DIR__) . '/app/Services/StudentAccessControlService.php';
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Services\StudentAccessControlService;
|
||||
|
||||
$pdo = class_exists('Pdo\\Sqlite') ? new \Pdo\Sqlite('sqlite::memory:') : new PDO('sqlite::memory:');
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
if ($pdo instanceof \Pdo\Sqlite) $pdo->createFunction('NOW', static fn() => gmdate('Y-m-d H:i:s'), 0);
|
||||
else $pdo->sqliteCreateFunction('NOW', static fn() => gmdate('Y-m-d H:i:s'), 0);
|
||||
(new ReflectionProperty(Database::class, 'instance'))->setValue(null, $pdo);
|
||||
|
||||
$pdo->exec('CREATE TABLE directorates (id INTEGER PRIMARY KEY, type TEXT)');
|
||||
$pdo->exec('CREATE TABLE schools (id INTEGER PRIMARY KEY, name TEXT, type TEXT, directorate_id INTEGER)');
|
||||
$pdo->exec('CREATE TABLE students (id INTEGER PRIMARY KEY, national_id TEXT, grade_level TEXT, school_id INTEGER, is_school_sponsored INTEGER)');
|
||||
$pdo->exec('CREATE TABLE teachers (id INTEGER PRIMARY KEY, is_school_exclusive INTEGER, is_marketplace_public INTEGER)');
|
||||
$pdo->exec('CREATE TABLE courses (id INTEGER PRIMARY KEY, teacher_id INTEGER, school_id INTEGER, is_school_exclusive INTEGER, is_published INTEGER, price_jod NUMERIC, grade_level TEXT)');
|
||||
$pdo->exec('CREATE TABLE lessons (id INTEGER PRIMARY KEY, course_id INTEGER, is_free_preview INTEGER)');
|
||||
$pdo->exec('CREATE TABLE school_rosters (school_id INTEGER, claimed_student_id INTEGER, is_claimed INTEGER, grade_level TEXT)');
|
||||
$pdo->exec('CREATE TABLE course_access_passes (id INTEGER PRIMARY KEY, student_id INTEGER, course_id INTEGER, pass_type TEXT, expires_at TEXT, is_active INTEGER)');
|
||||
$pdo->exec("INSERT INTO schools VALUES (1,'School A','private',NULL),(2,'School B','private',NULL)");
|
||||
$pdo->exec("INSERT INTO students VALUES (1,'a','grade_10',NULL,0),(2,'b','grade_10',1,1),(3,'c','grade_10',2,1),(4,'d','grade_9',NULL,0),(5,'e','grade_10',1,1)");
|
||||
$pdo->exec('INSERT INTO teachers VALUES (1,0,1),(2,1,0)');
|
||||
$pdo->exec("INSERT INTO courses VALUES (10,1,NULL,0,1,10,'grade_10'),(11,2,1,1,1,30,'grade_10'),(12,2,2,1,1,30,'grade_10'),(13,1,NULL,0,1,0,'grade_10'),(14,1,NULL,0,0,0,'grade_10')");
|
||||
$pdo->exec('INSERT INTO lessons VALUES (100,10,0),(110,11,0),(120,12,0),(130,13,0),(140,14,0)');
|
||||
$pdo->exec("INSERT INTO school_rosters VALUES (1,2,1,'grade_10'),(2,3,1,'grade_10')");
|
||||
$pdo->exec("INSERT INTO course_access_passes VALUES (1,1,10,'full_marketplace',NULL,1),(2,2,10,'school_included',NULL,1)");
|
||||
|
||||
$checks = 0;
|
||||
function expectAccess(int $studentId, int $courseId, int $lessonId, bool $allowed, string $reason, bool $legacySideEffects = false): void
|
||||
{
|
||||
global $checks;
|
||||
$result = StudentAccessControlService::validateLessonAccess($studentId, null, 'grade_10', $courseId, $lessonId, $legacySideEffects);
|
||||
if ((bool)$result['allowed'] !== $allowed || (string)$result['reason'] !== $reason) {
|
||||
throw new RuntimeException("Unexpected access for student {$studentId}, course {$courseId}: " . json_encode($result));
|
||||
}
|
||||
$checks++;
|
||||
}
|
||||
|
||||
expectAccess(1, 10, 100, true, 'paid_pass_active');
|
||||
expectAccess(1, 11, 110, false, 'school_scope_mismatch');
|
||||
expectAccess(2, 11, 110, true, 'school_course_included');
|
||||
expectAccess(2, 12, 120, false, 'school_scope_mismatch');
|
||||
expectAccess(2, 10, 100, false, 'payment_required');
|
||||
expectAccess(2, 13, 130, true, 'free_course');
|
||||
expectAccess(5, 11, 110, false, 'school_roster_unverified');
|
||||
expectAccess(4, 13, 130, false, 'grade_mismatch', true);
|
||||
expectAccess(1, 14, 140, false, 'course_unavailable');
|
||||
expectAccess(1, 10, 110, false, 'lesson_course_mismatch');
|
||||
expectAccess(99, 13, 130, false, 'unauthenticated_or_not_found');
|
||||
|
||||
$grade = $pdo->query('SELECT grade_level FROM students WHERE id=4')->fetchColumn();
|
||||
$passes = (int)$pdo->query('SELECT COUNT(*) FROM course_access_passes')->fetchColumn();
|
||||
if ($grade !== 'grade_9' || $passes !== 2) throw new RuntimeException('Access reads changed grade or issued passes');
|
||||
$checks++;
|
||||
echo "PASS {$checks} local student-access scope checks (not an HTTP/MySQL test)\n";
|
||||
Reference in New Issue
Block a user