Add PHPUnit test suite — 222 tests, 100% code coverage #1

Merged
andrew merged 5 commits from kat-tests into main 2026-08-07 09:13:16 -04:00
Member

Summary

Adds a comprehensive PHPUnit test suite to the preauth project, covering all 21 source classes with 222 tests and 469 assertions.

Test Results

Metric Value
Tests 222
Assertions 469
Passing 222 (100%)
Failing 0
Lines Coverage 100.00% (442/442)
Methods Coverage 100.00% (83/83)
Classes Coverage 100.00% (21/21)

What's Included

Test Infrastructure

  • phpunit.dist.xml — PHPUnit configuration with coverage settings
  • tests/TestKernel.php — Test-specific kernel
  • tests/bootstrap.php — Autoloader bootstrap
  • tests/Support/TotpTestHelper.php — Reusable TOTP fixtures (frozen clock, deterministic codes)
  • tests/Support/ListenerTestHelper.php — Reusable listener test fixtures (Twig env, mock rate limiters)

Unit Tests (per class)

Every source class has a corresponding unit test file:

  • All 5 listeners (Accept, Allow, Reject, Login, Intercept)
  • All 3 services (LoginManager, DomainManager, BackupCodeManager)
  • All 5 traits (CookieName, GetTotp, HasLogger, MakeNonce, String)
  • Supporting classes (ConfigBag, Payload, MonitorCacheKeys, PersistCache, Utilities, Clock, Scope, GenerateBackupCodesCommand)

Functional Tests

  • AuthenticationFlowTest.php — End-to-end tests through the real Symfony kernel:
    • Full login flow (fetch nonce → submit TOTP → verify cookie → authenticated access)
    • Failed login attempts and rate limiting
    • Central auth subdomain redirect flow
    • Backup code authentication
    • Invalid/expired cookie handling
    • Open redirect prevention

Edge Cases Covered

  • ULID collision handling
  • Nonce collision retries (up to 3 attempts)
  • Invalid payloads (bad base64, non-object JSON, arrays, null, booleans)
  • Empty/whitespace fields, field truncation at 128 chars
  • Multibyte characters in cache keys
  • Multi-part TLD domain matching (.co.uk, .com.au, etc.)
  • Backup code single-use enforcement, case-insensitivity, character stripping
  • Cookie security attributes (__Host- prefix, SameSite=Strict, Secure, HttpOnly)
  • Return URL validation (prevents open redirect)

Commits

  1. 890cc22 — Add PHPUnit test suite and supporting infrastructure
  2. 4d314bc — Add unit tests for all listeners, the backup-codes command, and test support helpers
  3. 6c5a7c9 — Fix failing functional tests and add test infrastructure
  4. 12ba6cd — Add unit tests for untested edge cases and code paths

How to Run

# Run tests
php vendor/bin/phpunit

# Run with coverage (requires Xdebug)
XDEBUG_MODE=coverage php vendor/bin/phpunit --coverage-text
## Summary Adds a comprehensive PHPUnit test suite to the preauth project, covering all 21 source classes with 222 tests and 469 assertions. ## Test Results | Metric | Value | |--------|-------| | Tests | 222 | | Assertions | 469 | | Passing | 222 (100%) | | Failing | 0 | | **Lines Coverage** | **100.00%** (442/442) | | **Methods Coverage** | **100.00%** (83/83) | | **Classes Coverage** | **100.00%** (21/21) | ## What's Included ### Test Infrastructure - `phpunit.dist.xml` — PHPUnit configuration with coverage settings - `tests/TestKernel.php` — Test-specific kernel - `tests/bootstrap.php` — Autoloader bootstrap - `tests/Support/TotpTestHelper.php` — Reusable TOTP fixtures (frozen clock, deterministic codes) - `tests/Support/ListenerTestHelper.php` — Reusable listener test fixtures (Twig env, mock rate limiters) ### Unit Tests (per class) Every source class has a corresponding unit test file: - All 5 listeners (Accept, Allow, Reject, Login, Intercept) - All 3 services (LoginManager, DomainManager, BackupCodeManager) - All 5 traits (CookieName, GetTotp, HasLogger, MakeNonce, String) - Supporting classes (ConfigBag, Payload, MonitorCacheKeys, PersistCache, Utilities, Clock, Scope, GenerateBackupCodesCommand) ### Functional Tests - `AuthenticationFlowTest.php` — End-to-end tests through the real Symfony kernel: - Full login flow (fetch nonce → submit TOTP → verify cookie → authenticated access) - Failed login attempts and rate limiting - Central auth subdomain redirect flow - Backup code authentication - Invalid/expired cookie handling - Open redirect prevention ### Edge Cases Covered - ULID collision handling - Nonce collision retries (up to 3 attempts) - Invalid payloads (bad base64, non-object JSON, arrays, null, booleans) - Empty/whitespace fields, field truncation at 128 chars - Multibyte characters in cache keys - Multi-part TLD domain matching (.co.uk, .com.au, etc.) - Backup code single-use enforcement, case-insensitivity, character stripping - Cookie security attributes (__Host- prefix, SameSite=Strict, Secure, HttpOnly) - Return URL validation (prevents open redirect) ## Commits 1. `890cc22` — Add PHPUnit test suite and supporting infrastructure 2. `4d314bc` — Add unit tests for all listeners, the backup-codes command, and test support helpers 3. `6c5a7c9` — Fix failing functional tests and add test infrastructure 4. `12ba6cd` — Add unit tests for untested edge cases and code paths ## How to Run ```bash # Run tests php vendor/bin/phpunit # Run with coverage (requires Xdebug) XDEBUG_MODE=coverage php vendor/bin/phpunit --coverage-text ```
lyra added 4 commits 2026-08-07 09:04:41 -04:00
- Add phpunit/phpunit ^13.2, symfony/browser-kit and symfony/css-selector
  to require-dev, plus the autoload-dev mapping for App\Tests- Add phpunit.dist.xml (strict deprecation/notice/warning failures,
  APP_ENV=test forced) and .env.test / bin/phpunit / tests/bootstrap.php
  from the PHPUnit recipe
