Refactor command handling, enhance tests, and improve discovery logic (#4878)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich
2026-03-22 00:42:27 -05:00
committed by GitHub
parent d136b162a4
commit c38bfc64de
76 changed files with 2220 additions and 1277 deletions

View File

@@ -20,6 +20,7 @@ import kotlinx.serialization.json.Json
import org.meshtastic.core.model.MqttJsonPayload
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class MQTTRepositoryImplTest {
@@ -67,8 +68,8 @@ class MQTTRepositoryImplTest {
val json = Json { ignoreUnknownKeys = true }
val jsonStr = json.encodeToString(MqttJsonPayload.serializer(), payload)
assert(jsonStr.contains("\"type\":\"text\""))
assert(jsonStr.contains("\"from\":12345678"))
assert(jsonStr.contains("\"payload\":\"Hello World\""))
assertTrue(jsonStr.contains("\"type\":\"text\""))
assertTrue(jsonStr.contains("\"from\":12345678"))
assertTrue(jsonStr.contains("\"payload\":\"Hello World\""))
}
}

View File

@@ -25,6 +25,7 @@ import kotlinx.coroutines.flow.flowOn
import org.koin.core.annotation.Single
import java.io.IOException
import java.net.InetAddress
import java.net.NetworkInterface
import javax.jmdns.JmDNS
import javax.jmdns.ServiceEvent
import javax.jmdns.ServiceListener
@@ -34,9 +35,14 @@ class JvmServiceDiscovery : ServiceDiscovery {
@Suppress("TooGenericExceptionCaught")
override val resolvedServices: Flow<List<DiscoveredService>> =
callbackFlow {
trySend(emptyList()) // Emit initial empty list so downstream combine() is not blocked
val bindAddress = findLanAddress() ?: InetAddress.getLocalHost()
Logger.i { "JmDNS binding to ${bindAddress.hostAddress}" }
val jmdns =
try {
JmDNS.create(InetAddress.getLocalHost())
JmDNS.create(bindAddress)
} catch (e: IOException) {
Logger.e(e) { "Failed to create JmDNS" }
null
@@ -93,4 +99,24 @@ class JvmServiceDiscovery : ServiceDiscovery {
}
}
.flowOn(Dispatchers.IO)
companion object {
/**
* Finds a non-loopback, up, IPv4 LAN address for JmDNS to bind to. On many systems (especially Windows),
* [InetAddress.getLocalHost] resolves to `127.0.0.1` or `::1`, which prevents JmDNS from seeing multicast
* traffic on the actual LAN interface.
*/
@Suppress("TooGenericExceptionCaught", "LoopWithTooManyJumpStatements")
internal fun findLanAddress(): InetAddress? = try {
NetworkInterface.getNetworkInterfaces()
?.toList()
.orEmpty()
.filter { it.isUp && !it.isLoopback }
.flatMap { it.inetAddresses.toList() }
.firstOrNull { !it.isLoopbackAddress && it is java.net.Inet4Address }
} catch (e: Exception) {
Logger.w(e) { "Failed to enumerate network interfaces, falling back to getLocalHost()" }
null
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.network.repository
import app.cash.turbine.test
import kotlinx.coroutines.test.runTest
import org.junit.Test
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class JvmServiceDiscoveryTest {
@Test
fun `resolvedServices emits initial empty list immediately`() = runTest {
val discovery = JvmServiceDiscovery()
discovery.resolvedServices.test {
val first = awaitItem()
assertNotNull(first, "First emission should not be null")
assertTrue(first.isEmpty(), "First emission should be an empty list")
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `findLanAddress returns non-loopback address or null`() {
val address = JvmServiceDiscovery.findLanAddress()
// On CI machines there may be no LAN interface, so null is acceptable
if (address != null) {
assertTrue(!address.isLoopbackAddress, "Address should not be loopback")
assertTrue(address is java.net.Inet4Address, "Address should be IPv4")
}
}
@Test
fun `findLanAddress does not throw`() {
// Ensure the method handles exceptions gracefully
val result = runCatching { JvmServiceDiscovery.findLanAddress() }
assertTrue(result.isSuccess, "findLanAddress should not throw: ${result.exceptionOrNull()}")
}
}