3 changed files with 120 additions and 0 deletions
@ -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 }) } |
||||
} |
||||
} |
||||
@ -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…
Reference in new issue