Files
mantra-kmp/composeApp
Kgothatso Ngako 5fa0d08dfd test: cover nip17 room derivation and its preconditions
A NIP-17 room has no MLS group, no key packages and no invites -- membership
*is* the p-tag set on each message. Two things follow, and both are load-bearing.

The room id is deriveChatRoomId over the member set, the same aggregate the
inbound path derives from an arriving gift wrap. That is what makes creation
idempotent, and idempotence here is not a nicety: two people starting the same
conversation have to land on one room, or the thread exists twice with each side
writing into its own copy and neither seeing the other. Covered from three
angles -- the order members are named in does not change the id, creating the
same conversation twice reuses the room as it stands rather than rewriting it,
and a different member set derives a different room.

The order-independence test is guarding `deriveChatRoomId`'s own `.sorted()`,
not the DAO's `.toSet()`, and its comment now says so. That was established by
mutation rather than assumed: rebuilding the member set as an order-preserving
LinkedHashSet in the DAO changes nothing, because the derivation sorts anyway,
while removing the sort fails the test. The distinction matters for anyone
reading the DAO and concluding the set is what does the work.

And `mlsGroupState = null` is what marks the room NIP-17. sendChatMessage reads
exactly that field to choose between a kind:445 group event and per-recipient
gift wraps, so a room that acquired MLS state would have its messages routed
down a path no recipient is running.

Covered alongside: the creator is a participant of their own conversation even
when not listed among the participants -- sealGiftWrapPayload walks that list to
decide who to wrap for, so omitting the creator would send messages every other
member could read and the sender could not -- and naming the creator among the
participants does not produce a second row for them.

One test records a precondition and an asymmetry. Participant.participantPublicKey
is a foreign key onto Profile, so createNip17ChatRoom raises a SQLite constraint
failure for a member this device has no profile for, while getOrCreateChatRoom,
one method down, answers the same "never seen this user" situation by returning
null. A caller treating the two alike gets an unhandled exception out of the
first. That was found by writing the tests -- seven of them failed with SQLite
787 before every member was seeded -- and is pinned rather than seeded around
silently.

The remaining getOrCreate coverage: it returns the room already stored rather
than overwriting it with the defaults passed in, stands one up for a user it has
a profile for, and writes nothing at all when it does not.

Real secp256k1 keys throughout, because deriveChatRoomId does point arithmetic
and treats an off-curve value differently from a valid one -- hex filler would
exercise a path users never reach.

11 tests. composeApp jvmTest is 309 tests, 0 failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 03:25:19 +02:00
..