Test Android file wire formats and nonempty directory status

This commit is contained in:
evgeny committed 2026-10-10 01:27:34 +02:00
1 parent 11b6f78924
commit 468aeeebc7
2 files changed
+86 -1

No files matched your search

@@ -0,0 +1,79 @@
package com.utun.chat.data
import org.junit.Assert.*
import org.junit.Test
import java.nio.ByteBuffer
import java.nio.ByteOrder
class MemberFilesProtocolTest {
private val uid = 0xfedcba9876543210uL.toLong()
private fun buffer(size: Int) = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN)
private fun reply(path: String = "", status: Int = 0): ByteArray {
val wire = MemberFilesProtocol.request(71, uid, MemberFilesProtocol.LIST, path)
wire[1] = 2; wire[3] = status.toByte()
return buffer(16 + wire.size).putLong(uid).putLong(23).put(wire).array()
}
private fun entry(name: String, type: Int = 1, size: Long = 400): ByteArray {
val bytes = name.toByteArray()
return buffer(35 + bytes.size).putShort(bytes.size.toShort()).put(type.toByte()).putLong(size).putLong(123)
.put(ByteArray(16) { 7 }).put(bytes).array()
}
@Test fun unicodeNamesAndUnsignedIdentityRoundTrip() {
val r = MemberFilesProtocol.response(reply("Документы/😀.pdf"))
assertEquals(uid, r.group); assertEquals(uid, r.owner); assertEquals(23L, r.server)
assertEquals(71L, r.id); assertEquals("Документы/😀.pdf", r.path)
val e = MemberFilesProtocol.entries(entry("😀.pdf")).single()
assertEquals("😀.pdf", e.name); assertEquals(400L, e.size); assertEquals(123L, e.mtime)
assertArrayEquals(ByteArray(16) { 7 }, e.version)
}
@Test fun portablePathsAndUtf8ByteLimits() {
listOf("", "photos/夏.jpg", "Документы/😀.pdf", "a".repeat(255)).forEach { assertTrue(it, MemberFilesProtocol.validPath(it)) }
listOf("../file", "a//b", "/a", "a/", ".hidden", "a\\b", "CON.txt", "com1", "a.", "a ", "a\u0000b",
"a".repeat(256), "я".repeat(128), (1..61).joinToString("/") { "a" }, "bad\uD800")
.forEach { assertFalse(it, MemberFilesProtocol.validPath(it)) }
assertFalse(MemberFilesProtocol.validPath("", false))
assertTrue(MemberFilesProtocol.servicePath("Avatars/file")); assertFalse(MemberFilesProtocol.servicePath("photos/avatars"))
assertThrows(IllegalArgumentException::class.java) { MemberFilesProtocol.child("docs", "a/b") }
}
@Test fun paginationAndMutationKeepTheSnapshotToken() {
val token = ByteArray(16) { it.toByte() }
val wire = MemberFilesProtocol.request(71, uid, MemberFilesProtocol.RENAME, "docs/a", "docs/b", version = token)
assertArrayEquals(token, wire.copyOfRange(45, 61))
wire[1] = 2
val r = MemberFilesProtocol.response(buffer(16 + wire.size).putLong(uid).putLong(23).put(wire).array())
assertEquals("docs/a", r.path); assertArrayEquals(token, r.version)
val page = MemberFilesProtocol.request(72, uid, MemberFilesProtocol.LIST, "docs", cursor = 82, version = token)
assertEquals(82L, ByteBuffer.wrap(page).order(ByteOrder.LITTLE_ENDIAN).getLong(37))
assertThrows(IllegalArgumentException::class.java) {
MemberFilesProtocol.request(71, uid, MemberFilesProtocol.REMOVE, "")
}
}
@Test fun malformedDirectoryCannotBecomeASuccessfulPartialList() {
val e = entry("file")
assertThrows(IllegalArgumentException::class.java) { MemberFilesProtocol.entries(e + e) }
assertThrows(IllegalArgumentException::class.java) { MemberFilesProtocol.entries(e.copyOf(e.size - 1)) }
assertThrows(IllegalArgumentException::class.java) { MemberFilesProtocol.entries(entry("../x")) }
assertThrows(IllegalArgumentException::class.java) { MemberFilesProtocol.entries(entry("x", 3)) }
assertThrows(IllegalArgumentException::class.java) { MemberFilesProtocol.entries(entry("x", size = -1)) }
assertThrows(Exception::class.java) { MemberFilesProtocol.entries(entry("x").also { it[35] = 0xff.toByte() }) }
}
@Test fun errorsAndMalformedFramingArePreserved() {
assertEquals(14, MemberFilesProtocol.response(reply(status = 14)).status)
assertThrows(IllegalArgumentException::class.java) { MemberFilesProtocol.response(reply(status = 15)) }
assertThrows(IllegalArgumentException::class.java) { MemberFilesProtocol.response(reply().copyOf(140)) }
assertThrows(IllegalArgumentException::class.java) { MemberFilesProtocol.response(reply().also { it[134] = 2 }) }
assertThrows(IllegalArgumentException::class.java) { MemberFilesProtocol.response(reply("abc").also { it[141] = 0 }) }
}
@Test fun quotaAndProgressValidate64BitValues() {
val q = MemberFilesProtocol.quota(buffer(40).putLong(12).putLong(1000).putLong(34).putLong(5).putLong(6).array())
assertEquals(34L, q.reserved); assertEquals(6L, q.directories)
assertThrows(IllegalArgumentException::class.java) { MemberFilesProtocol.quota(ByteArray(40)) }
val wire = buffer(51).putLong(71).putLong(uid).putLong(23).putLong(uid).putLong(20).putLong(40).putShort(0).put(1).array()
val progress = MemberFilesProtocol.progress(wire)
assertFalse(progress.finished); assertEquals(uid, progress.owner); assertEquals(20L, progress.done)
wire[50] = 2; wire[48] = 12
assertTrue(MemberFilesProtocol.progress(wire).finished); assertEquals(12, MemberFilesProtocol.progress(wire).status)
ByteBuffer.wrap(wire).order(ByteOrder.LITTLE_ENDIAN).putLong(32, 41)
assertThrows(IllegalArgumentException::class.java) { MemberFilesProtocol.progress(wire) }
}
}