Recognise mentions in a group message (§6.18)
Codec only — the app does not compose or act on them yet — but it must already understand an envelope the iPhone app sends, and more importantly must not be made to do work by one. 🔴 Bounded and deduplicated on receipt. The list comes off the wire, and a hostile sender could name ten thousand ids in one envelope; the cost of that lands on every recipient's device rather than theirs. Capped at 256, far above any real roster. A malformed list does not break the message: it is an ordinary message with an odd field, not a reason to drop what somebody wrote. Refs paulhitt/flamenet-messenger-ios#49 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d5c2db4e6e
commit
19f4247b71
@@ -0,0 +1,74 @@
|
||||
package io.flamenet.e2e
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Addressing one person inside a group (§6.18).
|
||||
*
|
||||
* ⚠️ **The codec half.** The Android app does not compose or act on mentions
|
||||
* yet, but it must already understand an envelope the iPhone app sends — and,
|
||||
* more importantly, must not be made to do work by one. The bound below is the
|
||||
* part that matters today.
|
||||
*/
|
||||
class MentionsTest {
|
||||
|
||||
@Test
|
||||
fun `mentions ride the envelope`() {
|
||||
val wire = Content.groupText("@Ada look", "g1", messageId = "m",
|
||||
mentions = listOf(2L, 4L))
|
||||
assertEquals(listOf(2L, 4L), Content.parse(wire).mentions)
|
||||
}
|
||||
|
||||
/** Omitted when there are none, which is nearly every message. */
|
||||
@Test
|
||||
fun `a message with no mentions carries none`() {
|
||||
val wire = Content.groupText("hello", "g1", messageId = "m")
|
||||
assertFalse(wire.contains("ment"), "wrote an empty mention list: $wire")
|
||||
assertTrue(Content.parse(wire).mentions.isEmpty())
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔴 **Bounded.** The list comes off the wire, and a hostile sender could
|
||||
* name ten thousand ids in one envelope — the cost of which lands on every
|
||||
* recipient's device rather than theirs.
|
||||
*/
|
||||
@Test
|
||||
fun `an absurd mention list is bounded`() {
|
||||
val huge = (1..10_000).joinToString(",")
|
||||
val wire = """{"fnc":1,"kind":"text","ment":[$huge],"gid":"g1","caption":"hi"}"""
|
||||
assertEquals(Content.MAXIMUM_MENTIONS, Content.parse(wire).mentions.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a repeated mention is counted once`() {
|
||||
val wire = """{"fnc":1,"kind":"text","ment":[2,2,2,4],"gid":"g1","caption":"hi"}"""
|
||||
assertEquals(listOf(2L, 4L), Content.parse(wire).mentions)
|
||||
}
|
||||
|
||||
/** ⚠️ A malformed list must not throw — it is an ordinary message with an
|
||||
* odd field, not a reason to drop what somebody wrote. */
|
||||
@Test
|
||||
fun `a malformed mention list does not break the message`() {
|
||||
val wire = """{"fnc":1,"kind":"text","ment":"not an array","gid":"g1","caption":"hi"}"""
|
||||
val content = Content.parse(wire)
|
||||
assertEquals("hi", content.text)
|
||||
assertTrue(content.mentions.isEmpty())
|
||||
}
|
||||
|
||||
/** ⚠️ `fnc` first, like every other envelope. */
|
||||
@Test
|
||||
fun `the envelope starts with the marker`() {
|
||||
assertTrue(Content.groupText("hi", "g1", mentions = listOf(2L))
|
||||
.startsWith("{\"fnc\":1,"))
|
||||
}
|
||||
|
||||
/** A mention does not stop a message being a message. */
|
||||
@Test
|
||||
fun `a mentioning message is still renderable`() {
|
||||
assertTrue(Content.parse(
|
||||
Content.groupText("@Ada hi", "g1", mentions = listOf(2L))).isRenderable)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user