test: raise API coverage to 99.88% of lines and 100% of branches - #138
Merged
Conversation
Adds coverage tooling and the tests to close the gaps it found. Baseline was 86.51% of lines and 64.69% of branches across 97 tests; it is now 99.88% and 100% across 289. Tooling - coverlet.collector on both test projects, configured by coverlet.runsettings at the repository root: Cobertura and coverlet JSON, only the four TodoApp.* assemblies, auto-properties skipped. - JSON as well as Cobertura because the two suites have to be merged. Cobertura records only a per-line branch percentage, so a branch covered by the unit suite and one covered by the integration suite both read "1 of 2" and a naive merge under-reports. The JSON keeps each branch as its own record with a hit count, so the merge is exact. Tests - Auth: the rejection paths (unknown, expired and replayed refresh tokens, deactivated accounts, logout against a token the caller does not own), the already-linked and orphaned Google sign-in branches, and the concurrent-insert race that has to surface as 409 rather than 500. - Domain: every entity guard, including that Revoke keeps the first reason and that Activate does not rotate the security stamp. - Todos and categories: ownership isolation, filters, search, ordering, and the concurrency conflict where the row is deleted between the read and the write. - Infrastructure: the Pwned Passwords range lookup against a stub handler (k-anonymity, match, threshold, malformed lines, and every fail-open path), the current-user claims reader, the hasher's malformed-hash paths, and the provider selection in the composition root. - WebApi: each arm of the exception-to-problem mapping, the refresh cookie and its CSRF check, the JWT signing-key guard, and the app hosted as Production and with no appsettings at all. Production changes, both to make untested behaviour testable - GoogleTokenValidator: the payload mapping is split into FromPayload so it can be tested without Google's live signing keys. No behaviour change. - Program.cs: the startup database initialisation and its background retry loop move to DatabaseStartup, which returns the retry task instead of discarding it internally. Program.cs still discards it, so startup does not wait; the retry delay and attempt count are now explicit at the call site. This is 31 lines of recovery logic that previously had no test at all. Two lines remain uncovered, both needing a live cloud service: the mapping after a successful Google token validation, and the closing brace of the Key Vault block, which is only reached when AddAzureKeyVault succeeds against a real vault. Both are documented in docs/development/testing.md §3.4. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019h7znwAftvs36vD4YwKMRf
Two failures the CI runner exposed and this machine could not. The breach check is on in appsettings.json and off only in appsettings.Development.json. The rest of the suite runs as Development and so inherited "off"; the new Production and bare-configuration hosts did not, and started calling the real Pwned Passwords service. "Password1" is in the corpus, so registration returned 400 on a runner with connectivity and passed on this sandbox, where the request fails open. Turning it off in the base factory makes the guarantee explicit for every environment a factory might host, and a new test asserts it rather than leaving it to be rediscovered. The bare-configuration factory also deleted its temporary content root in Dispose, which runs on both the sync and async teardown paths — the second delete threw out of test-class cleanup. It now checks the directory is still there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019h7znwAftvs36vD4YwKMRf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Raises API coverage from 86.51% of lines / 64.69% of branches to 99.88% / 100%, and the suite from 97 tests to 289. All green.
Tooling
coverlet.collectoron both test projects, configured bycoverlet.runsettingsat the repository root: only the fourTodoApp.*assemblies, auto-properties skipped.docs/development/testing.md§3.4.Tests
Revokekeeps the first reason and thatActivatedoes not rotate the security stamp.Source changes
Two, both to make untested behaviour testable. No behaviour change either time.
GoogleTokenValidator— the payload mapping is split intoFromPayloadso it can be tested without Google's live signing keys.Program.cs— the startup database initialisation and its background retry loop move to a newDatabaseStartup, which returns the retry task rather than discarding it internally.Program.csstill discards it, so startup does not wait; the retry delay and attempt count are now explicit at the call site. That is 31 lines of recovery logic that previously had no test at all.What is not covered
Two lines, both needing a live cloud service:
GoogleTokenValidator.ValidateAsync— the mapping after a successful validationFromPayload; the configuration guard and the malformed-token path are tested too.Program.cs— the closing brace of the Key Vault blockAddAzureKeyVaultsucceeds against a real vault. The opt-in branch is covered both ways, including that a malformedKeyVault:Uristops startup.Verification
dotnet test TodoApp.slnon .NET 10 — 212 unit + 77 integration, 0 failures, in Debug and Release (this repo builds withTreatWarningsAsErrors).🤖 Generated with Claude Code
https://claude.ai/code/session_019h7znwAftvs36vD4YwKMRf
Generated by Claude Code