Skip to content

Testing ​

Run everything from the repo root unless a step says otherwise.

The gate ​

bash
npm run verify        # backend tests and PHPStan, lint, web tests, type checks, web build

Before calling a change done, also run the checks for the part you touched.

Backend ​

CommandDoes
npm run backend:testcomposer test: clears config, then php artisan test (Pest runs the PHPUnit-style tests too)
cd apps/backend-laravel && php artisan test --filter=NameOne test class or method
cd apps/backend-laravel && composer test:pestPest directly
npm run backend:analysePHPStan with Larastan at level 5 over app, routes and database/seeders

Tests live in apps/backend-laravel/tests: Feature (about 90 files, one per module) and Unit. Pest.php applies the shared TestCase to the Feature folder.

phpunit.xml sets the environment: SQLite in memory, array cache and sessions, QUEUE_CONNECTION=sync, MAIL_MAILER=array, broadcasting off and EXTENSIBILITY_RETHROW=true. Rethrow means an exception inside a plugin listener fails the test instead of being logged and skipped. So tests run on SQLite while development uses Postgres and the demo uses MySQL. Write portable SQL, and give composite indexes and foreign keys explicit short names (under 64 characters) so MySQL accepts them.

What a good module test covers, following the reference module (git show 40a3a41 --stat):

  • The happy path for each endpoint.
  • Permission denied (403) for a user without the key.
  • Tenant isolation: a record in another organization is a 404, and a foreign id in the body fails validation.
  • Validation errors and edge cases.
  • Workflow rules, such as an invalid status move or an edit to a locked record.
  • Idempotency on a retry, and audit records on mutations.
  • AuthorizationAndValidationTest checks that every route has permission coverage and that TenantResourcePolicy covers every tenant controller, so a new route without a permission fails it.

Demo seeders are skipped under tests (app()->runningUnitTests()). Prove seeders separately:

bash
cd apps/backend-laravel
DB_CONNECTION=sqlite DB_DATABASE=/path/to/scratch.sqlite php artisan migrate:fresh --seed

Postgres and MySQL ​

The default run is SQLite, which hides engine differences: LIKE case, uuid columns that reject non-uuid text, boolean columns, partial indexes, and a caught unique violation aborting a whole Postgres transaction. Run the suite on Postgres before a release or after touching queries or migrations:

bash
docker compose -f infra/local-dev/docker-compose.yml up -d postgres
docker compose -f infra/local-dev/docker-compose.yml exec postgres createdb -U rangefront rangefront_waste_test
cd apps/backend-laravel && composer test:pgsql

composer test:pgsql sets DB_CONNECTION=pgsql and the compose credentials through the environment, so phpunit.xml and the SQLite default stay as they are. It uses its own rangefront_waste_test database (and _test_N copies for parallel workers), because the tests rebuild the schema. Never point it at your development database. Under emulation on Apple Silicon the run is slow, so php artisan test --parallel --processes=6 with the same variables is faster.

Postgres runs a whole migrate:fresh in one transaction group, so the compose file raises max_locks_per_transaction to 256. A Postgres you start some other way needs that too, or parallel runs fail with "out of shared memory".

CI runs SQLite only. The public demo runs on MySQL 8 (see DEMO-APP.md); the suite has not been run there, but migrations, the rollback and the seeders have, and must keep working:

bash
docker run -d --name rf-mysql -e MYSQL_ROOT_PASSWORD=rf -e MYSQL_DATABASE=rf_test -p 33099:3306 mysql:8.0
cd apps/backend-laravel
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=33099 DB_DATABASE=rf_test DB_USERNAME=root DB_PASSWORD=rf php artisan migrate:fresh --seed

Use a throwaway database for both. migrate:fresh drops every table.

Fakes for outside services ​

Tests use Http::fake() for Stripe, Twilio, Anthropic, providers and council endpoints, and Mail::fake() or the array mailer for email. Don't let a test call a real service.

Web console ​

CommandDoes
npm --workspace apps/web-aurelia run lintESLint on src, test, e2e, and Stylelint on the CSS
npm --workspace apps/web-aurelia run testVitest, once (test:watch for watch mode). It runs lint first, through pretest
npm run web:buildProduction build. Run it before claiming a web change is done
npm run web:e2ePlaywright against a dev server, with demo login switched off

Vitest specs are in apps/web-aurelia/test, with setup.ts for the DOM. Specs cover the main states of many pages: empty, loading, error, permission denied and the happy path. The Playwright spec is e2e/operator-workflows.spec.ts.

Field app ​

bash
cd apps/mobile-expo
npx tsc --noEmit      # type check
npm run lint

Real-device behaviour (photo markup flattening, push registration, biometric unlock) is on the manual list in TODO.md. Expo is version-sensitive, so read apps/mobile-expo/AGENTS.md first.

Contracts ​

If you change API routes or response shapes, regenerate the contract and commit it:

bash
npm run contracts:generate

Plugins and themes ​

Plugin tests live beside the core ones in tests/Feature/Extensibility. The bundled plugins are covered there, and are a good template. See extending.

Before you commit ​

  1. npm run backend:test and npm run backend:analyse for backend changes.
  2. Web lint, test and build for console changes.
  3. npx tsc --noEmit for field app changes.
  4. Update TODO.md when a module moves from planned to built.

A$125 a month, or A$9,450 once with the source code. Contact us.