# PHPUnit in this project (no Composer) This legacy project does not use Composer. To run tests, use the PHPUnit PHAR. Quick start: 1. Download phpunit.phar (pick a version compatible with your PHP): ```bash curl -L -o phpunit.phar https://phar.phpunit.de/phpunit-9.6.phar chmod +x phpunit.phar ``` Alternatively, place `phpunit.phar` somewhere in your PATH. 2. From the project root, run the tests: ```bash php phpunit.phar # or php phpunit.phar --configuration phpunit.xml.dist ``` Structure added: - `phpunit.xml.dist` — PHPUnit configuration using `tests/bootstrap.php`. - `tests/bootstrap.php` — sets up error reporting and an autoloader matching `./inc/.inc.php`. - `tests/` — test files live here. Initial tests added for `IpUtil` under `tests/Inc/IpUtilTest.php`. Notes: - The bootstrap avoids including `index.php`/`api.php` to prevent side effects; it only loads `config.php` if present and registers an autoloader for `inc/`. - Tests target self-contained helpers first to build coverage without refactoring entangled code. ## Testing real classes when stubs exist Some tests load global stubs (e.g., `Database`, `User`) by default via a stub-first autoloader. To test a real class that also has a stub: - Run the test in a separate PHP process so it starts clean and doesn’t inherit classes from prior tests: - Add these annotations to the test class (or individual test methods): - `@runTestsInSeparateProcesses` - `@preserveGlobalState disabled` - Before the first reference to the class, tell the stub autoloader to allowlist the real class: ```php $GLOBALS['__TEST_USE_REAL_CLASSES'] = ['User']; ``` Alternatively, set an environment variable when running PHPUnit: ```bash TEST_USE_REAL_CLASSES=User php phpunit.phar tests/Inc/UserTest.php ``` - Optionally force-load the class immediately afterwards: ```php require_once __DIR__ . '/../../inc/user.inc.php'; ``` See `tests/Inc/UserTest.php` for a complete example. ## Module testing (modules-available/*/page.inc.php) This project’s modules define page classes (e.g., `Page_SysLog`) that normally run under `index.php`, which defines a global `Page` class and provides request/rendering infrastructure. For unit tests, a lightweight harness is provided via stubs under `tests/Stubs`: - `Page` — minimal base with `preprocess()` and `render()` calling the module’s `doPreprocess()`/`doRender()`. Also provides `Page::getModule()` and `getIdentifier()` as needed by some code. - `Request` — `any()`, `get()`, `post()` backed by PHP superglobals with simple type casting. - `Render` — records `addTemplate()` calls for assertions. - `Permission` — `addGlobalTags()` no-op that fills the provided array with the requested tags. - `Paginate` — captures the SQL and args, returns queued results in tests, and records the template/data passed to `render()`. - Existing stubs like `Database`, `User`, `Session`, `Message`, `Dictionary`, and `Property` are reused. How to write a module test: - Reset stubs and superglobals in `setUp()`. - Make sure your test loads the module file directly: ```php require_once __DIR__ . '/../../modules-available/syslog/page.inc.php'; $page = new Page_SysLog('syslog'); $page->render(); ``` - Drive inputs via `$_GET`, `$_POST`, and the `User`/`Property` stubs. - Seed DB and pagination data by pushing into stub queues: ```php Database::$simpleQueryResponses[] = [ ['logtypeid' => 'x', 'counter' => 1] ]; Paginate::$queuedResults[] = [ ['dateline' => time(), 'logtypeid' => 'x', ...] ]; ``` - Assert on `Paginate::$lastExecArgs`, `Paginate::$lastRender`, and `Render::$templates`. See `tests/Modules/SyslogPageTest.php` for a complete example that covers: - Permission denial path (no `view` permission) - Filter/search handling and type list augmentation - Location restriction joining via `getAllowedLocations()` ## Database-backed tests without MySQL (SQLite test backend) Global schema and seed data: - The SQLite backend now auto-creates a minimal schema on first use and seeds a canonical dataset so tests can share consistent data without duplicating setup. - Tables created include: location, subnet, machine, user, role, role_x_user, role_x_location, role_x_permission, clientlog, mail_queue, mail_config, audit. - Seed content highlights: - Locations: Campus (1) → Building A (2) → Room 101 (4); Building B (3); Offsite (5) - Subnets: 10.0.0.0/24 @ 1, 10.0.0.128/26 @ 2, 192.168.1.0/24 @ 5 - Users/roles: alice(1), bob(2); roles Admin(1, builtin), Tech(2) with sample permissions Utility helpers (optional): - `Database::resetSchema()` — drop and recreate the in-memory DB with fresh schema and seed. - `Database::reseed()` — reinsert the seed data into the existing schema. - `Database::truncateAll()` — delete rows from all known tables. - `Database::pdo()` — access the underlying PDO if you need custom SQL. Parameter handling: - Array parameters in `IN (:list)` and bulk inserts like `VALUES :arg` (legacy pattern) are expanded automatically. There is a very thin translation layer that tries to convert MySQL-specific syntax into SQLite syntax. It might need improvements and extensions as more tests get added.