Browse Source

android: test avatar event framing, limits and member updates

master
evgeny 6 days ago
parent
commit
15dbb6fe51
  1. 1
      tools/chatgui-android/app/build.gradle.kts
  2. 80
      tools/chatgui-android/app/src/test/java/com/utun/chat/data/AvatarProtocolTest.kt
  3. 39
      tools/chatgui-android/app/src/test/java/com/utun/chat/data/MemberAvatarTest.kt

1
tools/chatgui-android/app/build.gradle.kts

@ -78,6 +78,7 @@ android {
}
dependencies {
testImplementation("junit:junit:4.13.2")
implementation(platform("androidx.compose:compose-bom:2024.02.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")

80
tools/chatgui-android/app/src/test/java/com/utun/chat/data/AvatarProtocolTest.kt

@ -0,0 +1,80 @@
package com.utun.chat.data
import org.junit.Assert.*
import org.junit.Test
import java.nio.ByteBuffer
import java.nio.ByteOrder
class AvatarProtocolTest {
private val uid = 0xfedcba9876543210uL.toLong()
private fun buffer(size: Int) = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN)
private fun group(size: Int = 0): ByteArray = buffer(60 + size).apply {
putLong(71); putLong(uid); putLong(123)
put(ByteArray(32) { 0xa5.toByte() }); put(1); put(0); putShort(size.toShort())
put(ByteArray(size) { 0x7f })
}.array()
private fun response(): ByteArray = buffer(141).apply {
putLong(uid); putLong(23)
put(4); put(2); put(2); putShort(0)
putLong(71); putLong(uid); putLong(0); putLong(512)
put(16 + 117, 1)
}.array()
@Test fun unsignedNodeAndContentAddress() {
assertEquals(uid, AvatarProtocol.node("fedcba9876543210"))
assertEquals("fedcba9876543210", AvatarProtocol.hex(uid))
assertTrue(AvatarProtocol.validFile("a".repeat(64) + ".png"))
listOf("A".repeat(64) + ".png", "../" + "a".repeat(64) + ".png", "a.png", "a".repeat(64) + ".jpg")
.forEach { assertFalse(it, AvatarProtocol.validFile(it)) }
assertEquals(0L, AvatarProtocol.node("invalid"))
}
@Test fun groupLimitAndSignedDeletion() {
val record = AvatarProtocol.group(group(16384))
assertEquals(71L, record.id); assertEquals(uid, record.group); assertEquals(123L, record.version)
assertEquals("a5".repeat(32), record.hash); assertTrue(record.canEdit); assertFalse(record.failed)
assertEquals(16384, record.image.size)
assertEquals(0, AvatarProtocol.group(group()).image.size)
}
@Test fun rejectTruncatedOversizedAndMalformedGroup() {
assertThrows(IllegalArgumentException::class.java) { AvatarProtocol.group(group(1).copyOf(60)) }
assertThrows(IllegalArgumentException::class.java) { AvatarProtocol.group(group(16385)) }
assertThrows(IllegalArgumentException::class.java) { AvatarProtocol.group(group().also { it[56] = 2 }) }
assertThrows(IllegalArgumentException::class.java) { AvatarProtocol.group(group().also { it[57] = -1 }) }
}
@Test fun statHasSizeAndTypeWithoutAContentHash() {
// STAT общего сервера возвращает метаданные; хеш присутствует только после COMMIT/stream-open.
val reply = AvatarProtocol.response(response())
assertEquals(uid, reply.group); assertEquals(23L, reply.server); assertEquals(uid, reply.owner)
assertEquals(71L, reply.id); assertEquals(2, reply.command); assertEquals(0, reply.status)
assertEquals(512L, reply.size); assertEquals(1, reply.fileType); assertEquals("0".repeat(64), reply.hash)
}
@Test fun rejectInvalidFileFraming() {
assertThrows(IllegalArgumentException::class.java) { AvatarProtocol.response(response().copyOf(140)) }
assertThrows(IllegalArgumentException::class.java) { AvatarProtocol.response(response().also { it[16] = 3 }) }
assertThrows(IllegalArgumentException::class.java) { AvatarProtocol.response(response().also { it[17] = 1 }) }
assertThrows(IllegalArgumentException::class.java) { AvatarProtocol.response(response().also { it[135] = 1 }) }
val path = response().copyOf(144).also { it[135] = 3; "abc".toByteArray().copyInto(it, 141) }
assertEquals(71L, AvatarProtocol.response(path).id)
}
@Test fun progressMustFinishBeforeResolvingARequest() {
val progress = buffer(51).apply {
putLong(71); putLong(uid); putLong(23); putLong(uid)
putLong(0); putLong(512); putShort(0); put(1)
}.array()
assertNull(AvatarProtocol.transfer(progress))
progress[50] = 2
assertEquals(71L, AvatarProtocol.transfer(progress)?.id)
progress[50] = 3
assertThrows(IllegalArgumentException::class.java) { AvatarProtocol.transfer(progress) }
progress[50] = 2
ByteBuffer.wrap(progress).order(ByteOrder.LITTLE_ENDIAN).putLong(32, 513)
assertThrows(IllegalArgumentException::class.java) { AvatarProtocol.transfer(progress) }
}
@Test fun failedPublicationRemainsAnError() {
val result = buffer(17).apply { putLong(71); putLong(uid); put(1) }.array()
assertEquals(1, AvatarProtocol.published(result).status)
assertEquals(uid, AvatarProtocol.published(result).group)
assertThrows(IllegalArgumentException::class.java) { AvatarProtocol.published(result.copyOf(16)) }
assertThrows(IllegalArgumentException::class.java) { AvatarProtocol.published(result.also { it[16] = 2 }) }
}
}

39
tools/chatgui-android/app/src/test/java/com/utun/chat/data/MemberAvatarTest.kt

@ -0,0 +1,39 @@
package com.utun.chat.data
import org.junit.Assert.*
import org.junit.Test
import java.nio.ByteBuffer
import java.nio.ByteOrder
class MemberAvatarTest {
@Test fun sharedSnapshotKeepsAvatarAndSignedStorageRole() {
val bytes = ByteArray(157)
val b = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
b.putLong(0, 123); bytes[8] = 1; bytes[10] = 2
"Alice".toByteArray().copyInto(bytes, 15)
val server = 0xfedcba9876543210uL.toLong()
b.putLong(79, server)
val file = "b".repeat(64) + ".png"
file.toByteArray().copyInto(bytes, 87); bytes[156] = 1
val previous = ChatMember(123, "Old", isVerified = true, nodeType = 3, directUp = true, bgpNodes = 7)
val member = requireNotNull(parseMemberWire(bytes)).toChatMember(previous)
assertEquals("Alice", member.name); assertTrue(member.online); assertTrue(member.isAdmin)
assertEquals(server, member.avatarServer); assertEquals(file, member.avatarFile); assertTrue(member.storageRole)
assertTrue(member.isVerified); assertEquals(3, member.nodeType); assertTrue(member.directUp); assertEquals(7, member.bgpNodes)
// Сеть передаёт storage priority отдельно от подписанной роли хранителя.
assertFalse(member.isStorage)
}
@Test fun clearingReferenceDoesNotKeepAnOldAvatar() {
val previous = ChatMember(123, "Alice", avatarServer = 456, avatarFile = "b".repeat(64) + ".png", storageRole = true)
val updated = MemberWire(123, 1, true, 0, 0, 0, false, "Alice").toChatMember(previous)
assertEquals(0L, updated.avatarServer); assertEquals("", updated.avatarFile); assertFalse(updated.storageRole)
assertTrue(updated.isSelf)
}
@Test fun recordsAtAnOffsetRequireAll157Bytes() {
val bytes = ByteArray(160)
ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).putLong(3, 987)
assertEquals(987L, parseMemberWire(bytes, 3)?.nodeId)
assertNull(parseMemberWire(bytes, 4)); assertNull(parseMemberWire(bytes, -1))
assertNull(parseMemberWire(bytes.copyOf(156)))
}
}
Loading…
Cancel
Save