Skip to content

PP-4542: 3.2.0 regression test-quality fixes (F-003/F-005/F-006/F-007/F-008)#1052

Open
mauricecarrier7 wants to merge 7 commits into
developfrom
fix/pp4542-regression-test-coverage
Open

PP-4542: 3.2.0 regression test-quality fixes (F-003/F-005/F-006/F-007/F-008)#1052
mauricecarrier7 wants to merge 7 commits into
developfrom
fix/pp4542-regression-test-coverage

Conversation

@mauricecarrier7

Copy link
Copy Markdown
Contributor

Test-quality fixes surfaced by the 3.2.0 pre-release regression (PP-4542) against the release/3.2.0 candidate. Predominantly test additions that kill specific surviving mutants; one behavior-preserving extraction enables the audiobook coverage.

Findings addressed

  • F-003 — BookRegistrySync false-green tests. The PP-4407 awaitReady() migration changed sync() so the no-loansUrl case transitions setState(.syncing) → .loaded instead of being a synchronous no-op. Two baseline tests were band-aided with XCTSkipIf(currentAccount?.loansUrl != nil) — so they skipped on a signed-in CI sim (false green) and failed in a clean env. Replaced the skip-dodge with deterministic fixture-injected tests that assert the real transient-state contract.
  • F-006 — DownloadAuthRetryHandler 33% → 100% kill. Added 401-vs-non-401 discrimination + the no-retry false branch (host-scoped 401 guard: foreign-host short-circuit, same-host positive control, 403 negative).
  • F-007 — AudiobookSessionManager 0% → 88.9% kill. Position-validation / candidate-selection coverage (recency ordering, TOC track-key match, validationFailure guards). Required a behavior-preserving extraction of selectMostRecentValidBookmark(from:in:) + 5 privateinternal seam exposures (no behavior change).
  • F-008 — SAML/browser-auth branch discrimination. Killed the authDef?.isSaml/isBrowserBased == true survivors across BookReturnService / BorrowOperation / TokenRefreshInterceptor with SAML-vs-OAuth discriminating test pairs. Cosmetic/dead/equivalent mutants were deliberately NOT chased (documented).
  • F-005 — palace_mutate skip-rule. Test-environment-detection guard lines (XCTestConfigurationFilePath, etc.) are unkillable by design; now skipped like log lines, so they stop deflating kill rate (TPPReauthenticator was a false 0%).

Verification

  • Consolidated retest on the merged tree: 118 tests, 0 failures, 0 skips (BookRegistrySync now runs without the XCTSkipIf dodge).
  • Diff-only mutation vs origin/release/3.1.0: DownloadAuthRetryHandler 33%→100%, BookReturnService→100%, AudiobookSessionManager 0%→88.9%, TokenRefreshInterceptor/BorrowOperation SAML branches killed.
  • ForgeOS cs_7e7ab728: architect (rev_39f29990) + qa_test (rev_7114cb1a) approved; review/testing/release gates passed.

Notes / follow-ups (not in this PR)

  • F-009 (pre-existing): BookReturnService.swift:235 force-unwraps task! before the Task{} assignment — an IUO-nil race in the return path. Surfaced while writing the return-path tests; needs a separate production fix.
  • QA non-blocking warning: a few async tests use fixed polling loops — prefer condition-await in follow-ups.

Jira: PP-4542

mauricecarrier7 and others added 6 commits June 8, 2026 16:23
…ve XCTSkipIf dodge (F-003 / PP-4542)

The PP-4407 awaitReady() migration moved BookRegistrySync.sync()'s loansUrl
read out of the synchronous guard and into the post-awaitReady() Task. The
old `guard let loansUrl = currentAccount.loansUrl else { return }` was a clean
synchronous no-op; the new code reaches setState(.loaded) + completion(nil,false)
for the "current account present but no loansUrl" case — either synchronously
(no stored credentials) or after awaitReady() resolves to AccountDetails with a
nil loansUrl (anonymous library).

Two tests were band-aided with `try XCTSkipIf(currentAccount?.loansUrl != nil)`,
so they false-greened on a signed-in CI simulator and only ran (and would only
catch the regression) in a clean environment. That is illusory coverage on the
registry-sync critical path (TPPBookRegistry is the single source of truth for
checkouts/holds/reading position).

This replaces the dodge with deterministic, host-independent behavior tests
that inject a fresh-UUID fixture account (so the production-stack credentials
lookup — TPPUserAccount.sharedAccount(libraryUUID:) → production manager's
userAccount(for:) — returns a never-signed-in instance regardless of host
sign-in state):

- test_sync_withCurrentAccountButNoCredentials_resolvesToLoadedSynchronously
  Asserts the NEW synchronous credentials-gate contract: setState exactly
  [.loaded], completion(nil,false), syncUrl stays nil. No keychain needed.
