Skip to content

PavlokPager — Testing

Mirrors the WorkWingman testing mandate: mocked unit coverage (xUnit + Bogus) and Stryker mutation testing, both enforced in CI.

Run it

dotnet test                         # from repo root (uses PavlokPager.slnx)
dotnet tool restore                 # once, restores the Stryker local tool
cd tests/PavlokPager.Tests
dotnet stryker                      # mutation testing; fails under the break threshold

Suite shape

59 tests, all isolated: each derives from IsolatedTest, which points PAVLOK_HOME at a throwaway temp directory, clears the API-key env, injects a fake HttpClient, zeroes the retry and ladder delays, and captures stdin/stdout — so nothing touches the real profile, network, or band, and nothing sleeps.

Area File Covers
Arg parsing ArgParsingTests value/reason/dry-run parsing, strict unknown-arg rejection
Budget BudgetTests 8-hour page-start window, mode filtering, boundaries
Config + crypto ConfigAndCryptoTests defaults, roundtrip, DPAPI/plain encrypt-decrypt, key resolution, exact default ladders
Log LogTests well-formed reads, blank skipping, corrupt-line tolerance
Send SendStimulusTests dry-run, success, Bearer header, value clamp, 401 fast-fail, 5xx/exception retry, retry ceiling
End-to-end IntegrationTests every Main() command: dispatch, page ladder, budget refusal, login/setup via piped stdin, log
Mutation-hardening DefensiveTests zap-only clamping, all-steps-fail, log -n, debug-gate redaction, login validation guard

Fake data is generated with Bogus (Faker). HTTP is mocked with QueueHandler, a replayable HttpMessageHandler that records URIs, bodies, and auth headers and can simulate transport failures.

Mutation testing

Stryker mutates the production code (flip <, blank a string, negate a condition) and checks the tests notice. Config: tests/PavlokPager.Tests/stryker-config.json.

Current score: 63.06% (194 killed / 76 survived / 4 timeout / 40 no-coverage) — above the configured break threshold of 60%.

The score climbed 25.8% → 55.7% → 63.06% as coverage moved from the pure seams, to end-to-end Main() tests, to targeted branch tests. The remaining survivors are almost entirely cosmetic console-message string mutations, plus the interactive Console.ReadKey password path, which can't be exercised without a real keyboard. Thresholds: break 60, low 70, high 85.

4-layer matrix

Layer Status Notes
PowerShell (Pester) N/A No PowerShell component in this repo — pure C# CLI.
C# (xUnit + Bogus + Stryker) This suite. 59 xUnit tests, Bogus fakes, real Stryker.NET mutation testing (score below).
Python (pytest + mutmut) N/A No Python component.
Churn CI churn job runs the suite 5x and fails on any pass/fail-count disagreement (flake detection).
Automation / E2E ✓ (hermetic mock) IntegrationTests.cs drives every Main() command end-to-end against a fake HttpMessageHandler (QueueHandler) — no real network or band contact. The one path that can't be hermetically exercised is the interactive Console.ReadKey password prompt (needs a real keyboard); it's excluded and documented, not silently skipped.

CI

.github/workflows/tests.yml runs dotnet test on every push/PR, then Stryker (which fails the job below the break threshold) and uploads the HTML mutation report as an artifact. .github/workflows/security.yml runs Semgrep. Both add a churn job that repeats the suite 5x and fails on any disagreement between runs.