package io.flamenet.e2e import org.json.JSONObject import javax.crypto.KeyGenerator import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue /** * Disappearing messages (ยง6.11), at the vault. */ class DisappearingTest { private class MapStorage : Vault.Storage { private val records = mutableMapOf() 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 vault() = Vault(MapStorage(), KeyGenerator.getInstance("AES").apply { init(256) }.generateKey()) private fun line( text: String, at: Long, ) = JSONObject().put("text", text).put("mine", true).put("at", at) private val now = 1_700_000_000_000L @Test fun `with no timer nothing is removed`() { val v = vault() v.append("t", line("ancient", 1L)) assertEquals(1, v.history("t", now).length()) } @Test fun `messages older than the timer are gone on the next read`() { val v = vault() v.saveDisappearSeconds("t", 60) v.append("t", line("old", now - 120_000)) v.append("t", line("fresh", now - 10_000)) val kept = v.history("t", now) assertEquals(1, kept.length()) assertEquals("fresh", kept.getJSONObject(0).optString("text")) } /** Gone from disk, not merely filtered out of the answer. */ @Test fun `expiry is a deletion, not a filter`() { val v = vault() v.saveDisappearSeconds("t", 60) v.append("t", line("old", now - 120_000)) v.history("t", now) // Read again with the timer switched off: an expired message must not // come back, which it would if the first read had only hidden it. v.saveDisappearSeconds("t", 0) assertEquals(0, v.history("t", now).length()) } /** * A message with no timestamp predates the field. */ @Test fun `a message with no timestamp is kept`() { val v = vault() v.saveDisappearSeconds("t", 60) v.append("t", JSONObject().put("text", "from before").put("mine", true)) assertEquals(1, v.history("t", now).length()) } @Test fun `the timer is per conversation`() { val v = vault() v.saveDisappearSeconds("timed", 60) v.append("timed", line("old", now - 120_000)) v.append("untimed", line("old", now - 120_000)) assertEquals(0, v.history("timed", now).length()) assertEquals(1, v.history("untimed", now).length()) } @Test fun `turning the timer off forgets it`() { val v = vault() v.saveDisappearSeconds("t", 3600) assertEquals(3600, v.disappearSeconds("t")) v.saveDisappearSeconds("t", 0) assertEquals(0, v.disappearSeconds("t")) } /** Exactly at the boundary the message survives, off-by-one deletes data. */ @Test fun `a message exactly at the cutoff survives`() { val v = vault() v.saveDisappearSeconds("t", 60) v.append("t", line("edge", now - 60_000)) assertTrue(v.history("t", now).length() == 1) } }