Files
mantra-kmp/composeApp/src
Kgothatso Ngako aff1e330a0 fix(repository): a no-op answers all sixty calls, and a test keeps it total
Sixty `TODO("Not yet implemented")` bodies sat across four no-op repositories -- 41 of
45 on `NostrRepository`, 12 of 37 on `ChatRepository`, 6 of 7 on `MarmotRepository`,
1 of 1 on `SearchRepository`. f46a7c62 fixed the one that was firing and flagged the
rest for their own change; this is that change.

**Why a stub here is not one test's problem.** These are reached from view models that
call their repository inside `viewModelScope.launch(Dispatchers.IO)`, a coroutine nobody
awaits. The `NotImplementedError` escapes to the platform handler, where
kotlinx-coroutines-test's `ExceptionCollector` stashes it in `unprocessedExceptions` --
a `runBlocking` test has no `TestScope` registered to take it -- and the *next* `runTest`
drains the stash and rethrows it as `UncaughtExceptionsBeforeTest`, under its own name,
for a call it never made. Reproduced before touching anything: `jvmTest --rerun-tasks`
failed `NavigationRoutingTest > an account already on the device does not stay on the
loading screen` with four suppressed `NotImplementedError`s, each naming
`NostrRepository.kt:376` from `ChatMessageListViewModel.kt:366`. Seventeen jvmTest files
build on the Nostr one alone.

**Inert, not loud -- for every member.** The question per member was whether a silent
body or a deliberate throw is right, and the callers settle it: the only two populations
are `@ConformancePreviews` composables in commonMain and test fixtures, and both want a
repository that has no data rather than one that refuses. A preview that throws is a
broken preview. `NO_OP_DKG_REPOSITORY` and `NO_OP_FROST_SIGNING_REPOSITORY` already work
this way, so this follows that idiom instead of inventing a second one.

**Throwing is never the mechanism for being loud.** Keeping a throw for members that
"should not be called" was the alternative, and it is wrong twice: it re-opens exactly
this bug the next time such a member is reached from an unawaited launch, and it puts the
assertion in the fixture instead of the test. A member a test must not call is that
test's assertion to make -- override it with a recorder and assert it stayed untouched.

**A callback is not invoked.** `onCompletion`, `onSuccess` and `onError` are left alone
by `createNewProfile`, `createNewTextNote`, `leaveChatRoom`, `softDeleteChatRoom` and
`deleteChatRoom`. Calling `onCompletion` would say work happened that did not, which is
how a test comes to pass on the fixture rather than on the code; a test that wants a
branch fired overrides the member and fires the one it is testing.

**A flow emits its empty value once, rather than never.** `flowOf(null)` and
`flowOf(emptyList())` throughout, and `ChatRepository`'s two existing `flow { }` stubs
converted with them. An empty flow leaves a screen in Loading for ever; one emission lets
it render its empty state, which is what a preview is for.

**`createMlsDirectMessage` is the one that cannot say "nothing".** It owes the id of a
room and `String` has no absent value, so it returns `""`, documented as such. The
signature is the real oddity -- both creators beside it can report failure
(`createNip17ChatRoom` returns null, `createMarmotGroup` an `Outcome`) -- but changing it
would touch `DatabaseChatRepository` and its caller, so it is flagged in a comment rather
than widened here. Nothing reaches it in practice: `initiateNewChat` calls it only once a
profile and a key package have both resolved, and against this object neither ever does.

**Nothing was passing vacuously, checked rather than assumed.** The risk in a blanket
sweep is a test that currently fails honestly starting to pass on an empty body. No test
anywhere asserts `NotImplementedError`, and the suite went from 982 tests with 1 failure
to 983 with 0, with nothing newly failing -- including across the `flow { }` change and
the callback decision.

**The guard discovers the singletons rather than listing them.** `NoOpRepositoryTotality`
reads the `repository/` directory, keeps every file declaring a `NO_OP_` val, and reports
each `TODO(` in code with its file, line and member -- comments excluded, since the
contract on `NO_OP_NOSTR_REPOSITORY` names `TODO()` in its own prose. A hand-written list
was the first attempt and was already wrong: it named six files when the tree holds eight,
missing `MantraRepository` and `CachingImportRepository`, both total by luck rather than
cover. A floor of 8 goes with it, in the spirit of `m3-audit.sh`'s floors, because the way
a source-reading test fails silently is by finding nothing and passing. Both halves were
mutation-tested: a reintroduced `TODO()` fails naming `MarmotRepository.kt:49` and its
member, and a floor of 99 reports all eight files found.

**Calling the members for real was the alternative, and tells you less.** Sixty live
calls need a fixture for every parameter type, and a reflective sweep cannot tell a body
that threw from an argument it could not build -- so a failure would not distinguish the
bug from the harness. The invariant is a source-level one, so it is checked there.

**f46a7c62's narrow override is removed.** With the no-op total, the
`object : NostrRepository by ...` in `CeremonyQueueRoutingJvmTest` is dead weight, and its
comment had become false -- it asserted the member is "one of the forty-one `TODO()`
bodies". Replaced with the bare no-op and a past-tense note pointing at the guard.

**Not done here: the reporting mechanism.** The open question was whether the suite should
fail fast on an uncaught coroutine exception, or name the right test. Tracing
kotlinx-coroutines-test 1.11 shows the culprit's stack trace is already preserved, as a
suppressed cause on the `UncaughtExceptionsBeforeTest` -- nothing is lost, it is only
filed under the wrong name, and Gradle's console shows just the top line. A handler that
re-printed it would add noise, not information, so none was added; the missing name is
what this commit supplies. `MarmotRepository.NO_OP_MARMOT_KEY_PACKAGE_BUNDLE` is also
still misnamed -- it is a repository, not a bundle -- and left for its own change.

1171 jvm tests, 0 failed, under `--rerun-tasks`, on the merged tree. m3Audit clean,
`:composeApp:compileDebugKotlinAndroid` green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-02 22:21:49 +02:00
..