### Copenhagen Truck Wash API — Development Guidelines (Project‑specific)

#### Scope
This document captures project‑specific knowledge for building, configuring, testing, and extending the API. It assumes an advanced developer familiar with Docker, PHP 8.2, and HTTP APIs.

---

### Build and Configuration

- Stack overview (local):
  - Reverse proxy/router: Traefik 2.x (`docker-compose.yml` service `traefik`).
  - Web server: Caddy (`caddy`) serving the PHP app from `services/nginx/app` and proxied by Traefik.
  - PHP runtime: Multiple PHP‑FPM containers (`php1`..`php5`), sharing the bind‑mounted app directory.
  - Redis: `redis` for caching/queues/locks.

- App location: `services/nginx/app` is the effective application root (many scripts/tests derive `WD` to point here).

- Composer and dependencies:
  - `services/php/Dockerfile` installs Composer and PHP extensions.
  - `services/php/docker-entrypoint.sh` performs a guarded Composer install on `php1` at container start when `AUTO_COMPOSER_INSTALL=true` and `composer.json` is present.
  - App dependencies live under `services/nginx/app/composer.json` (note: very light, primarily runtime libs; dev tool `rector/rector`).

- Configuration source of truth during containerized runs is environment variables consumed by `services/nginx/app/config.php`.
  - `config.php` requires `USE_ENV=true`; otherwise it throws an exception. Many CLI scripts/tests bypass `config.php` entirely to remain env‑agnostic.

- Local run targets and routing:
  - Traefik exposes:
    - `http://localhost` → routes to Caddy → app (HTTP only for dev).
    - `https://localhost` → also mapped, using Traefik’s default/self‑signed dev cert.
    - `http(s)://localhost/api/*` → Traefik strip‑prefix middleware forwards to Caddy; the app sees paths without the `/api` prefix.
  - For production‑like HTTPS with real certs, see `README.md` for `LETSENCRYPT_PATH` mounting strategy (only needed if you want the exact `api.truckwash.dk` TLS behavior locally).

- Minimal bring‑up for local development:
  - Prerequisites: Docker Desktop 4.x+.
  - First run will build PHP images and start dependent services. Composer install runs automatically on `php1`.
  - Recommended minimal set:
    - `docker compose up -d traefik redis php1 caddy`
  - Full set (scale out PHP or add observability as needed):
    - `docker compose up -d` (starts `traefik`, `redis`, `caddy`, `php1`..`php5`, and other declared services).
  - Logs:
    - `docker compose logs -f caddy`
    - `docker compose logs -f php1`
    - `docker compose logs -f traefik`

- Security note: `docker-compose.yml` currently embeds sensitive env values (DB, API tokens). Treat the file as secret in private repos; never re‑publish as is. Prefer `.env` overrides and secrets providers for wider teams.

---

### Testing

The repository does not use PHPUnit for the app. Instead, tests are lightweight CLI scripts under `services/nginx/app/tests`. Conventions:

- Test style
  - Self‑contained procedural PHP scripts intended to be executed with `php`. No framework required.
  - Many tests define the `WD` constant to the app root and then `require_once` specific class/trait/interface files they exercise.
  - Integration‑style scripts that need configuration will rely on `services/nginx/app/config.php` and so must run within a properly provisioned environment (Docker containers with `USE_ENV=true`).
  - Fast unit‑style scripts should avoid `config.php` and any I/O; they manually include only what’s needed and/or use test doubles.

- Running tests from host (fastest path)
  - Prereq: PHP CLI available on host. Verified with:
    - `php -v` → observed on our env: `PHP 8.2.30 (cli)`
  - Example: an existing, fully self‑contained test validating subuser permission wiring:
    - Command:
      - `php services/nginx/app/tests/subusers/SelfservePermissionInitTest.php`
    - Verified output (captured):
      -
        ```
        ✔ SELFSERVE_ADD is granted as expected
        ✔ SELFSERVE_LIST is not granted as expected
        ✔ SELFSERVE_EDIT is not granted as expected
        ✔ SELFSERVEDELETE is not granted as expected
        SelfservePermissionInitTest completed.
        ```

