C2: test the audio half of validate, and the one video refusal missing #146

Merged
JMR-dev merged 3 commits from test/container-capabilities-audio into test/readspec-enum-fallbacks 2026-08-27 13:55:33 +00:00
JMR-dev commented 2026-08-27 03:27:49 +00:00 (Migrated from github.com)

Closes #136.

The two halves of ContainerCapabilities.validate were written together and only one of them was ever checked. Six audio outcomes had no test — every one a string the user reads — while the video twin of each was already covered.

Seven tests, shaped like their twins

new test the twin it mirrors
unidentifiable source audio on a COPY an unidentifiable source codec cannot be copied
container cannot hold the copied source a codec the container cannot hold is refused in both modes
container cannot carry it on the encode path H265 in AVI is refused — AVI predates it
this app cannot encode it copying is offered as the fix when the codec is right but unencodable
accepts(_, AudioCodec.NONE, _) → true the VideoCodec.NONE arm
accepts(_, AudioCodec.COPY, _) throws resolving COPY before asking the matrix is required

Deliberately shaped like the twins rather than as a fresh idea about what to assert — the point is that one axis was checked and the other was not.

The seventh test is not the audio axis: validateVideo's copy-into-a-container-that-cannot-hold-it refusal (:196-199) was the one video outcome with no test, and it is the same shape in the same file.

Each asserts the message verbatim and re-validates every suggestion the refusal offers. Validation.Invalid promises its suggestions are themselves valid and names this class as the proof; the existing property test walks the presets, and no preset reaches suggestions() through validateAudio.

Mutations — seven run, seven red, each isolated to exactly one test

mutation reddens
CARRIES_AUDIO check → false encode-path test only
drop the AudioCodec.COPY -> error(...) arm resolve-first test only
AudioCodec.NONE -> false no-audio-track test only
drop the ENCODABLE_AUDIO check unencodable test only
drop the audio copy container check audio-copy test only
drop the video copy container check video-copy test only
drop the unidentified-audio guard unidentifiable test only

Perfect isolation in every case — no mutation reddened a test other than its own.

Coverage

ContainerCapabilities.kt has no never-executed lines (was :101-102, :197-199, :227-229, :232-234, :241-243, :247-250). Missed branches 21 → 12. Not the acceptance — the mutation table is.

Note for #131

an audio codec this app cannot encode is refused, and copying is offered instead pins the other side of finding F1 in docs/coverage-read-findings.md: Matroska carries Vorbis, nothing here encodes it, and FFmpegCommandBuilder:188 has a libvorbis arm anyway. This test locks in today's answer; it does not settle whether that answer is right.

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

🤖 Generated with Claude Code

Closes #136. The two halves of `ContainerCapabilities.validate` were written together and only one of them was ever checked. **Six audio outcomes had no test** — every one a string the user reads — while the video twin of each was already covered. ### Seven tests, shaped like their twins | new test | the twin it mirrors | |---|---| | unidentifiable source audio on a COPY | `an unidentifiable source codec cannot be copied` | | container cannot hold the copied source | `a codec the container cannot hold is refused in both modes` | | container cannot carry it on the encode path | `H265 in AVI is refused — AVI predates it` | | this app cannot encode it | `copying is offered as the fix when the codec is right but unencodable` | | `accepts(_, AudioCodec.NONE, _)` → true | the `VideoCodec.NONE` arm | | `accepts(_, AudioCodec.COPY, _)` throws | `resolving COPY before asking the matrix is required` | Deliberately shaped like the twins rather than as a fresh idea about what to assert — the point is that one axis was checked and the other was not. The seventh test is **not** the audio axis: `validateVideo`'s copy-into-a-container-that-cannot-hold-it refusal (`:196-199`) was the one video outcome with no test, and it is the same shape in the same file. Each asserts the message **verbatim** and re-validates every suggestion the refusal offers. `Validation.Invalid` promises its suggestions are themselves valid and names this class as the proof; the existing property test walks the presets, and no preset reaches `suggestions()` through `validateAudio`. ### Mutations — seven run, seven red, each isolated to exactly one test | mutation | reddens | |---|---| | `CARRIES_AUDIO` check → `false` | encode-path test only | | drop the `AudioCodec.COPY -> error(...)` arm | resolve-first test only | | `AudioCodec.NONE -> false` | no-audio-track test only | | drop the `ENCODABLE_AUDIO` check | unencodable test only | | drop the audio copy container check | audio-copy test only | | drop the video copy container check | video-copy test only | | drop the unidentified-audio guard | unidentifiable test only | Perfect isolation in every case — no mutation reddened a test other than its own. ### Coverage `ContainerCapabilities.kt` has **no never-executed lines** (was `:101-102, :197-199, :227-229, :232-234, :241-243, :247-250`). Missed branches 21 → **12**. Not the acceptance — the mutation table is. ### Note for #131 `an audio codec this app cannot encode is refused, and copying is offered instead` pins the *other side* of finding **F1** in `docs/coverage-read-findings.md`: Matroska carries Vorbis, nothing here encodes it, and `FFmpegCommandBuilder:188` has a `libvorbis` arm anyway. This test locks in today's answer; it does not settle whether that answer is right. Local gate green: `ktlintCheck`, `detekt`, `testDebugUnitTest`, `compileDebugAndroidTestKotlin`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.