Test atomic JSON snapshots, failure diagnostics and subsequent recovery
This commit is contained in:
1 parent
3eb7f8b0e6
commit
a0c8d5490c
2 files changed
+46
-10
No files matched your search
@@ -214,7 +214,7 @@ fun cleanSocketName(raw: String): String =
|
||||
raw.removePrefix("as_").replace(Regex("_v[46]_(tcp|udp)$"), " $1")
|
||||
|
||||
/** Stateless JNI/JSON adapter. Снимки применяются целиком: null означает отказ, пустой список — корректный пустой снимок. */
|
||||
class ChatRepository {
|
||||
class ChatRepository(private val reportFailure: (String) -> Unit = { LogManager.addLog("ERROR", "Repository", it) }) {
|
||||
fun getChannels(): List<Channel>? {
|
||||
val list = mutableListOf<Channel>()
|
||||
try {
|
||||
@@ -231,7 +231,7 @@ class ChatRepository {
|
||||
))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "Repository", "Channel snapshot failed: $e"); return null
|
||||
reportFailure("Channel snapshot failed: $e"); return null
|
||||
}
|
||||
return list
|
||||
}
|
||||
@@ -243,7 +243,7 @@ class ChatRepository {
|
||||
val obj = arr.getJSONObject(i)
|
||||
if (obj.getString("id") == channelId) return obj.optInt("peersOnline", 0)
|
||||
}
|
||||
} catch (e: Exception) { LogManager.addLog("ERROR", "Repository", "Peer count failed channel=$channelId: $e") }
|
||||
} catch (e: Exception) { reportFailure("Peer count failed channel=$channelId: $e") }
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -369,7 +369,7 @@ class ChatRepository {
|
||||
))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "Repository", "Message snapshot failed channel=$channelId anchor=$anchor: $e"); return null
|
||||
reportFailure("Message snapshot failed channel=$channelId anchor=$anchor: $e"); return null
|
||||
}
|
||||
return list
|
||||
}
|
||||
@@ -419,7 +419,7 @@ class ChatRepository {
|
||||
))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "Repository", "Member snapshot failed channel=$channelId: $e"); return emptyList()
|
||||
reportFailure("Member snapshot failed channel=$channelId: $e"); return emptyList()
|
||||
}
|
||||
return list
|
||||
}
|
||||
@@ -457,7 +457,7 @@ class ChatRepository {
|
||||
inviterId = if (obj.isNull("inviter_id")) null else obj.getLong("inviter_id"),
|
||||
inviterName = obj.optString("inviter_name", "")
|
||||
)
|
||||
} catch (e: Exception) { LogManager.addLog("ERROR", "Repository", "Member detail failed channel=$channelId node=$nodeId: $e") }
|
||||
} catch (e: Exception) { reportFailure("Member detail failed channel=$channelId node=$nodeId: $e") }
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -505,7 +505,7 @@ class ChatRepository {
|
||||
linksUp = obj.optInt("lu", 0),
|
||||
links = links
|
||||
)
|
||||
} catch (e: Exception) { LogManager.addLog("ERROR", "Repository", "Member links failed node=$nodeId: $e") }
|
||||
} catch (e: Exception) { reportFailure("Member links failed node=$nodeId: $e") }
|
||||
return MemberLinks()
|
||||
}
|
||||
|
||||
@@ -535,7 +535,7 @@ class ChatRepository {
|
||||
}
|
||||
list.sortWith(compareBy<LocalSocketState> { it.ifName }.thenBy { it.bindIp }.thenBy { it.protocol }.thenBy { it.port })
|
||||
return list
|
||||
} catch (e: Exception) { LogManager.addLog("ERROR", "Repository", "Socket snapshot parse failed: $e") }
|
||||
} catch (e: Exception) { reportFailure("Socket snapshot parse failed: $e") }
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -563,7 +563,7 @@ class ChatRepository {
|
||||
))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "Repository", "PM conversation snapshot failed: $e"); return null
|
||||
reportFailure("PM conversation snapshot failed: $e"); return null
|
||||
}
|
||||
return list
|
||||
}
|
||||
@@ -612,7 +612,7 @@ class ChatRepository {
|
||||
))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "Repository", "PM message snapshot failed: $e"); return null
|
||||
reportFailure("PM message snapshot failed: $e"); return null
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.utun.chat.data
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
/** Повреждение второй строки не должно публиковать первый элемент как полный снимок. */
|
||||
class ChatRepositorySnapshotTest {
|
||||
private val errors = mutableListOf<String>()
|
||||
private val repo = ChatRepository { errors.add(it) }
|
||||
|
||||
@Test fun conversationParseFailureDiffersFromAValidEmptySnapshotAndAllowsRetry() {
|
||||
val row = """{"conv_id":"123","peer_node_id":4,"peer_name":"Узел 😀","generation":2}"""
|
||||
assertNull(repo.parseDmConversations("[$row,{}]"))
|
||||
assertTrue(errors.single().contains("PM conversation snapshot failed"))
|
||||
assertEquals(emptyList<DmConversation>(), repo.parseDmConversations("[]"))
|
||||
val restored = repo.parseDmConversations("[$row]")!!.single()
|
||||
assertEquals("Узел 😀", restored.peerName); assertEquals(2L, restored.generation)
|
||||
}
|
||||
|
||||
@Test fun invalidInviteCannotPublishAPartialMessageHistory() {
|
||||
val row = """{"seq":1,"author":"4","data":"Текст 😀","ts":50}"""
|
||||
assertNull(repo.parseDmMessages("[$row,{\"ct\":\"invite\",\"tags\":{}}]"))
|
||||
assertTrue(errors.single().contains("PM message snapshot failed"))
|
||||
assertEquals(emptyList<Message>(), repo.parseDmMessages("[]"))
|
||||
assertEquals("Текст 😀", repo.parseDmMessages("[$row]")!!.single().text)
|
||||
}
|
||||
|
||||
@Test fun missingRequiredNatFieldsDoNotClearOrTruncateTheSocketSnapshot() {
|
||||
val row = """{"si":1,"nm":"as_eth_v4_udp","nmapping":1,"ninbound":1,"nsource":1,"external":"","externalPort":0}"""
|
||||
val before = repo.parseLocalSockets("[$row]")!!
|
||||
assertNull(repo.parseLocalSockets("[$row,{}]"))
|
||||
assertTrue(errors.single().contains("Socket snapshot parse failed"))
|
||||
assertEquals(emptyList<LocalSocketState>(), repo.parseLocalSockets("[]"))
|
||||
assertEquals(before, repo.parseLocalSockets("[$row]"))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user