Reply to a story, as a sealed direct message
Android half of ios#30, spec §6.20. A reply is an ordinary direct message to the author carrying `sre.sid`, landing in the existing conversation. Stories gain no comment thread and no viewer list. The reference carries the id and nothing else. A story's author had the story by definition, so the only way they no longer hold it is that their own timer removed it — sending a copy back would resurrect exactly that, at a moment they did not choose. 🔴 `Content.text` returns the raw body when it has nothing to wrap, so `storyReplyId` had to join that condition. Left out, a reply with no message id and no quote would have gone out as plain text and arrived with the reference silently dropped — on exactly the path a story reply normally takes. There is a test for it. The disclosure is shown before the first reply this account sends, names what is revealed, and says where the reply goes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
03c7855a88
commit
819fab3161
@@ -0,0 +1,85 @@
|
||||
package io.flamenet.e2e
|
||||
|
||||
import org.json.JSONObject
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Story replies as sealed direct messages (spec §6.20, ios#30).
|
||||
*
|
||||
* A story keeps no comment thread and no viewer list. A reply is an ordinary
|
||||
* direct message to the author carrying the story's id — and nothing else.
|
||||
*/
|
||||
class StoryReplyTest {
|
||||
|
||||
@Test
|
||||
fun `a reply carries the story id`() {
|
||||
val wire = Content.text("where is this?", messageId = "m1", storyReplyId = "story-7")
|
||||
val content = Content.parse(wire)
|
||||
assertEquals("story-7", content.storyReplyId)
|
||||
assertEquals("where is this?", content.text)
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔴 **The whole point of the section.** A reply that carried the caption
|
||||
* or a thumbnail would put an expired story back on the author's screen —
|
||||
* the failure §6.19 documents for pins, arriving by a different door.
|
||||
*/
|
||||
@Test
|
||||
fun `the story reference carries nothing but the id`() {
|
||||
val wire = Content.text("nice", messageId = "m1", storyReplyId = "story-7")
|
||||
val sre = JSONObject(wire).getJSONObject("sre")
|
||||
assertEquals(setOf("sid"), sre.keys().asSequence().toSet())
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔴 **The raw-bytes shortcut is the trap.** `text()` returns the body
|
||||
* unwrapped when there is nothing to carry. Without `storyReplyId` in that
|
||||
* condition, a reply with no message id and no quote goes out as plain
|
||||
* text and arrives with the reference silently dropped — on exactly the
|
||||
* path a story reply normally takes.
|
||||
*/
|
||||
@Test
|
||||
fun `a reply with nothing else attached is still an envelope`() {
|
||||
val wire = Content.text("nice", storyReplyId = "story-7")
|
||||
assertTrue(wire.startsWith("{\"fnc\":1"), wire)
|
||||
assertEquals("story-7", Content.parse(wire).storyReplyId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an ordinary message carries no story reference`() {
|
||||
val wire = Content.text("hello", messageId = "m1")
|
||||
assertTrue(!wire.contains("sre"), wire)
|
||||
assertNull(Content.parse(wire).storyReplyId)
|
||||
}
|
||||
|
||||
/** ⚠️ `fnc` must stay first: the receive side recognises it by prefix. */
|
||||
@Test
|
||||
fun `the marker stays first`() {
|
||||
assertTrue(Content.text("hi", storyReplyId = "s1").startsWith("{\"fnc\":1"))
|
||||
}
|
||||
|
||||
/** A newer peer may add fields to `sre`; the message must still read. */
|
||||
@Test
|
||||
fun `an unknown story reference shape does not break the message`() {
|
||||
val wire = """{"fnc":1,"kind":"text","caption":"hi","sre":{"sid":"s1","extra":9}}"""
|
||||
val content = Content.parse(wire)
|
||||
assertEquals("hi", content.text)
|
||||
assertEquals("s1", content.storyReplyId)
|
||||
}
|
||||
|
||||
/** An `sre` with no usable id is absent, not an empty string. */
|
||||
@Test
|
||||
fun `an empty story id reads as no reference`() {
|
||||
val wire = """{"fnc":1,"kind":"text","caption":"hi","sre":{"sid":""}}"""
|
||||
assertNull(Content.parse(wire).storyReplyId)
|
||||
}
|
||||
|
||||
/** A story reply is a renderable message, not control traffic. */
|
||||
@Test
|
||||
fun `a story reply is renderable`() {
|
||||
assertTrue(Content.parse(Content.text("hi", storyReplyId = "s1")).isRenderable)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user