- Running tests inside Docker (when host PHP is unavailable or when env is required)
  - Ensure containers are up: `docker compose up -d traefik redis php1 caddy`
  - Execute a test within `php1`:
    - `docker compose exec -T php1 php /var/www/html/tests/subusers/SelfservePermissionInitTest.php`
  - For integration tests that depend on `config.php` (e.g., Redis/DB), environment variables are preconfigured in `docker-compose.yml` for the PHP services. Running inside `php1` will satisfy `USE_ENV=true`.

- Adding a new test
  - Place the file under `services/nginx/app/tests/<domain>/YourTest.php`.
  - At top of the file, define `WD` if not already defined:
    ```php
    if (!defined('WD')) { define('WD', dirname(__DIR__, 2)); }
    ```
  - Prefer self‑contained tests that avoid I/O. If you need to touch internal classes without Composer autoloading, include files directly, mirroring existing tests.
  - If the test must hit Redis/DB or rely on globals from `config.php`, run it inside a PHP container (`php1`) with `USE_ENV=true`.

- Demonstration: creating and running a minimal test
  - We created a temporary, env‑agnostic test at `services/nginx/app/tests/examples/HelloWorldTest.php` with these semantics:
    - Define `WD`, perform trivial assertions, and print success markers.
  - Command executed and verified output:
    - Command:
      - `php services/nginx/app/tests/examples/HelloWorldTest.php`
    - Output (captured):
      -
        ```
        ✔ Basic arithmetic works (2 + 2 = 4)
        ✔ WD points to the application root: C:\\Users\\2jepp\\PhpstormProjects\\api\\services\\nginx\\app
        HelloWorldTest completed.
        ```
  - The example file was removed afterwards to keep the repository unchanged. You can replicate by creating a similar file and removing it after execution.

---

### Additional Development Information

- Routing and HTTP surface
  - Routes live under `services/nginx/app/routes`. Example route `exampleRoute.php` exposes `GET /example` returning `{"message":"Hello World!"}`. Local access paths (with Traefik):
    - `http://localhost/example` (direct)
    - `http://localhost/api/example` (Traefik strip‑prefix `/api` → still routes to `/example` in the app).
  - The OpenAPI contract is at the repo root `openapi.yaml` (large, authoritative). Keep it synchronized with implemented routes and payloads.

- Module layout and traits
  - Domain modules live in `services/nginx/app/modules/*` and are heavily trait‑based. Tests often pull in precise files from here to avoid full app bootstrap.
  - Example: subusers module (`modules/subusers/...`) provides permission node containers and helpers. The test `tests/subusers/SelfservePermissionInitTest.php` demonstrates overriding DB‑backed methods to inject permissions for fast, deterministic checks.

- Config and globals
  - `services/nginx/app/config.php` populates globals like `$CONFIG_DB`, `$REDIS_CONFIG`, etc., but only when `USE_ENV=true`. If you see tests throwing “Environment variables are not set”, run them inside Docker or set required envs for host PHP.

- Code style
  - `.editorconfig` at repo root configures formatting. Key PHP rules:
    - Encoding: UTF‑8 with CRLF line endings.
    - Indent: 4 spaces; continuation indent 4.
    - Class brace style: next_line; function/method blank lines: 1.
    - Import sorting: alphabetic; various alignment toggles are disabled.
  - Match existing patterns (procedural scripts for tests; namespacing in app code; traits for cross‑cutting concerns). Avoid adding new frameworks for tests unless explicitly requested.

- Composer / Autoload
  - There is no Composer autoload configured for the app code in `composer.json`; tests include files directly. If you introduce autoloading, coordinate with Docker entrypoint behaviors and ensure zero‑downtime for existing scripts.

- Performance & reliability hints for tests
  - Prefer small, deterministic CLI scripts; inject dependencies and override I/O methods (as shown in `SelfservePermissionInitTest.php`).
  - Avoid hitting external services (Stripe, e‑conomic, MinIO) from tests; instead, stub/override or provide fakes.

---

### Quick Reference

- Bring up minimal dev stack:
  - `docker compose up -d traefik redis php1 caddy`

- Run a fast, self‑contained test (host PHP):
  - `php services/nginx/app/tests/subusers/SelfservePermissionInitTest.php`

- Run a test inside Docker (env‑backed):
  - `docker compose exec -T php1 php /var/www/html/tests/subusers/SelfservePermissionInitTest.php`
