> IMPORTANT: This document is the OPUS implementation, it must be updated to match the final codex + opus version

# Integration Testing Guide

This document describes how HTTP integration tests work, how to write them, and the underlying mechanisms.

## Overview

Integration tests spin up a real PHP web server and make actual HTTP requests. This tests:
- Full routing (index.php → controller → view)
- Session handling (login/logout)
- Form submissions and redirects
- Access control (auth guards, admin checks)
- Template rendering
- No PHP errors in output

## Architecture

### Test Base Class

`IntegrationTestCase` (in `tests/auto/integration/IntegrationTestCase.php`) provides:

1. **Server Lifecycle** - Starts PHP built-in server once per test class
2. **HTTP Client** - Uses cURL to make requests
3. **Session Isolation** - Each test gets its own cookie jar
4. **Assertion Helpers** - Simplified assertions for common cases

### Server Management

The server starts in `setUpBeforeClass()`:

```php
// Start PHP built-in server
$cmd = sprintf('php -S %s:%d -t %s', $host, $port, $docroot);
$process = proc_open($cmd, $descriptors, $pipes);
```

The server runs on `127.0.0.1:8989` serving `src/` as the document root. It shuts down in `tearDownAfterClass()`.

If port 8989 is already in use (from a previous run), the tests reuse the existing server.

### HTTP Requests

Each request goes through cURL:

```php
$ch = curl_init();
curl_setopt_array($ch, [
  CURLOPT_URL => $url,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HEADER => true,
  CURLOPT_COOKIEFILE => $this->cookieJar,  // Read cookies
  CURLOPT_COOKIEJAR => $this->cookieJar,  // Write cookies
  // ...
]);
```

The response is parsed into:
- `status` - HTTP status code (int)
- `headers` - Array of header lines
- `body` - Response body string
- `redirect` - Location header if present (string or null)

### Session Isolation

Each test gets a **fresh cookie jar**:

```php
protected function setUp(): void {
  $this->cookieJar = tempnam(sys_get_temp_dir(), 'phptest_cookies_');
}
```

This ensures tests don't leak session state between each other. The cookie jar is a file that cURL reads/writes for each request, simulating a browser's cookie storage.

### Login Flow

`loginAs()` posts to the login form and follows the redirect:

```php
public function loginAs(string $username, string $password): array {
  return $this->httpPost('/?page=login', [
    'login' => '1',
    'Username' => $username,
    'Password' => $password,
  ]);
}
```

The cURL client doesn't follow redirects (`CURLOPT_FOLLOWLOCATION = false`), so the test can inspect the `Location` header. After login, subsequent requests to the same cookie jar carry the session.

## Writing Tests

### Basic Smoke Test

```php
final class myPageTest extends IntegrationTestCase {
  public function testPageLoads(): void {
    $r = $this->httpGet('/?page=mypage');
    $this->assertStatus($r, 200);
    $this->assertBodyContains($r, 'Expected Content');
  }
}
```

### Auth Guard Test

```php
public function testRequiresLogin(): void {
  $r = $this->httpGet('/?page=account-panel');
  // index.php changes $page to 'login' when not authenticated
  $this->assertBodyContains($r, 'Username');
  $this->assertBodyContains($r, 'Password');
}
```

### Authenticated Access Test

```php
public function testLoggedInSeesDashboard(): void {
  $this->loginAs('testuser', 'testpass');
  $r = $this->httpGet('/?page=account-panel');
  $this->assertStatus($r, 200);
  $this->assertBodyContains($r, 'testuser');
}
```

### Form Submission Test

```php
public function testWrongPassword(): void {
  $r = $this->loginAsAjax('testuser', 'wrongpassword');
  $this->assertStatus($r, 200);
  $this->assertNotNull($r['json']);
  $this->assertEquals('error', $r['json']['status']);
}
```

### Data Provider for Multiple Pages

```php
/**
 * @dataProvider pageProvider
 */
public function testNoErrors(string $page): void {
  $r = $this->httpGet('/?page=' . $page);
  $this->assertBodyNotContains($r, 'Fatal error');
}

public function pageProvider(): array {
  return [
    'login' => ['login'],
    'register' => ['register'],
    'ranking' => ['ranking'],
  ];
}
```

## Test Data Management

### Creating Test Accounts

Tests can create accounts directly via models in `setUpBeforeClass()`:

```php
public static function setUpBeforeClass(): void {
  AccountModel::createAccount('testuser', 'testpass', 'test@test.com', 'english');
}
```

This bypasses the HTTP layer for faster, more reliable setup.

### Cleaning Up

Use `tearDownAfterClass()` to clean up test data:

```php
public static function tearDownAfterClass(): void {
  AccountModel::updatePassword('testuser', 'deleted');
  // Or delete if your model supports it
}
```

## Requirements

- **php-curl** extension must be installed
- **PHP built-in server** must be available (`php` command)
- **Port 8989** must be available (or tests will reuse existing server)
- **Database** must have test accounts (see bootstrap in `config.php`)

## Debugging Failed Tests

### Server Not Starting

If tests fail with connection errors:
1. Check port 8989 isn't in use: `lsof -i :8989`
2. Check PHP is in PATH: `which php`
3. Check src/ directory exists and is readable

### Session Issues

If login tests fail:
1. Verify the login form fields match (`Username`, `Password`, `login`)
2. Check the redirect is being detected correctly
3. Try `loginAsAjax()` instead to get JSON response

### Debugging Responses

Add debugging to tests:

```php
public function testDebug void {
  $():r = $this->httpGet('/?page=mypage');
  echo "\n=== STATUS ===\n" . $r['status'];
  echo "\n=== REDIRECT ===\n" . ($r['redirect'] ?? 'none');
  echo "\n=== BODY PREVIEW ===\n" . substr($r['body'], 0, 500);
  $this->assertTrue(true); // Always passes
}
```

## Running Tests

```bash
# All integration tests
composer test-integration

# Single test file
phpunit --testdox --bootstrap ./tests/auto/integration/config.php tests/auto/integration/publicPagesTest.php --debug

# Single test method
phpunit --testdox --bootstrap ./tests/auto/integration/config.php --filter testLoginAs integration/authFlowTest.php --debug
```
