Kgothatso Ngako ba0c60dd2e test: cover the NostrDao event funnel and the publish durability split
NostrDao is what every event passes through, inbound and outbound, so its two
decisions carry everything downstream: which of two copies of an event wins, and
what survives when the enrichment after a write fails. Both were described in
comments and neither was asserted.

Deduplication, at all four positions. A first sighting is stored. A strictly
newer copy replaces the stored one. An older copy is ignored. And -- the case
that actually distinguishes the implementations -- a redelivery carrying the
*same* timestamp is a no-op, because the comparison is a strict `>`. That last
one is not hypothetical: relays redeliver and negentropy re-syncs, so the
common case is the same event arriving again unchanged, and a `>=` there would
rewrite the row on every delivery.

The publish durability split, which is where a bug shipped.
`commitPublishedNostrEvent` is the durable half -- mark the unsigned row signed,
store the event, queue a broadcast per relay -- and indexing is best-effort
enrichment that runs in its own transaction. They used to share one, so any
throw in indexing rolled back `signedAt` too. Because the notary drains one
unsigned row at a time, that row was then re-selected forever and every event
queued behind it went unsigned, including the MLS key package that is enqueued
last. The test provokes the failure the way the code itself would fail:
publishing with no target relays reaches `relayURLs.first()` inside the try and
throws. It then asserts `signedAt` and the stored event both survived. The happy
path is covered alongside it, asserting a broadcast request per target relay, so
the durability test cannot pass by publishing nothing at all.

Also covered: an event from an author with no profile leaves a "LOADING..."
placeholder stamped GENESIS_AT rather than nothing, since that row is the only
record that the pubkey was seen and needs fetching; and
rescheduleBroadcastNostrEventRequests re-queueing a broadcast and re-linking it
to the chat line when the event is a group message that has one, without
inventing a relation when it does not.

One test began as a wrong assumption and the schema corrected it. The "no chat
line" case was first written against an event id that had never been stored, and
failed with SQLite 787: BroadcastNostrEventRequest.nostrEventId is a foreign key
onto NostrEvent. So the real invariant is that a broadcast cannot be scheduled
for an event the caller has not saved; the test now stores the event and leaves
only the chat line missing, and says so in a comment rather than quietly
seeding around it.

Verified by mutation: relaxing the dedup comparison to `>=` fails the
same-timestamp test; removing the try/catch around indexing so the throw
propagates fails the durability test. Both mutations were reverted; no
production source is touched by this commit.

Uses `runBlocking<Unit>` on the durability test because its last expression is an
assertNotNull, and a test method that returns a value is rejected by the JUnit4
runner outright.

9 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 03:05:36 +02:00
2026-07-05 23:21:02 +02:00
2026-07-28 09:10:20 +02:00
2026-03-24 04:47:19 +02:00
2026-03-23 01:41:39 +02:00
2026-03-23 01:41:39 +02:00
2026-03-23 01:41:39 +02:00

This is a Kotlin Multiplatform project targeting Android, iOS, Desktop (JVM).

  • /composeApp is for code that will be shared across your Compose Multiplatform applications. It contains several subfolders:

    • commonMain is for code thats common for all targets.
    • Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name. For example, if you want to use Apples CoreCrypto for the iOS part of your Kotlin app, the iosMain folder would be the right place for such calls. Similarly, if you want to edit the Desktop (JVM) specific part, the jvmMain folder is the appropriate location.
  • /iosApp contains iOS applications. Even if youre sharing your UI with Compose Multiplatform, you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project.

Build and Run Android Application

To build and run the development version of the Android app, use the run configuration from the run widget in your IDEs toolbar or build it directly from the terminal:

  • on macOS/Linux
    ./gradlew :composeApp:assembleDebug
    
  • on Windows
    .\gradlew.bat :composeApp:assembleDebug
    

Build and Run Desktop (JVM) Application

To build and run the development version of the desktop app, use the run configuration from the run widget in your IDEs toolbar or run it directly from the terminal:

  • on macOS/Linux
    ./gradlew :composeApp:run
    
  • on Windows
    .\gradlew.bat :composeApp:run
    

Build and Run iOS Application

To build and run the development version of the iOS app, use the run configuration from the run widget in your IDEs toolbar or open the /iosApp directory in Xcode and run it from there.


Learn more about Kotlin Multiplatform

Description
Mantra as a kotlin multiplatform project
https://mantra.press
Readme 8.2 MiB
Languages
Kotlin 100%