Live tests: read what a person sees, not what the wire carries
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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
db5aa33280
commit
366f5cbeab
@@ -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<Conversations.Received> =
|
||||
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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user