- test_sync_whenAlreadySyncing_returnsBeforeSettingSyncingState
  Drives the re-entrancy guard, which is only REACHABLE once credentials exist
  (it sits after the credentials gate). Writes credentials to the exact
  production-manager instance sync() reads, drives awaitReady to a terminal
  detailsLoaded(nil loansUrl), and asserts currentState == .syncing
  short-circuits before setState. Keychain-gated (environment-capability guard,
  not a host-state dodge).
- test_sync_whenNotSyncing_withCredentialsAndNoLoansUrl_resolvesToLoaded
  Contrast: SAME credentialed/no-loansUrl fixture but currentState == .loaded
  proceeds into the Task and resolves to .loaded — proving the .syncing guard
  (not the credentials) is what suppresses setState.

Each assertion would fail under a real regression of the guard logic. No
production code changed — the new transient setState behavior is not
user-harmful (loaded → loaded for an account that was already loaded), so
option (a) honest-test was correct over option (b) production change.

**Scope:** test-only. PalaceTests/Book/BookRegistrySyncTests.swift.
Verified: -only-testing:PalaceTests/BookRegistrySyncTests → ** TEST SUCCEEDED **,
all 3 new tests pass (not skipped). check-test-name-vs-body.py exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n mutant coverage

PR #1028 added the stale-position-on-reborrow hardening in
AudiobookSessionManager (validationFailure / fallback-to-most-recent
bookmark). The changed lines shipped with a 0% diff-only mutation kill
rate — the position-validation and candidate-selection logic on the
audiobook-playback critical path was unasserted.

New behavior tests at the production seams pin the real decisions with
constructed TOC + registry + account fixtures (no live Audiobook/player
graph, so the toolkit's fragility doesn't leak into the suite):

  - candidate recency ordering: a reverse sort ('<') now returns the
    OLDEST bookmark and fails testSelectMostRecentValidBookmark_*
  - TOC track-key match: in-manifest vs foreign key asserted both ways
  - validationFailure==nil filter inside selectMostRecentValidBookmark:
    a cap-exceeding (valid-key, reconstructs) candidate must be dropped
    and the valid older one selected — kills the filter-inversion mutant
  - isValidPosition valid/invalid both sides
  - isUserAuthenticated auth-doc-load-failure -> not-authenticated

To make the candidate filter+sort testable without a live Audiobook,
extracted the pure selection step into selectMostRecentValidBookmark
(from: [TPPBookLocation], in: AudiobookTableOfContents); the four seam
methods are now `internal` (not `private`) so @testable tests reach
them — mirrors the existing static-policy-mirror pattern in this file.

Mutation (diff-only vs origin/release/3.1.0, full changed-line coverage
across AudiobookPositionRestoreTests + the existing
AudiobookNetworkValidationTests + AudiobookLoadFailureSAMLReauthTests):
0% -> 88.9% (8/9 killed). The lone survivor is line 1324 '>' -> '>='
on candidates.sorted: with distinct ISO8601 timestamps '>' and '>='
produce identical output, and on a tie either bookmark is equally
"most recent" — killing it would require asserting an arbitrary Swift
sort-stability detail with no behavioral contract, which is banned
fluff. Documented as an equivalent mutant, not covered.

**Scope:** Test-coverage fix on a critical path (audiobook position
restore). Production change is visibility-only + one pure-function
extraction (no behavior change): 4 methods private->internal, plus
fallbackToMostRecentValidBookmark delegating to the extracted
selectMostRecentValidBookmark. No call-site, signature, or control-flow
change to shipping behavior.
**Not done:** the 1324 '>'->'>=' tie-break mutant (equivalent — see above).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds discriminating, behavior-asserting tests for two 3.2.0-regression
test-quality findings surfaced by diff-only mutation testing. Test-only
change — no production code touched.

F-006 DownloadAuthRetryHandler (foreign-host 401 guard, lines 234/240):
  - testHandle_401_foreignHost_browserSAML_shortCircuitsReturnsFalse_noReauth
  - testHandle_401_accountHost_browserSAML_guardDoesNotFire_drivesSAMLRetry
  - testHandle_403_foreignHost_noCredentials_guardDoesNotIntercept_signInStillFires
  Kills :234 ==->!= and :240 return false->true. diff-only kill rate 100% (3/3).

F-008 SAML/browser-auth branch detection:
  - BookReturnService :476 isBrowserBased==true (markCredentialsStale gate):
    testReturnBook_SAMLBrowserAuth_invalidCredentials_marksCredentialsStaleBeforeReauth
    + basicAuth negative control. diff-only 100% (2/2), :476 KILLED.
  - TokenRefreshInterceptor :166 + :216 isSaml==true (SAML-vs-browser legacy
    dispatch): two tests asserting .SAMLStarted+startDownload+no-reauth vs
    .downloadNeeded+reauth. Both :166 and :216 KILLED.
  - BorrowOperation :636 needsBrowserReauth (isBrowserBased==true && hasCreds):
    testBorrow_SAMLBrowserAuth_withCredentials_routesToReauthModalNotAlert.
    :636 ==->!= and &&->|| both KILLED (file 33%->63.6%).

