From 366f5cbeab05f8fc86f4299d409719418fd89934 Mon Sep 17 00:00:00 2001 From: Paul Hitt Date: Sun, 30 Aug 2026 17:04:38 -0400 Subject: [PATCH] Live tests: read what a person sees, not what the wire carries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six live conversation tests failed after key-log checking became automatic, and the product was right and the tests were wrong. `poll` returns control traffic as well as messages. Automatic enforcement is what sets the transparency-log head pin โ€” and ยง19.4 gossip has nothing to send without one, so until now the first send to a contact never carried this device's view of the log. It does now. That envelope is consumed by the client and never shown, exactly as the app has always filtered it, but these tests asserted on the raw list and counted it as a message. Worth stating plainly, because it is the more interesting half: gossip was written, called, and inert. Each client verified the relay was consistent with itself and never offered a head for anybody to compare against โ€” which is the whole mechanism for catching a relay that serves two different logs. Turning enforcement on turned that on too. Verified against a live relay rather than a mock: 203 client tests including sign-up, device registration, prekey publication, two-party and group conversations, images, sealed sender and the deployed key log. Co-Authored-By: Claude Opus 4.8 --- .../flamenet/client/LiveConversationTest.kt | 37 ++++++++++++++----- .../io/flamenet/client/LiveShapesTest.kt | 9 ++++- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/client/src/test/kotlin/io/flamenet/client/LiveConversationTest.kt b/client/src/test/kotlin/io/flamenet/client/LiveConversationTest.kt index f097b0b..c8b7168 100644 --- a/client/src/test/kotlin/io/flamenet/client/LiveConversationTest.kt +++ b/client/src/test/kotlin/io/flamenet/client/LiveConversationTest.kt @@ -69,6 +69,23 @@ class LiveConversationTest { ) } + /** + * What a person would see, which is what every assertion below means. + * + * ๐Ÿ”ด **`poll` returns control traffic too**, and since key-log checking + * became automatic there is reliably some: the first send to a contact now + * gossips this device's view of the transparency log (ยง19.4), because + * enforcement sets the head pin that gossip had nothing to send without. + * That envelope is consumed by the client and never shown, and asserting on + * the raw list counted it as a message โ€” so these tests failed on a change + * that made the product more correct, not less. + * + * The app has always filtered this way; the tests were reading a layer + * lower than the thing they were describing. + */ + private fun Conversations.readable(): List = + poll().filter { it.content.isRenderable } + @Test fun `two people exchange messages through a real relay`() { val base = fleet ?: return @@ -81,7 +98,7 @@ class LiveConversationTest { assertTrue(sent.reachedSomeone, "nothing was delivered: ${sent.failed}") assertEquals(listOf(bob.identity.deviceId), sent.delivered) - val received = bob.conversations.poll() + val received = bob.conversations.readable() assertEquals(1, received.size, "Bob's inbox had ${received.size} messages") assertEquals("hello from the Android engine ๐Ÿ‘‹", received[0].text) assertEquals(alice.userId, received[0].fromUser) @@ -90,16 +107,16 @@ class LiveConversationTest { // Bob replies on the session he just established, which is the step // that turns a handshake into a conversation. assertTrue(bob.conversations.send(alice.userId, "and hello back").reachedSomeone) - val back = alice.conversations.poll() + val back = alice.conversations.readable() assertEquals(1, back.size) assertEquals("and hello back", back[0].text) // Several turns, so the ratchet is exercised past the handshake. for (turn in 1..3) { alice.conversations.send(bob.userId, "alice $turn") - assertEquals("alice $turn", bob.conversations.poll().single().text) + assertEquals("alice $turn", bob.conversations.readable().single().text) bob.conversations.send(alice.userId, "bob $turn") - assertEquals("bob $turn", alice.conversations.poll().single().text) + assertEquals("bob $turn", alice.conversations.readable().single().text) } } @@ -116,8 +133,8 @@ class LiveConversationTest { val bob = signUp(base, "d") alice.conversations.send(bob.userId, "only once please") - assertEquals(1, bob.conversations.poll().size) - assertEquals(0, bob.conversations.poll().size, "the relay re-delivered a read message") + assertEquals(1, bob.conversations.readable().size) + assertEquals(0, bob.conversations.readable().size, "the relay re-delivered a read message") } /** @@ -146,8 +163,8 @@ class LiveConversationTest { val sent = alice.conversations.send(bob.userId, "to both of your devices") assertEquals(2, sent.delivered.size, "only reached ${sent.delivered}; failed: ${sent.failed}") - assertEquals("to both of your devices", bob.conversations.poll().single().text) - assertEquals("to both of your devices", secondConversations.poll().single().text) + assertEquals("to both of your devices", bob.conversations.readable().single().text) + assertEquals("to both of your devices", secondConversations.readable().single().text) } /** @@ -198,7 +215,7 @@ class LiveConversationTest { bob.userId, picture, "image/png", width = 64, height = 48) assertTrue(sent.reachedSomeone, "nothing delivered: ${sent.failed}") - val received = bob.conversations.poll().single() + val received = bob.conversations.readable().single() val ref = received.content.attachment assertTrue(ref != null, "no attachment reference arrived; text was '${received.text}'") assertEquals("image/png", ref!!.mime) @@ -249,7 +266,7 @@ class LiveConversationTest { assertTrue(sent.reachedSomeone, "nothing delivered: ${sent.failed}") for (member in listOf(bob, carol)) { - val got = member.conversations.poll().single() + val got = member.conversations.readable().single() assertEquals("hello everyone", got.text) assertEquals(group.id, got.content.groupId, "the message did not carry group routing, so it would file under the sender") diff --git a/client/src/test/kotlin/io/flamenet/client/LiveShapesTest.kt b/client/src/test/kotlin/io/flamenet/client/LiveShapesTest.kt index 723e8a3..bdc6581 100644 --- a/client/src/test/kotlin/io/flamenet/client/LiveShapesTest.kt +++ b/client/src/test/kotlin/io/flamenet/client/LiveShapesTest.kt @@ -284,7 +284,14 @@ class LiveShapesTest { val sent = alice.second.send(bob.first, text) assertTrue(sent.reachedSomeone, "$base delivered nothing: ${sent.failed}") - val got = bob.second.poll() + // ๐Ÿ”ด What a PERSON would see. `poll` also returns control traffic, and + // since key-log checking became automatic there is reliably some: the + // first send to a contact gossips this device's view of the + // transparency log (ยง19.4), which enforcement is what sets the pin for. + // The client consumes that envelope and never shows it โ€” counting it as + // a message made this test fail on a change that made the product more + // correct. + val got = bob.second.poll().filter { it.content.isRenderable } assertEquals(1, got.size, "$base: expected one message, got ${got.size}") assertEquals(text, got.single().text) }