package social.grain import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue import org.junit.Test import social.grain.util.Tid /** * TIDs are the reason a publish is safe to retry, so their two load-bearing * properties — valid syntax and strict monotonicity — are worth pinning down. */ class TidTest { @Test fun `next produces a valid 13-character record key`() { repeat(100) { val tid = Tid.next() assertEquals(13, tid.length) assertTrue("'$tid' should be a valid TID", Tid.isValid(tid)) } } @Test fun `successive TIDs strictly increase even within one clock tick`() { // The whole point of the lastMicros bump: two calls in the same // millisecond must still sort in the order they were issued. val tids = List(1_000) { Tid.next() } for (index in 1 until tids.size) { assertTrue( "${tids[index]} should sort after ${tids[index - 1]}", tids[index] > tids[index - 1], ) } assertEquals(tids.size, tids.toSet().size) } @Test fun `encode packs micros and clock id into sortable base32`() { val earlier = Tid.encode(micros = 1_700_000_000_000_000, clockId = 5) val later = Tid.encode(micros = 1_700_000_000_000_001, clockId = 5) assertTrue(later > earlier) assertNotEquals(earlier, later) assertTrue(Tid.isValid(earlier)) } @Test fun `encode never sets the high bit`() { // A leading character above 'j' would mean the 64-bit value's top bit // is set, which the TID spec forbids. val extreme = Tid.encode(micros = 0x1FFFFFFFFFFFFF, clockId = 0x3FF) assertTrue("Leading char '${extreme.first()}' out of range", Tid.isValid(extreme)) } @Test fun `isValid rejects malformed keys`() { assertTrue(!Tid.isValid("")) assertTrue(!Tid.isValid("short")) assertTrue(!Tid.isValid("zzzzzzzzzzzzz")) // leading char too high assertTrue(!Tid.isValid("3aaaaaaaaaaa1")) // '1' is not in the alphabet } }