**Scope:** test-only; 4 PalaceTests files; +417 LOC.
**Not done (deliberate, anti-fluff per CLAUDE.md):** BorrowOperation :685/:677
(isSaml==true selecting authLabel/reason) are cosmetic — authLabel is
log-only and reason collapses to the same AuthCoordinator.routeName "modal"
route for a SAML mechanism, so no observable downstream difference. :581/:592
are logically dead: the isAuthError closure unconditionally returns true at
:592, so :581 flips change nothing observable. Killing these would require
fluff or a production-logic change out of F-008 scope.
**Deferred:** none.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…diobook-position-coverage' into fix/pp4542-bookregistrysync-contract
…4542)

XCTestConfigurationFilePath / environment["XCTest..."] / NSClassFromString("XCTest")
guards are unkillable by design (suite always runs under XCTest), so counting
them as mutants deflates kill rate with no real coverage signal. Mirrors the
existing _LOG_LINE_PATTERN skip. TPPReauthenticator.swift:66 was the canonical
false-0%: now correctly reports 0 mutable points on the changed line.

**Scope:** tooling only (scripts/palace_mutate.py); no production or test code.
Verified: diff-only enumeration on TPPReauthenticator drops 1->0 mutants.
…hangeset

ForgeOS changeset: cs_7e7ab728
Reviewed-by (architect): rev_39f29990 APPROVED (extraction behavior-preserving; internal-not-public visibility; no behavior change)
Reviewed-by (qa_test): rev_7114cb1a APPROVED (XCTSkipIf dodge removed; mutation kills credible; deliberate skips legitimate)

Gates passed: review, testing, release (proj_87884c17).
Critical-path file touched: Palace/Audiobooks/AudiobookSessionManager.swift.

**Scope:** governance record only (empty commit). Non-blocking follow-ups:
F-009 (BookReturnService:235 task! force-unwrap, pre-existing, separate changeset);
QA warning on fixed-poll async test loops.
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

🏗️ CodeAtlas Ledger Analysis

✅ All Checks Passed


♿ Accessibility (via AccessLint)

💡 Static analysis against WCAG 2.1 AA guidelines for iOS accessibility.

No accessibility issues detected


🧪 Test Coverage (via QAAtlas)

⚠️ No coverage data available - QAAtlas requires an OpenAI API key.
Set OPENAI_API_KEY in repository secrets to enable AI-powered test coverage analysis.


🏛️ Architecture Analysis

Metric Value ℹ️ What This Means
Components 22 Distinct modules/layers detected in your codebase
Dependency Cycles 0 Circular dependencies (A→B→C→A). Goal: 0
Layer Violations 1 Dependencies that break architectural boundaries
Hotspots 0 Files with high complexity + frequent changes
Avg Coupling 0.55 How interconnected modules are (lower is better, <1.0 is good)

🔍 Reachability Analysis

💡 Detects code that cannot be reached from entry points (dead code).

No dead code detected


📊 0 files analyzed | 📦 Download Full Report

Powered by CodeAtlas Ledger
• Accessibility: AccessLint

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

🧪 Unit Test Results

📊 View Full Interactive Report

❌ TESTS FAILED

📊 Testing Coverage Breakdown

Why two coverage numbers? Testable coverage subtracts files that can't be exercised from xcodebuild — SwiftUI views, UIKit VCs, lifecycle (see scripts/coverage-exclude.json) — so raising it means more testable logic is tested, not that we shipped less UI. Total coverage is kept for continuity. The excluded paths are covered by simdrive E2E journeys (see chaos-replay-on-pr.yml).


🔗 Interactive HTML Report | CI Run Details

📦 Downloadable Artifacts
Artifact Description
test-report 📄 Markdown + HTML reports
test-data 📊 JSON data for tooling
test-results 🔍 Full xcresult (open in Xcode)

…007/F-003 tests

CI build-and-test on #1052 failed the static isolation lints (not a flake):
- AudiobookPositionRestoreTests used a bare `AccountsManager()` (via a
  `makeIsolatedAccountsManager()` helper) — the exact polluter the lint guards
  (bare init spins a background loadCatalogs Task that outlives the test).
  Fixed: build the manager through the whitelisted `makeTestAppContainer`
  (pins deferInitialLoadCatalogsForTesting, so no background work) and rename
  the helper to `makeIsolatedManager()` so its call sites don't trip the
  substring `AccountsManager(` matcher.
- BookRegistrySyncTests' 3 `AppContainer.production().accountsManager
  .userAccount(for:)` credential writes are legitimate: sync() checks
  hasCredentials() via sharedAccount→production keychain-backed userAccount
  (only currentAccount STATE is injected). Marked `// MIGRATED-DEFERRED:` per
  the lint's documented escape for "production resolution IS the test contract".

Verified: AccountsManagerIsolationLintTests + AppContainerIsolationLintTests +
AudiobookPositionRestoreTests + BookRegistrySyncTests = 42 tests, 0 failures.

**Scope:** test-only; no production change. Resolves the #1052 CI red.
@mauricecarrier7 mauricecarrier7 self-assigned this Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant