---
apply: always
---

### Creating and maintaining tests — Rules (Project‑specific)

These rules apply to all tests under `services/nginx/app/tests` for the Copenhagen Truck Wash API. They codify how to create, run, and maintain the lightweight CLI PHP tests used in this repository.

1. Scope and philosophy
   - Prefer fast, deterministic CLI scripts over framework‑based tests.
   - Keep unit‑style tests isolated from I/O (no DB/Redis/files/network). Use test doubles and explicit `require_once` for only the code exercised.
   - Use integration tests sparingly and only when configuration/globals are required. Run them in Docker with `USE_ENV=true`.

2. Location and naming
   - Place tests in `services/nginx/app/tests/<domain>/YourTest.php` where `<domain>` reflects the feature area (e.g., `subusers`, `orders`, `redis`).
   - File names must end with `Test.php` (e.g., `SelfservePermissionInitTest.php`).
   - Keep one coherent scenario per file. If a file grows beyond ~150 lines or mixes unrelated scenarios, split it.

3. Execution modes
   - Unit‑style (preferred): run on host with PHP CLI.
     - Command: `php services/nginx/app/tests/<domain>/<Name>Test.php`
   - Integration‑style (needs env/globals): run inside Docker (php1) where `USE_ENV=true` and env vars are provided by `docker-compose.yml`.
     - Start minimal stack: `docker compose up -d traefik redis php1 caddy`
     - Command: `docker compose exec -T php1 php /var/www/html/tests/<domain>/<Name>Test.php`

4. Bootstrapping and includes
   - At the top of every test, define `WD` if not already defined:
     ```php
     if (!defined('WD')) { define('WD', dirname(__DIR__, 2)); }
     ```
   - For unit‑style tests: include only the files you exercise via explicit `require_once WD . '/path/to/file.php';`.
   - Do NOT include `config.php` for unit‑style tests; it throws if `USE_ENV` is not set.
   - For integration‑style tests that need globals (`$CONFIG_DB`, `$REDIS_CONFIG`, etc.): `require_once WD . '/config.php';` and run the test inside `php1`.

5. Output and exit codes
   - Use simple helpers for human‑readable output:
     ```php
     function ok($message){ echo "\n\033[32m✔ $message\033[0m\n"; }
     function fail($message){ echo "\n\033[31m✖ $message\033[0m\n"; }
     ```
   - Prefer collecting failures and exiting non‑zero when any assertion fails:
     ```php
     $failed = 0;
     // ... on failure set $failed = 1;
     exit($failed);
     ```
   - End with a concise completion line, e.g., `echo "\n<Name>Test completed.\n";`.

6. Determinism, time, and randomness
   - Avoid `sleep()` and real time dependencies. If time is relevant, pass it as a parameter or stub the time source.
   - Do not use random values unless strictly necessary; if used, seed explicitly (`mt_srand(1234)`) and document it.

7. External I/O and side effects
   - Never call external services (Stripe, e‑conomic, MinIO, Slack, WordPress) from tests. Stub or override code paths as shown in `tests/subusers/SelfservePermissionInitTest.php`.
   - Do not modify files under the repository during tests. If temporary files are required, use `sys_get_temp_dir()` and delete them before exit.

8. Redis and database (integration‑only)
   - Run inside Docker (`php1`) so `config.php` can populate `$REDIS_CONFIG` and `$CONFIG_DB`.
   - Use unique, namespaced Redis keys for test data (e.g., `test:<feature>:<uuid>`). Clean them up at the end. Do NOT call `FLUSHALL`.
   - Prefer fakes/stubs over real DB writes. If DB writes are unavoidable, scope them to clearly identifiable rows and delete them before exit.

9. Performance budgets
   - Each unit‑style test script should complete in < 100 ms on a typical dev machine.
   - Each integration‑style test should complete in < 2 s and must avoid N+1 loops or heavy queries.

10. When to add or update tests
   - New route/feature: add at least one unit‑style test for core logic. If behavior depends on configuration/globals, add a minimal integration test.
   - Bug fix: first reproduce with a failing test; then fix and ensure the test passes.
   - Refactor: keep behavior‑preserving tests green. If public behavior changes intentionally, update both tests and `openapi.yaml` accordingly.

11. Maintenance expectations
   - Do not weaken or remove assertions to “make tests pass”. Investigate and fix root causes.
   - Keep tests small and readable. Extract tiny helpers within the test file rather than introducing new shared libraries.
   - Match the project code style and line endings (see `.editorconfig`: UTF‑8, CRLF, 4‑space indent, class brace `next_line`).

