fix: resolve release/2.8.0 branch-review findings (car hosts, AI node IDs, discovery abort, AQ zeros) (#5813)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
James Rich
2026-06-16 15:31:58 -05:00
parent bfe3440a11
commit 8874352ba4
10 changed files with 289 additions and 45 deletions

View File

@@ -130,7 +130,7 @@ class AiFunctionProviderImpl(
val nodes =
nodeMap.values.map { node ->
NodeSummary(
id = "!${node.num.toString(HEX_RADIX)}",
id = NodeAddress.numToDefaultId(node.num),
name = node.user.long_name.takeIf { it.isNotBlank() } ?: "Node ${node.num}",
batteryLevel = node.deviceMetrics.battery_level?.coerceIn(0, MAX_BATTERY_LEVEL),
lastHeard = node.lastHeard.toLong() * MS_PER_SEC,
@@ -201,8 +201,11 @@ class AiFunctionProviderImpl(
try {
val node =
if (nodeId.startsWith("!")) {
// Hex format: extract number and search
val nodeNum = nodeId.drop(1).toInt(HEX_RADIX)
// Canonical hex node ID (e.g. "!ffffffff"). idToNum parses the full unsigned 32-bit
// range and returns null for malformed input, which we surface as NotFound.
val nodeNum =
NodeAddress.idToNum(nodeId)
?: return@withTimeout GetNodeDetailsResult.NotFound("Node not found: $nodeId")
nodeRepository.nodeDBbyNum.first()[nodeNum]
} else {
// User ID format
@@ -218,7 +221,7 @@ class AiFunctionProviderImpl(
val details =
NodeDetails(
id = "!${node.num.toString(HEX_RADIX)}",
id = NodeAddress.numToDefaultId(node.num),
userId = node.user.id,
name = node.user.long_name.takeIf { it.isNotBlank() } ?: "Node ${node.num}",
batteryLevel = node.deviceMetrics.battery_level?.coerceIn(0, MAX_BATTERY_LEVEL),
@@ -502,7 +505,6 @@ class AiFunctionProviderImpl(
companion object {
private val OPERATION_TIMEOUT = 5.seconds
private const val MAX_BATTERY_LEVEL = 100
private const val HEX_RADIX = 16
private const val MS_PER_SEC = 1000L
private const val HEALTH_SCORE_BASE = 50
private const val HEALTH_SCORE_ONLINE_RATIO = 50

View File

@@ -137,6 +137,22 @@ class AiFunctionProviderImplTest {
assertEquals(true, isHandled)
}
@Test
fun getNodeDetails_round_trips_high_bit_node_num() = runTest {
// A node num with the high bit set (-1 == 0xFFFFFFFF) must format and parse as the canonical
// "!ffffffff", not the signed "!-1" — regression guard for the node-ID hex fix.
val testNode = Node(num = -1, user = User(id = "!ffffffff", long_name = "HighBit", short_name = "HB"))
val nodeMap = MutableStateFlow(mapOf(-1 to testNode))
every { nodeRepository.nodeDBbyNum } returns nodeMap
val provider = createProvider()
val result = provider.getNodeDetails("!ffffffff")
assertIs<GetNodeDetailsResult.Success>(result)
assertEquals("!ffffffff", result.node.id)
assertEquals("HighBit", result.node.name)
}
// --- getMeshMetrics tests ---
@Test