C0 — Extract the fake SAF provider into shared scaffolding and give it a way to answer wrongly #134

Closed
opened 2026-08-27 03:06:30 +00:00 by JMR-dev · 0 comments
JMR-dev commented 2026-08-27 03:06:30 +00:00 (Migrated from github.com)

Child 0 of 7 decomposing #132. Blocks C3 (#137) and C6 (#140) — pick this up first. No production change, no new test.

Why this exists

Two of #132's six items are cursor-shaped — InputQuery's row reads (C3 (#137)) and
OutputPublisher.destinationIsKnownEmpty's three short-circuits (C6 (#140)) — and neither can be written
until something can hand them a misbehaving provider.

The provider itself already exists. OutputPublisherPublishTest.kt:49 declares:

internal open class FakeSafProvider : ContentProvider() {
    override fun query(...): Cursor? {
        val file = backingFile(uri)
        if (!file.exists()) return null
        return MatrixCursor(arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)).apply {
            addRow(arrayOf<Any?>(file.name, file.length()))
        }
    }

Those are exactly the two columns InputQuery.displayNameOrNull() and sizeOrNull() read, and
register(...) beside it already does the Robolectric.buildContentProvider(...).create(info)
dance. What it cannot do is answer wrongly. Its row is always (file.name, file.length()), so
there is no way to produce a null value, a missing column, or a negative size — which is the entire
surface C3 (#137) and C6 (#140) are about.

It is also declared inside a test class's file and used only there, so a second test file naming it
would be depending on a class that lives in OutputPublisherPublishTest.kt.

Scope

1. Move it to shared scaffolding. New file app/src/test/java/org/libremediaconverter/convert/FakeProviders.kt,
carrying FakeSafProvider, FakePlainProvider and the register(...) helper. Same package, so
OutputPublisherPublishTest keeps compiling with no import change.

In-tree precedent, same directory: StagingCleanupSupport.kt and ParkedPickDispatcher.kt are both
shared test scaffolding in their own file with no Test suffix. work/WorkerStubs.kt says the rule
out loud — scaffolding more than one test needs moves; a stub serving one test stays next to its
assertion.

2. Give it a way to misbehave, driven by the same reset(...)-style static the class already
uses. Enough to produce:

case what the row looks like
normal (name, length) — today's behaviour, unchanged
null value a row whose DISPLAY_NAME or SIZE cell is null
missing column a cursor whose projection omits one or both columns
negative size a row whose SIZE is -1, the pipe/streaming case InputQuery:98 documents
no row a cursor with zero rows (distinct from today's return null)

Not a mock framework and not a knob per test — five states, named, on a class that already exists.

Done means

OutputPublisherPublishTest passes unchanged against the moved class, and the five states above are
reachable from another test file in the same package.

Mutation: this child adds no assertions of its own, so its check is C3 (#137)'s and C6 (#140)'s — if either can
be written against it without further scaffolding work, this landed correctly. Do not add tests
here
to make the ticket feel substantial; it is an enabler and #57 is the precedent for filing one.

_Child 0 of 7 decomposing #132. **Blocks C3 (#137) and C6 (#140) — pick this up first.** No production change, no new test._ ### Why this exists Two of #132's six items are cursor-shaped — `InputQuery`'s row reads (C3 (#137)) and `OutputPublisher.destinationIsKnownEmpty`'s three short-circuits (C6 (#140)) — and neither can be written until something can hand them a *misbehaving* provider. The provider itself already exists. `OutputPublisherPublishTest.kt:49` declares: ```kotlin internal open class FakeSafProvider : ContentProvider() { override fun query(...): Cursor? { val file = backingFile(uri) if (!file.exists()) return null return MatrixCursor(arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)).apply { addRow(arrayOf<Any?>(file.name, file.length())) } } ``` Those are exactly the two columns `InputQuery.displayNameOrNull()` and `sizeOrNull()` read, and `register(...)` beside it already does the `Robolectric.buildContentProvider(...).create(info)` dance. **What it cannot do is answer wrongly.** Its row is always `(file.name, file.length())`, so there is no way to produce a null value, a missing column, or a negative size — which is the entire surface C3 (#137) and C6 (#140) are about. It is also declared *inside a test class's file* and used only there, so a second test file naming it would be depending on a class that lives in `OutputPublisherPublishTest.kt`. ### Scope **1. Move it to shared scaffolding.** New file `app/src/test/java/org/libremediaconverter/convert/FakeProviders.kt`, carrying `FakeSafProvider`, `FakePlainProvider` and the `register(...)` helper. Same package, so `OutputPublisherPublishTest` keeps compiling with no import change. In-tree precedent, same directory: `StagingCleanupSupport.kt` and `ParkedPickDispatcher.kt` are both shared test scaffolding in their own file with no `Test` suffix. `work/WorkerStubs.kt` says the rule out loud — scaffolding *more than one test needs* moves; a stub serving one test stays next to its assertion. **2. Give it a way to misbehave**, driven by the same `reset(...)`-style static the class already uses. Enough to produce: | case | what the row looks like | |---|---| | normal | `(name, length)` — today's behaviour, unchanged | | null value | a row whose `DISPLAY_NAME` or `SIZE` cell is null | | missing column | a cursor whose projection omits one or both columns | | negative size | a row whose `SIZE` is `-1`, the pipe/streaming case `InputQuery:98` documents | | no row | a cursor with zero rows (distinct from today's `return null`) | Not a mock framework and not a knob per test — five states, named, on a class that already exists. ### Done means `OutputPublisherPublishTest` passes unchanged against the moved class, and the five states above are reachable from another test file in the same package. **Mutation:** this child adds no assertions of its own, so its check is C3 (#137)'s and C6 (#140)'s — if either can be written against it without further scaffolding work, this landed correctly. **Do not add tests here** to make the ticket feel substantial; it is an enabler and #57 is the precedent for filing one.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: JMR-dev/LibreMediaConverter#134