12. Templates
   - Unit‑style template:
     ```php
     <?php
     if (!defined('WD')) { define('WD', dirname(__DIR__, 2)); }
     // require_once WD . '/modules/foo/classes/bar.php';

     function ok($m){ echo "\n\033[32m✔ $m\033[0m\n"; }
     function fail($m){ echo "\n\033[31m✖ $m\033[0m\n"; }
     $failed = 0;

     // Arrange / Act
     $sum = 2 + 2;

     // Assert
     if ($sum === 4) { ok('Basic arithmetic works (2 + 2 = 4)'); } else { fail('Expected 4'); $failed = 1; }

     echo "\nExampleUnitTest completed.\n";
     exit($failed);
     ```

   - Integration‑style template (Redis example):
     ```php
     <?php
     if (!defined('WD')) { define('WD', dirname(__DIR__, 2)); }
     require_once WD . '/config.php'; // populates $REDIS_CONFIG when USE_ENV=true
     require_once WD . '/classes/redis.php';

     function ok($m){ echo "\n\033[32m✔ $m\033[0m\n"; }
     function fail($m){ echo "\n\033[31m✖ $m\033[0m\n"; }
     $failed = 0;

     // Use a namespaced test key
     $key = 'test:redis:example:' . uniqid('', true);

     try {
         $r = new classes\redis();
         $r->set($key, 'pong');
         $val = $r->get($key);
         if ($val === 'pong') { ok('Redis set/get works for namespaced key'); } else { fail('Unexpected value from Redis'); $failed = 1; }
     } finally {
         // Best‑effort cleanup
         try { $r->del($key); } catch (\Throwable $e) { /* ignore */ }
     }

     echo "\nExampleRedisIntegrationTest completed.\n";
     exit($failed);
     ```

13. Quick reference
   - Run unit‑style test on host: `php services/nginx/app/tests/<domain>/<Name>Test.php`
   - Run integration‑style test in Docker: `docker compose exec -T php1 php /var/www/html/tests/<domain>/<Name>Test.php`
   - Minimal stack for integration: `docker compose up -d traefik redis php1 caddy`

For broader build, configuration, and existing examples, see `.junie/guidelines.md` (Testing section).

---
apply: always
---

### Creating and maintaining tests — Rules (Project‑specific)

These rules apply to all tests under `services/nginx/app/tests` for the Copenhagen Truck Wash API. They codify how to create, run, and maintain the lightweight CLI PHP tests used in this repository.

1. Scope and philosophy
    - Prefer fast, deterministic CLI scripts over framework‑based tests.
    - Keep unit‑style tests isolated from I/O (no DB/Redis/files/network). Use test doubles and explicit `require_once` for only the code exercised.
    - Use integration tests sparingly and only when configuration/globals are required. Run them in Docker with `USE_ENV=true`.

2. Location and naming
    - Place tests in `services/nginx/app/tests/<domain>/YourTest.php` where `<domain>` reflects the feature area (e.g., `subusers`, `orders`, `redis`).
    - File names must end with `Test.php` (e.g., `SelfservePermissionInitTest.php`).
    - Keep one coherent scenario per file. If a file grows beyond ~150 lines or mixes unrelated scenarios, split it.

3. Execution modes
    - Unit‑style (preferred): run on host with PHP CLI.
        - Command: `php services/nginx/app/tests/<domain>/<Name>Test.php`
    - Integration‑style (needs env/globals): run inside Docker (php1) where `USE_ENV=true` and env vars are provided by `docker-compose.yml`.
        - Start minimal stack: `docker compose up -d traefik redis php1 caddy`
        - Command: `docker compose exec -T php1 php /var/www/html/tests/<domain>/<Name>Test.php`

4. Bootstrapping and includes
    - At the top of every test, define `WD` if not already defined:
      ```php
      if (!defined('WD')) { define('WD', dirname(__DIR__, 2)); }
      ```
    - For unit‑style tests: include only the files you exercise via explicit `require_once WD . '/path/to/file.php';`.
    - Do NOT include `config.php` for unit‑style tests; it throws if `USE_ENV` is not set.
    - For integration‑style tests that need globals (`$CONFIG_DB`, `$REDIS_CONFIG`, etc.): `require_once WD . '/config.php';` and run the test inside `php1`.

