C0 + C3: fake-provider scaffolding, and what InputQuery makes of a metadata row #144

Merged
JMR-dev merged 2 commits from test/fake-provider-scaffolding into main 2026-08-27 12:14:15 +00:00
JMR-dev commented 2026-08-27 03:20:15 +00:00 (Migrated from github.com)

Closes #134. Closes #137.

Two commits, one per ticket. #134 is an enabler with no consumer of its own, so it ships with the first thing that consumes it — that is also the only way its "done means" is checkable.

#134 — move the fake providers to scaffolding, let them answer wrongly

FakeSafProvider lived inside OutputPublisherPublishTest.kt and could only answer correctly: its row was always (file.name, file.length()). Moved to FakeProviders.kt in the same package, following StagingCleanupSupport.kt and ParkedPickDispatcher.kt. UnreliableOutputStream stays behind — it serves one test, which is the line WorkerStubs.kt draws.

Added RowShape: eight ways a provider can answer a metadata query. Column granularity is deliberate — OutputPublisher reads only SIZE, InputQuery reads both and reaches different answers depending on which is bad — and so is keeping null, missing, negative, no-row and throws distinct rather than folding them into one "bad" case.

No production change. OutputPublisherPublishTest, OutputPublisherStagingTest and UnknownInputSizeTest pass unchanged.

#137 — pin what InputQuery makes of a metadata row

Nothing had ever handed InputQuery a row. firstRow's body, displayNameOrNull and sizeOrNull had never executed in the JVM suite.

Nine tests. What they pin is not "reads a cursor" — that passes against almost any implementation — but the rule the class exists for: a size nobody could determine must arrive as null, never 0. hasSpaceFor(0) is only "is there 128 MB free".

Mutations run — three bite, one does not

mutation result
drop takeIf { it >= 0 } from sizeOrNull red — negative-size test
drop !isNull(it) from sizeOrNull red — null-size test
drop the runCatching in firstRow red — throwing-provider test
drop !isNull(it) from displayNameOrNull green — does not bite

That last row is in the test's KDoc as a named exemption, not papered over. Measured: MatrixCursor.getString on a null cell returns null, while getLong returns 0. So the guard is load-bearing on the size path — it is exactly what stops a null becoming a real number — and unfalsifiable on the name path, where getString already yields null. It stays regardless: Cursor.getString's contract makes throwing-on-null implementation-defined, so a real provider may do what MatrixCursor does not.

Numbers

InputQuery.kt now has no never-executed lines (was 90, 104-105, 107-108). Suite 456 → 465 tests. Branch coverage 63.8% → 65.6%; line 84.9% → 85.0%. The coverage move is not the acceptance — the mutation table is.

Local gate green: ktlintCheck, detekt, lintDebug, testDebugUnitTest, compileDebugAndroidTestKotlin.

🤖 Generated with Claude Code

Closes #134. Closes #137. Two commits, one per ticket. #134 is an enabler with no consumer of its own, so it ships with the first thing that consumes it — that is also the only way its "done means" is checkable. ### #134 — move the fake providers to scaffolding, let them answer wrongly `FakeSafProvider` lived inside `OutputPublisherPublishTest.kt` and could only answer *correctly*: its row was always `(file.name, file.length())`. Moved to `FakeProviders.kt` in the same package, following `StagingCleanupSupport.kt` and `ParkedPickDispatcher.kt`. `UnreliableOutputStream` stays behind — it serves one test, which is the line `WorkerStubs.kt` draws. Added `RowShape`: eight ways a provider can answer a metadata query. Column granularity is deliberate — `OutputPublisher` reads only `SIZE`, `InputQuery` reads both and reaches different answers depending on which is bad — and so is keeping null, missing, negative, no-row and throws distinct rather than folding them into one "bad" case. No production change. `OutputPublisherPublishTest`, `OutputPublisherStagingTest` and `UnknownInputSizeTest` pass unchanged. ### #137 — pin what `InputQuery` makes of a metadata row Nothing had ever handed `InputQuery` a row. `firstRow`'s body, `displayNameOrNull` and `sizeOrNull` had **never executed** in the JVM suite. Nine tests. What they pin is not "reads a cursor" — that passes against almost any implementation — but the rule the class exists for: **a size nobody could determine must arrive as `null`, never `0`.** `hasSpaceFor(0)` is only "is there 128 MB free". ### Mutations run — three bite, one does not | mutation | result | |---|---| | drop `takeIf { it >= 0 }` from `sizeOrNull` | **red** — negative-size test | | drop `!isNull(it)` from `sizeOrNull` | **red** — null-size test | | drop the `runCatching` in `firstRow` | **red** — throwing-provider test | | drop `!isNull(it)` from `displayNameOrNull` | **green — does not bite** | That last row is in the test's KDoc as a **named exemption**, not papered over. Measured: `MatrixCursor.getString` on a null cell returns `null`, while `getLong` returns **`0`**. So the guard is load-bearing on the size path — it is exactly what stops a null becoming a real number — and unfalsifiable on the name path, where `getString` already yields null. It stays regardless: `Cursor.getString`'s contract makes throwing-on-null *implementation-defined*, so a real provider may do what `MatrixCursor` does not. ### Numbers `InputQuery.kt` now has **no never-executed lines** (was 90, 104-105, 107-108). Suite 456 → 465 tests. Branch coverage 63.8% → **65.6%**; line 84.9% → 85.0%. The coverage move is not the acceptance — the mutation table is. Local gate green: `ktlintCheck`, `detekt`, `lintDebug`, `testDebugUnitTest`, `compileDebugAndroidTestKotlin`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.