mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
181 lines
7.9 KiB
Markdown
181 lines
7.9 KiB
Markdown
# XAMXAM — Test Coverage Plan
|
||
|
||
## Prerequisites (do these first, in order)
|
||
|
||
1. **Add Composer** — create `composer.json` if not present, ensure `vendor/` is gitignored except for committed tools
|
||
2. **Install PHPUnit** — `composer require --dev phpunit/phpunit ^11`
|
||
3. **Create `phpunit.xml`** at project root:
|
||
```xml
|
||
<?xml version="1.0"?>
|
||
<phpunit bootstrap="tests/bootstrap.php" colors="true">
|
||
<testsuites>
|
||
<testsuite name="XAMXAM">
|
||
<directory>tests/phpunit</directory>
|
||
</testsuite>
|
||
</testsuites>
|
||
</phpunit>
|
||
```
|
||
4. **Create `tests/bootstrap.php`** — autoload classes under test, define any constants the app needs (DB credentials from env, app root path, etc.)
|
||
5. **Create `tests/phpunit/`** directory — all new tests go here. The existing `run-tests.php` and its 8 test files are left untouched for now.
|
||
|
||
---
|
||
|
||
## Phase 1 — Pure Logic (no DB, no filesystem, no network)
|
||
|
||
**Goal:** Cover all stateless classes and helper methods. These have zero external dependencies and should take 2–3 hours total. Every test here runs in milliseconds and needs no fixtures.
|
||
|
||
### 1.1 `CryptoTest.php` — HIGH PRIORITY
|
||
|
||
Cover:
|
||
- Encrypt → decrypt round-trip returns original plaintext
|
||
- Different plaintexts produce different ciphertexts (IV randomness)
|
||
- `isEncrypted()` correctly identifies encrypted vs plain strings
|
||
- Legacy fallback path decrypts values encrypted with the old scheme
|
||
- Empty string handling (encrypt/decrypt empty string without error)
|
||
- Invalid base64 input to decrypt throws or returns false gracefully
|
||
- Wrong key produces failure, not silent garbage
|
||
|
||
### 1.2 `EmailObfuscatorTest.php` — MEDIUM PRIORITY
|
||
|
||
Cover:
|
||
- `encode()` produces output with no literal `@` character
|
||
- `email()` renders a working obfuscated mailto link
|
||
- `mailto()` builds correct href structure
|
||
- `emailText()` replaces inline email addresses in a block of text
|
||
- `mailtoInText()` wraps addresses in mailto links
|
||
- `obfuscateHtml()` reconstructs anchor tags correctly
|
||
- Edge cases: empty string, string with no emails, already-obfuscated content, multiple emails in one string
|
||
|
||
### 1.3 `SystemControllerHelpersTest.php` — HIGH VALUE, LOW EFFORT (~15 min)
|
||
|
||
Cover the static pure functions (no HTTP context needed):
|
||
- `humanBytes()` — 0, 1023, 1024, 1MB, 1GB boundaries
|
||
- `diskColor()` — thresholds (below warning, warning, critical)
|
||
- `logLineClass()` — maps log level strings to CSS class names
|
||
- `nginxLineClass()` — maps nginx status codes to classes
|
||
- `statusLabel()` / `statusClass()` — all defined statuses
|
||
|
||
### 1.4 `StudentEmailTest.php` — LOW EFFORT
|
||
|
||
Cover `buildHtml()`:
|
||
- Returns a non-empty string containing key thesis fields (title, author)
|
||
- HTML-escapes special characters in thesis data
|
||
- Handles a thesis row with missing/null optional fields without error
|
||
|
||
### 1.5 `TfeControllerOgTest.php`
|
||
|
||
Cover `buildOgTags()`:
|
||
- Returns array with all required OG keys (`og:title`, `og:description`, `og:image`, etc.)
|
||
- Falls back correctly when image is absent
|
||
- Long descriptions are truncated to meta limits
|
||
|
||
---
|
||
|
||
## Phase 2 — Integration (requires test database)
|
||
|
||
**Goal:** Cover DB-layer methods not yet touched. Requires a dedicated test database seeded with fixtures, wiped between test runs.
|
||
|
||
### Setup required before Phase 2
|
||
|
||
- Create a `tests/fixtures/` directory with SQL seed files (one per test class or shared base)
|
||
- Add a `TestDatabase` helper class that boots a PDO connection to the test DB, runs migrations, seeds, and truncates on teardown
|
||
- Store test DB credentials in `.env.test` (never committed), read in `bootstrap.php`
|
||
|
||
### 2.1 `DatabaseExtendedTest.php`
|
||
|
||
High-value targets (cover these first):
|
||
- `escapeLikeString()` — percent signs, underscores, backslashes in input
|
||
- `buildSearchConditions()` — various combinations of filters produce correct WHERE clauses (check SQL structure, not just that it runs)
|
||
- `findDuplicateThesis()` — detects exact duplicate, misses near-duplicate, handles empty table
|
||
- `generateThesisIdentifier()` — format is correct, increments correctly, no collision on concurrent inserts
|
||
- `getCoverPathsForTheses()` — returns correct paths for known IDs, returns empty for unknown IDs
|
||
- `findOrCreateAuthor()` — idempotent (calling twice with same name returns same ID)
|
||
- `deduplicateLanguages()` / `renameLanguage()` / `mergeLanguage()` — data integrity after merge
|
||
- `renameTag()` / `mergeTag()` — same pattern
|
||
|
||
### 2.2 `ShareLinkExtendedTest.php`
|
||
|
||
Extend existing ShareLinkTest with:
|
||
- `listActive()` — only returns active links
|
||
- `listArchived()` — only returns archived
|
||
- `findBySlug()` — hit and miss cases
|
||
- `setPassword()` + `getDecryptedPassword()` round-trip
|
||
- `update()` — fields change, others don't
|
||
|
||
### 2.3 `RateLimitExtendedTest.php`
|
||
|
||
Extend existing RateLimitTest with:
|
||
- `checkKey()` — counts per key, not globally
|
||
- `getRemaining()` — decrements correctly
|
||
- `getClientIdentifier()` — produces consistent output for same input, ignores X-Forwarded-For
|
||
|
||
---
|
||
|
||
## Phase 3 — Validation & Controller Logic
|
||
|
||
**Goal:** Cover the validation and sanitisation logic that lives inside controllers, tested by driving through the public interface rather than calling private methods directly.
|
||
|
||
### 3.1 `ThesisCreateValidationTest.php`
|
||
|
||
Drive `ThesisCreateController` through its `handle()` method with a mock HTTP POST:
|
||
- Valid submission creates a record
|
||
- Missing required fields (title, author, year) returns error, nothing written to DB
|
||
- Invalid year format (letters, future year beyond threshold, year 0) rejected
|
||
- Malformed URL in website field rejected
|
||
- Tag list with duplicates deduplicated before save
|
||
- XSS payload in title stored escaped, never executed
|
||
|
||
### 3.2 `ThesisEditValidationTest.php`
|
||
|
||
Same pattern for `ThesisEditController`:
|
||
- `load()` returns correct data for known ID, 404 for unknown
|
||
- `collectJuryMembers()` handles empty list, single member, duplicates
|
||
- `handleWebsiteUrl()` normalises http/https, rejects non-URLs
|
||
|
||
### 3.3 `autofocusFieldForErrorTest.php`
|
||
|
||
`ThesisEditController` has its own copy of this helper with different field names from `CreateController`. Verify:
|
||
- Returns correct field name for each known error key
|
||
- Returns null/default for unknown error key
|
||
- Does not leak `CreateController` field names
|
||
|
||
---
|
||
|
||
## Phase 4 — Cleanup (no new tests, housekeeping)
|
||
|
||
**Goal:** Consolidate the two test systems.
|
||
|
||
- Migrate the 8 existing custom-runner tests to PHPUnit equivalents in `tests/phpunit/`
|
||
- Validate they all pass under `vendor/bin/phpunit`
|
||
- Remove `run-tests.php` and the old test files
|
||
- Add `vendor/bin/phpunit` to CI pipeline (or a `Makefile` target: `make test`)
|
||
- Generate a baseline coverage report: `vendor/bin/phpunit --coverage-html coverage/`
|
||
- Commit the `coverage/` baseline so regressions are visible in future reports
|
||
|
||
---
|
||
|
||
## What is explicitly out of scope
|
||
|
||
These classes are noted as hard to test and are **not part of this plan**. Do not attempt to test them without first adding dependency injection or a proper HTTP testing layer:
|
||
|
||
- `App` — session/header-heavy
|
||
- `Dispatcher` — requires full HTTP context
|
||
- `FilepondHandler` — requires `$_FILES` injection
|
||
- `SmtpRelay` — socket-level, needs mock SMTP server
|
||
- `PeerTubeService` — OAuth + HTTP, needs VCR-style mocking
|
||
- `Parsedown` — third-party, tested upstream
|
||
|
||
---
|
||
|
||
## Summary
|
||
|
||
| Phase | Scope | Effort | Requires |
|
||
|---|---|---|---|
|
||
| 0 — Prerequisites | PHPUnit setup | ~1h | Composer |
|
||
| 1 — Pure logic | Crypto, Obfuscator, SystemController helpers, StudentEmail, OG tags | ~2–3h | Nothing |
|
||
| 2 — Integration | DB extended, ShareLink extended, RateLimit extended | ~3–4h | Test DB |
|
||
| 3 — Controller validation | Create/Edit validation paths | ~2–3h | Test DB + HTTP mock |
|
||
| 4 — Cleanup | Migrate old tests, CI, coverage report | ~2h | Phase 1–3 done |
|
||
|
||
**Start with Phase 0 + Phase 1.** They are fully unblocked and deliver the highest security-relevant coverage (Crypto, EmailObfuscator) with the least setup friction.
|