5. Output and exit codes
    - Use simple helpers for human‑readable output:
      ```php
      function ok($message){ echo "\n\033[32m✔ $message\033[0m\n"; }
      function fail($message){ echo "\n\033[31m✖ $message\033[0m\n"; }
      ```
    - Prefer collecting failures and exiting non‑zero when any assertion fails:
      ```php
      $failed = 0;
      // ... on failure set $failed = 1;
      exit($failed);
      ```
    - End with a concise completion line, e.g., `echo "\n<Name>Test completed.\n";`.

6. Determinism, time, and randomness
    - Avoid `sleep()` and real time dependencies. If time is relevant, pass it as a parameter or stub the time source.
    - Do not use random values unless strictly necessary; if used, seed explicitly (`mt_srand(1234)`) and document it.

7. External I/O and side effects
    - Never call external services (Stripe, e‑conomic, MinIO, Slack, WordPress) from tests. Stub or override code paths as shown in `tests/subusers/SelfservePermissionInitTest.php`.
    - Do not modify files under the repository during tests. If temporary files are required, use `sys_get_temp_dir()` and delete them before exit.

8. Redis and database (integration‑only)
    - Run inside Docker (`php1`) so `config.php` can populate `$REDIS_CONFIG` and `$CONFIG_DB`.
    - Use unique, namespaced Redis keys for test data (e.g., `test:<feature>:<uuid>`). Clean them up at the end. Do NOT call `FLUSHALL`.
    - Prefer fakes/stubs over real DB writes. If DB writes are unavoidable, scope them to clearly identifiable rows and delete them before exit.

9. Performance budgets
    - Each unit‑style test script should complete in < 100 ms on a typical dev machine.
    - Each integration‑style test should complete in < 2 s and must avoid N+1 loops or heavy queries.

10. When to add or update tests
- New route/feature: add at least one unit‑style test for core logic. If behavior depends on configuration/globals, add a minimal integration test.
- Bug fix: first reproduce with a failing test; then fix and ensure the test passes.
- Refactor: keep behavior‑preserving tests green. If public behavior changes intentionally, update both tests and `openapi.yaml` accordingly.

11. Maintenance expectations
- Do not weaken or remove assertions to “make tests pass”. Investigate and fix root causes.
- Keep tests small and readable. Extract tiny helpers within the test file rather than introducing new shared libraries.
- Match the project code style and line endings (see `.editorconfig`: UTF‑8, CRLF, 4‑space indent, class brace `next_line`).

12. Templates
- Unit‑style template:
  ```php
  <?php
  if (!defined('WD')) { define('WD', dirname(__DIR__, 2)); }
  // require_once WD . '/modules/foo/classes/bar.php';

  function ok($m){ echo "\n\033[32m✔ $m\033[0m\n"; }
  function fail($m){ echo "\n\033[31m✖ $m\033[0m\n"; }
  $failed = 0;

  // Arrange / Act
  $sum = 2 + 2;

  // Assert
  if ($sum === 4) { ok('Basic arithmetic works (2 + 2 = 4)'); } else { fail('Expected 4'); $failed = 1; }

  echo "\nExampleUnitTest completed.\n";
  exit($failed);
  ```

- Integration‑style template (Redis example):
  ```php
  <?php
  if (!defined('WD')) { define('WD', dirname(__DIR__, 2)); }
  require_once WD . '/config.php'; // populates $REDIS_CONFIG when USE_ENV=true
  require_once WD . '/classes/redis.php';

  function ok($m){ echo "\n\033[32m✔ $m\033[0m\n"; }
  function fail($m){ echo "\n\033[31m✖ $m\033[0m\n"; }
  $failed = 0;

  // Use a namespaced test key
  $key = 'test:redis:example:' . uniqid('', true);

  try {
      $r = new classes\redis();
      $r->set($key, 'pong');
      $val = $r->get($key);
      if ($val === 'pong') { ok('Redis set/get works for namespaced key'); } else { fail('Unexpected value from Redis'); $failed = 1; }
  } finally {
      // Best‑effort cleanup
      try { $r->del($key); } catch (\Throwable $e) { /* ignore */ }
  }

  echo "\nExampleRedisIntegrationTest completed.\n";
  exit($failed);
  ```

13. Quick reference
- Run unit‑style test on host: `php services/nginx/app/tests/<domain>/<Name>Test.php`
- Run integration‑style test in Docker: `docker compose exec -T php1 php /var/www/html/tests/<domain>/<Name>Test.php`
- Minimal stack for integration: `docker compose up -d traefik redis php1 caddy`

For broader build, configuration, and existing examples, see `.junie/guidelines.md` (Testing section).

