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.
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.
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)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #136.
The two halves of
ContainerCapabilities.validatewere 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
an unidentifiable source codec cannot be copieda codec the container cannot hold is refused in both modesH265 in AVI is refused — AVI predates itcopying is offered as the fix when the codec is right but unencodableaccepts(_, AudioCodec.NONE, _)→ trueVideoCodec.NONEarmaccepts(_, AudioCodec.COPY, _)throwsresolving COPY before asking the matrix is requiredDeliberately 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.Invalidpromises its suggestions are themselves valid and names this class as the proof; the existing property test walks the presets, and no preset reachessuggestions()throughvalidateAudio.Mutations — seven run, seven red, each isolated to exactly one test
CARRIES_AUDIOcheck →falseAudioCodec.COPY -> error(...)armAudioCodec.NONE -> falseENCODABLE_AUDIOcheckPerfect isolation in every case — no mutation reddened a test other than its own.
Coverage
ContainerCapabilities.kthas 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 insteadpins the other side of finding F1 indocs/coverage-read-findings.md: Matroska carries Vorbis, nothing here encodes it, andFFmpegCommandBuilder:188has alibvorbisarm 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