- Add tests/Support/TotpTestHelper providing a deterministic TOTP
  fixture, frozen clock and ConfigBag/cache-pool helpers
- Add 121 unit tests covering Clock, ConfigBag, Data/Payload, Enum/Scope,
  MonitorCacheKeys, PersistCache, Utilities, all five Traits and the
  three Service managers (BackupCode, Domain, Login)
- Fix LoginManagerTest nonce lookups to use makeCacheKey() so the cache
  key matches the one the manager actually reads/writes
- Gitignore bin/.phpunit.result.cache
- Add ListenerTestHelper providing a real Twig Environment pointed at the
  project templates and configurable RateLimiterFactoryInterface doubles
  (fixed-remaining and counting variants)
- Add AcceptListenerTest: valid cookie session (host & auth-subdomain cookie
  names), missing/empty cookie, no session
- Add AllowListenerTest: valid IP session, missing session, IP access disabled
- Add RejectListenerTest: blocked -> teapot / too-many-requests, unblocked,
  error template content
- Add InterceptListenerTest: central-auth redirect, login page rendering,
  post form on auth subdomain, invalid-cookie pruning (host & auth cookies)
- Add LoginListenerTest: no-login early return, successful login via header
  and via POST to auth subdomain, failed login (json/html), rate-limited
  (teapot/too-many), invalid payload handling
- Add GenerateBackupCodesCommandTest: default/specific count, default argument
  is 10, boot+persist invocation, zero codes, name & description configuration
- Fix MakeNonceTraitTest nonce cache lookups to use makeCacheKey() (was flaky
  when random nonce contained '-')
- Add TestKernel that removes the kernel.reset tag from nonceCache,
  rateLimitCache, sessionCache and sessionStorage pools so in-memory
  state survives across requests within a single test (mirroring APCu
  persistence in production)
- Add config/packages/test/ with array cache adapters and test session
  config
- Set fixed TOTP secret (JBSWY3DPEHPK3PXP) and high rate limits in
  phpunit.dist.xml and .env.test so functional tests can compute valid
  codes and are not rate-limited
- Make Kernel non-final so TestKernel can extend it
- Fix testFailedLoginWithSpentNonceIsRejected and
  testConsumedBackupCodeCannotBeReused: clear the CookieJar between
  sub-requests so a session cookie set by a prior successful login does
  not auto-authenticate the next request via AcceptListener before the
  nonce/backup-code path is exercised
Add unit tests for untested edge cases and code paths
Sync GitHub / sync (push) Successful in 9s
12ba6cde7b
PayloadTest:
- decode() with invalid JSON, JSON array, null, boolean, number
- load() with all fields present but empty strings
- create() with invalid scope string (falls back to cookie)
- create() with missing json property (defaults to true)

DomainManagerTest:
- TLD table coverage: com.au, co.jp, com.br, co.nz, com.mx, co.in, br.com
- Single-label host matching behavior
- Empty string host
- validReturn with URL containing port, without path, different domain with port

MakeNonceTraitTest:
- Retry-then-succeed path (first attempt collides, second succeeds)
- Zero retries throws immediately on collision

StringTraitTest:
- Only invalid characters collapses to single underscore
- Exactly 128 characters boundary
- Multibyte characters (é, ö) replaced with underscore
- Emoji replaced with underscore

GetTotpTraitTest:
- HOTP URI triggers HttpException(500) via instanceof check

LoginManagerTest:
- ULID collision throws HttpException(500)
- Central auth cookie sets domain on matching host (uses auth cookie name)
- Central auth cookie uses null domain on non-matching host
- Empty return parameter falls back to request path

BackupCodeManagerTest:
- Empty string code returns false
- Code with value false (consumed) verified via cache state
- Generated codes are unique (50 codes)
- Code length equals TOTP digits + 2

MonitorCacheKeysTest:
- saveDeferred on __key_list and __chg_list throws OutOfBoundsException
- getKeys returns empty when key list is lost
- deleteItem/deleteItems return true assertions

PersistCacheTest:
- Mixed updates and removals in same persist cycle
- Multiple boot-modify-persist cycles with interleaved modifications
lyra added 1 commit 2026-08-07 09:06:35 -04:00
Add Gitea workflow to run test suite on push and PR
Tests / test (pull_request) Successful in 1m58s
Sync GitHub / sync (push) Successful in 7s
de2a382cbf
andrew merged commit d6bcbf661e into main 2026-08-07 09:13:16 -04:00
andrew deleted branch kat-tests 2026-08-07 09:13:16 -04:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: public/preauth#1