test(coverage): lane 1 — repository, mappers & domain logic to >=95% #254

Merged
JMR-dev merged 1 commits from test-246-coverage-repo-mappers-domain into main 2026-07-03 19:09:34 +00:00
JMR-dev commented 2026-07-03 18:56:28 +00:00 (Migrated from github.com)

What

JVM-only unit tests raising coverage of the lane-1 packages (data/repository, the data mappers, and pure domain/** logic). 74 new tests across 4 purely-additive test files — no production code is touched.

  • AccountRepositoryImplTest (new) — AccountRepositoryImpl had no test at all. Covers observeAccounts, testConnection (success + failure), addImapAccount/addOutlookAccount (happy path persists account + credential + channel + sync/backfill; rejected-LIST failure persists nothing; XOAUTH2 vs. password secret handling), deleteAccount, and resetBackfillProgress (per-account vs. all).
  • MailRepositoryImplCoverageTest (new) — the MailRepositoryImpl surface the existing MailRepositoryImplTest skips: observeAttachments/observeUnreadCounts/observeDrafts/observeOutbox, getMessage/getDraft/saveDraft, setStarred/deleteMessage (server-reachable vs. local-only), sendMessage + copyAttachments (staging, sanitized names, unreadable-URI skip, missing-account failure), searchServer (all-accounts vs. filtered, search-only membership), clearSearchResults/retryOutbox, and every "account/row gone mid-flight" fall-through (openMessage, prefetchMessage, inlineImages, refreshFolders, moveToFolder, expunge, downloadAttachment filename fallback, signature-disabled reply). Kept as a separate additive file so the in-flight search PR (#234) and the existing suite are left untouched.
  • MappersTest (new) — entity⇄domain mappers not already pinned: account/settings/message/attachment/outbox round-trips, the unknown-enum fallbacks (authType→PASSWORD_IMAP, security→SSL_TLS), toImapParams/toSmtpParams, and FetchedMessage.toEntity's composite-id / uid-parse rules.
  • DomainModelCoverageTest (new) — AccountSettings.signatureBlock (all three branches), Signature.plainText, default-argument constructors, and the Account.outlook / MailProvider.createAccount blank-display-name fallbacks.

Measured coverage (:app:jacocoTestReport)

In-scope target Instruction Line
domain/model (package) 100.0% 100.0%
domain/repository (package) 100% (no code) 100% (no code)
data/local · MappersKt (the mappers) 98.8% 100.0%
data/repository · AccountRepositoryImpl 100.0% 100.0%
data/repository · MailRepositoryImpl 94.5% 98.3%
data/repository (whole package) 82.9% 98.8%

Line coverage is ≥98.8% for every in-scope package, and instruction coverage is ≥98.8% for the mappers and 100% for all domain logic and AccountRepositoryImpl.

Why data/repository instruction sits at 82.9%

82.8% of the package's missed instructions live in Kotlin-coroutine synthetic classes — the Flow.map collector continuations (…$$inlined$map$1$1, stuck at ~30%) and suspend-lambda state machines (…$openMessage$2 etc., ~60%). Their suspend/resume dispatch branches only execute when a coroutine actually suspends and resumes, which synchronous mocks never trigger — a well-known JaCoCo × coroutines limitation, not untested logic (the package's line coverage is 98.8%). The same effect keeps MailRepositoryImpl's instruction metric at 94.5% despite 98.3% line coverage; only 4 of its lines are unhit.

Excluding those coroutine synthetics (or ratcheting to the achievable level) is JaCoCo-config territory, which per the issue plan is owned by the capstone lane #251 — this PR deliberately does not touch verification/ratchet config.

Verification

assembleDebug · testDebugUnitTest · compileDebugAndroidTestKotlin · lintDebug · ktlintCheck · detekt all green locally (JDK 21). Emulator E2E not applicable — this lane adds only JVM unit tests and changes no production or androidTest code.

Closes #246

🤖 Generated with Claude Code

## What JVM-only unit tests raising coverage of the lane-1 packages (`data/repository`, the data mappers, and pure `domain/**` logic). **74 new tests across 4 purely-additive test files — no production code is touched.** - **`AccountRepositoryImplTest`** (new) — `AccountRepositoryImpl` had **no test at all**. Covers `observeAccounts`, `testConnection` (success + failure), `addImapAccount`/`addOutlookAccount` (happy path persists account + credential + channel + sync/backfill; rejected-LIST failure persists nothing; XOAUTH2 vs. password secret handling), `deleteAccount`, and `resetBackfillProgress` (per-account vs. all). - **`MailRepositoryImplCoverageTest`** (new) — the `MailRepositoryImpl` surface the existing `MailRepositoryImplTest` skips: `observeAttachments`/`observeUnreadCounts`/`observeDrafts`/`observeOutbox`, `getMessage`/`getDraft`/`saveDraft`, `setStarred`/`deleteMessage` (server-reachable vs. local-only), `sendMessage` + `copyAttachments` (staging, sanitized names, unreadable-URI skip, missing-account failure), `searchServer` (all-accounts vs. filtered, search-only membership), `clearSearchResults`/`retryOutbox`, and every "account/row gone mid-flight" fall-through (`openMessage`, `prefetchMessage`, `inlineImages`, `refreshFolders`, `moveToFolder`, `expunge`, `downloadAttachment` filename fallback, signature-disabled reply). Kept as a **separate additive file** so the in-flight search PR (#234) and the existing suite are left untouched. - **`MappersTest`** (new) — entity⇄domain mappers not already pinned: account/settings/message/attachment/outbox round-trips, the **unknown-enum fallbacks** (`authType`→`PASSWORD_IMAP`, `security`→`SSL_TLS`), `toImapParams`/`toSmtpParams`, and `FetchedMessage.toEntity`'s composite-id / uid-parse rules. - **`DomainModelCoverageTest`** (new) — `AccountSettings.signatureBlock` (all three branches), `Signature.plainText`, default-argument constructors, and the `Account.outlook` / `MailProvider.createAccount` blank-display-name fallbacks. ## Measured coverage (`:app:jacocoTestReport`) | In-scope target | Instruction | Line | |---|---|---| | `domain/model` (package) | **100.0%** | **100.0%** | | `domain/repository` (package) | 100% (no code) | 100% (no code) | | `data/local` · `MappersKt` (the mappers) | **98.8%** | **100.0%** | | `data/repository` · `AccountRepositoryImpl` | **100.0%** | **100.0%** | | `data/repository` · `MailRepositoryImpl` | 94.5% | **98.3%** | | `data/repository` (whole package) | 82.9% | **98.8%** | **Line coverage is ≥98.8% for every in-scope package**, and instruction coverage is ≥98.8% for the mappers and 100% for all domain logic and `AccountRepositoryImpl`. ### Why `data/repository` *instruction* sits at 82.9% **82.8% of the package's missed instructions live in Kotlin-coroutine synthetic classes** — the `Flow.map` collector continuations (`…$$inlined$map$1$1`, stuck at ~30%) and suspend-lambda state machines (`…$openMessage$2` etc., ~60%). Their suspend/resume dispatch branches only execute when a coroutine actually suspends and resumes, which synchronous mocks never trigger — a well-known JaCoCo × coroutines limitation, not untested logic (the package's **line** coverage is 98.8%). The same effect keeps `MailRepositoryImpl`'s instruction metric at 94.5% despite 98.3% line coverage; only 4 of its lines are unhit. Excluding those coroutine synthetics (or ratcheting to the achievable level) is JaCoCo-config territory, which per the issue plan is owned by the capstone lane **#251** — this PR deliberately does not touch verification/ratchet config. ## Verification `assembleDebug` · `testDebugUnitTest` · `compileDebugAndroidTestKotlin` · `lintDebug` · `ktlintCheck` · `detekt` all green locally (JDK 21). Emulator E2E not applicable — this lane adds only JVM unit tests and changes no production or androidTest code. Closes #246 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.