package io.flamenet.e2e import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue /** * A private archive of your own stories (ios#54). * * 🔴 **This keeps something the interface said would disappear.** So it is * opt-in, and it only ever holds stories this account posted — an archive of * other people's expiring stories is the exact opposite of what they were * promised, and "it is private to you" does not repair that. */ class StoryArchiveTest { private fun story(id: String, mine: Boolean, at: Long) = Vault.Story( id = id, authorId = if (mine) 1L else 7L, mine = mine, ref = Attachment.Ref( kind = Attachment.STORY, id = "b$id", key = ByteArray(32), digest = ByteArray(32), bytes = 1, mime = "image/jpeg", width = 1, height = 1, duration = 0.0, expires = 1.0, ), caption = "c$id", postedAt = at, ) @Test fun `your own expired story is kept`() { assertEquals(listOf("1"), Vault.archive(emptyList(), listOf(story("1", true, 100))).map { it.id }) } /** 🔴 The rule the whole feature turns on. */ @Test fun `somebody elses expired story is never kept`() { assertTrue(Vault.archive(emptyList(), listOf(story("2", false, 100))).isEmpty()) } @Test fun `a mixed batch keeps only your own`() { val result = Vault.archive(emptyList(), listOf( story("1", true, 100), story("2", false, 200), story("3", true, 300))) assertEquals(setOf("1", "3"), result.map { it.id }.toSet()) } /** ⚠️ The sweep runs on every load; without this, one story piles up. */ @Test fun `archiving is idempotent`() { val one = story("1", true, 100) val first = Vault.archive(emptyList(), listOf(one)) assertEquals(1, Vault.archive(first, listOf(one)).size) } /** ⚠️ Newest first, so the screen needs no sort. */ @Test fun `the archive is newest first`() { val result = Vault.archive(emptyList(), listOf( story("1", true, 100), story("3", true, 300), story("2", true, 200))) assertEquals(listOf("3", "2", "1"), result.map { it.id }) } 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() = javax.crypto.KeyGenerator.getInstance("AES") .apply { init(256) }.generateKey() /** ⚠️ Kept in the vault, like everything else about somebody's stories. */ @Test fun `the archive round-trips through the vault`() { val vault = Vault(MapStorage(), key()) assertTrue(vault.storyArchive().isEmpty()) vault.saveStoryArchive(listOf(story("1", true, 100))) assertEquals(listOf("1"), vault.storyArchive().map { it.id }) } /** * 🔴 The archive must NOT be swept by the expiry rule the live set uses — * everything in it is expired by definition, so reusing `saveStories` * would empty it on the next write. */ @Test fun `an expired story survives in the archive`() { val vault = Vault(MapStorage(), key()) vault.saveStoryArchive(listOf(story("1", true, 100))) assertEquals(1, vault.storyArchive().size) } }