Harden published curriculum and student flows
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
// Isolated SQLite memory fixture. No server, production database, or money movement.
|
||||
// FOR UPDATE concurrency semantics remain a separate MySQL staging test.
|
||||
require_once dirname(__DIR__) . '/app/Core/Database.php';
|
||||
require_once dirname(__DIR__) . '/app/Services/TeacherLedgerService.php';
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Services\TeacherLedgerService;
|
||||
|
||||
$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);
|
||||
}
|
||||
$property = new ReflectionProperty(Database::class, 'instance');
|
||||
$property->setValue(null, $pdo);
|
||||
$pdo->exec('CREATE TABLE ledger_accounts (id INTEGER PRIMARY KEY, code TEXT, owner_type TEXT, owner_id INTEGER, currency TEXT)');
|
||||
$pdo->exec('CREATE TABLE ledger_entries (id INTEGER PRIMARY KEY, debit_account_id INTEGER, credit_account_id INTEGER, amount_fils INTEGER)');
|
||||
$pdo->exec('CREATE TABLE teacher_withdrawal_holds (id INTEGER PRIMARY KEY, uuid TEXT, teacher_id INTEGER, amount_fils INTEGER, status TEXT, idempotency_key TEXT, released_at TEXT, release_reason TEXT)');
|
||||
|
||||
function checkLedger(bool $condition, string $message): void
|
||||
{
|
||||
if (!$condition) throw new RuntimeException($message);
|
||||
}
|
||||
|
||||
$empty = TeacherLedgerService::balance(42);
|
||||
checkLedger($empty['account_id'] === null && $empty['available_fils'] === 0, 'Missing account must have zero available');
|
||||
checkLedger((int)$pdo->query('SELECT COUNT(*) FROM ledger_accounts')->fetchColumn() === 0, 'Balance read created an account');
|
||||
|
||||
$pdo->exec("INSERT INTO ledger_accounts VALUES (1, 'teacher_available', 'teacher', 42, 'JOD')");
|
||||
$pdo->exec('INSERT INTO ledger_entries VALUES (1, 2, 1, 2000), (2, 1, 2, 500)');
|
||||
$pdo->exec("INSERT INTO teacher_withdrawal_holds VALUES (1, '5de1e10c-a2ee-47c4-a9ba-5db6e894a94f', 42, 400, 'held', 'fixture-key', NULL, NULL)");
|
||||
$balance = TeacherLedgerService::balance(42);
|
||||
checkLedger($balance['gross_fils'] === 1500 && $balance['held_fils'] === 400 && $balance['available_fils'] === 1100, 'Signed net balance or hold subtraction wrong');
|
||||
|
||||
$wrongTeacher = TeacherLedgerService::releaseHold(43, '5de1e10c-a2ee-47c4-a9ba-5db6e894a94f', 'سبب مراجعة');
|
||||
checkLedger($wrongTeacher['status'] === 'hold_not_releasable', 'Cross-teacher release was allowed');
|
||||
$released = TeacherLedgerService::releaseHold(42, '5de1e10c-a2ee-47c4-a9ba-5db6e894a94f', 'فشل نهائي موثق');
|
||||
checkLedger($released['status'] === 'released', 'First release failed');
|
||||
$again = TeacherLedgerService::releaseHold(42, '5de1e10c-a2ee-47c4-a9ba-5db6e894a94f', 'إعادة');
|
||||
checkLedger($again['status'] === 'hold_not_releasable', 'Second release changed state');
|
||||
$row = $pdo->query('SELECT status, release_reason FROM teacher_withdrawal_holds WHERE id=1')->fetch(PDO::FETCH_ASSOC);
|
||||
checkLedger($row['status'] === 'released' && $row['release_reason'] === 'فشل نهائي موثق', 'Release reason not persisted');
|
||||
checkLedger(TeacherLedgerService::balance(42)['available_fils'] === 1500, 'Released hold still reduced availability');
|
||||
|
||||
$invalid = TeacherLedgerService::placeWithdrawalHold(42, 0, 'valid-key-1234567');
|
||||
checkLedger($invalid['http_status'] === 400, 'Zero hold should be rejected before database access');
|
||||
echo "PASS 9 local ledger checks (not a MySQL concurrency test)\n";
|
||||
Reference in New Issue
Block a user