package io.flamenet.e2e import javax.crypto.KeyGenerator import javax.crypto.SecretKey import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue /** * Muting one contact's stories (ios#33). * * The ordinary middle between seeing everything and blocking somebody. * Entirely local: no envelope, no relay call, and the author is never told. */ class StoryMuteTest { private class MapStorage : Vault.Storage { val records = LinkedHashMap() override fun read(name: String) = records[name] override fun write(name: String, bytes: ByteArray) { records[name] = bytes } override fun delete(name: String) { records.remove(name) } override fun names() = records.keys.toList() } private fun key(): SecretKey = KeyGenerator.getInstance("AES").apply { init(256) }.generateKey() private fun story(author: Long, at: Long) = Vault.Story( id = "s$author-$at", authorId = author, mine = false, ref = Attachment.Ref( kind = Attachment.STORY, id = "blob$at", key = ByteArray(32), digest = ByteArray(32), bytes = 1, mime = "image/jpeg", width = 1, height = 1, duration = 0.0, expires = 0.0, ), caption = "", postedAt = at, seen = false, ) @Test fun `a muted author is dropped from the strip`() { val visible = Vault.visibleStories( listOf(story(7, 3), story(8, 2), story(9, 1)), setOf(8), me = 1) assertEquals(listOf(7L, 9L), visible.map { it.authorId }) } /** ⚠️ Order is the caller's — newest first. Filtering must not re-sort. */ @Test fun `filtering preserves the order it was given`() { val visible = Vault.visibleStories( listOf(story(9, 3), story(7, 2), story(8, 1)), emptySet(), me = 1) assertEquals(listOf(9L, 7L, 8L), visible.map { it.authorId }) } /** * 🔴 Your own posts survive your own id being in the set. `setStoryMuted` * refuses to add it, but a restored backup could carry one, and hiding * somebody's own stories from them reads as the posts having failed. */ @Test fun `your own stories are never hidden even if your id is muted`() { val visible = Vault.visibleStories( listOf(story(1, 2), story(7, 1)), setOf(1, 7), me = 1) assertEquals(listOf(1L), visible.map { it.authorId }) } @Test fun `muting nobody changes nothing`() { val all = listOf(story(7, 2), story(8, 1)) assertEquals(2, Vault.visibleStories(all, emptySet(), me = 1).size) } @Test fun `a muted author with no stories is simply absent`() { val visible = Vault.visibleStories(listOf(story(7, 1)), setOf(99), me = 1) assertEquals(listOf(7L), visible.map { it.authorId }) } // -- Persistence --------------------------------------------------------- @Test fun `the muted set round-trips through the vault`() { val vault = Vault(MapStorage(), key()) assertTrue(vault.mutedStoryAuthors().isEmpty()) vault.saveMutedStoryAuthors(setOf(8L, 12L)) assertEquals(setOf(8L, 12L), vault.mutedStoryAuthors()) } /** An absent record is an empty set, not a crash. */ @Test fun `a vault written before this feature reads as nothing muted`() { assertTrue(Vault(MapStorage(), key()).mutedStoryAuthors().isEmpty()) } }