Refactor map layer management and navigation infrastructure (#4921)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-03-25 19:29:24 -05:00 committed by GitHub
parent b608a04ca4
commit a005231d94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
142 changed files with 5408 additions and 3090 deletions

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 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.data.repository
import org.junit.runner.RunWith
import org.meshtastic.core.testing.setupTestContext
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.test.BeforeTest
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [34])
class MeshLogRepositoryTest : CommonMeshLogRepositoryTest() {
@BeforeTest
fun setup() {
setupTestContext()
setupRepo()
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 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.data.repository
import org.junit.runner.RunWith
import org.meshtastic.core.testing.setupTestContext
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.test.BeforeTest
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [34])
class NodeRepositoryTest : CommonNodeRepositoryTest() {
@BeforeTest
fun setup() {
setupTestContext()
setupRepo()
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 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.data.repository
import org.junit.runner.RunWith
import org.meshtastic.core.testing.setupTestContext
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.test.BeforeTest
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [34])
class PacketRepositoryTest : CommonPacketRepositoryTest() {
@BeforeTest
fun setup() {
setupTestContext()
setupRepo()
}
}

View file

@ -29,13 +29,14 @@ import org.meshtastic.core.database.entity.FirmwareReleaseType
import org.meshtastic.core.database.entity.asExternalModel
import org.meshtastic.core.model.util.TimeConstants
import org.meshtastic.core.network.FirmwareReleaseRemoteDataSource
import org.meshtastic.core.repository.FirmwareReleaseRepository
@Single
class FirmwareReleaseRepository(
open class FirmwareReleaseRepositoryImpl(
private val remoteDataSource: FirmwareReleaseRemoteDataSource,
private val localDataSource: FirmwareReleaseLocalDataSource,
private val jsonDataSource: FirmwareReleaseJsonDataSource,
) {
) : FirmwareReleaseRepository {
/**
* A flow that provides the latest STABLE firmware release. It follows a "cache-then-network" strategy:
@ -44,14 +45,14 @@ class FirmwareReleaseRepository(
* 3. Emits the updated version upon successful fetch. Collectors should use `.distinctUntilChanged()` to avoid
* redundant UI updates.
*/
val stableRelease: Flow<FirmwareRelease?> = getLatestFirmware(FirmwareReleaseType.STABLE)
override val stableRelease: Flow<FirmwareRelease?> = getLatestFirmware(FirmwareReleaseType.STABLE)
/**
* A flow that provides the latest ALPHA firmware release.
*
* @see stableRelease for behavior details.
*/
val alphaRelease: Flow<FirmwareRelease?> = getLatestFirmware(FirmwareReleaseType.ALPHA)
override val alphaRelease: Flow<FirmwareRelease?> = getLatestFirmware(FirmwareReleaseType.ALPHA)
private fun getLatestFirmware(
releaseType: FirmwareReleaseType,
@ -118,7 +119,7 @@ class FirmwareReleaseRepository(
}
}
suspend fun invalidateCache() {
override suspend fun invalidateCache() {
localDataSource.deleteAllFirmwareReleases()
}

View file

@ -0,0 +1,147 @@
/*
* 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.data.repository
import dev.mokkery.MockMode
import dev.mokkery.answering.returns
import dev.mokkery.every
import dev.mokkery.mock
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import okio.ByteString.Companion.toByteString
import org.meshtastic.core.data.datasource.NodeInfoReadDataSource
import org.meshtastic.core.database.entity.MyNodeEntity
import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.model.MeshLog
import org.meshtastic.core.testing.FakeDatabaseProvider
import org.meshtastic.core.testing.FakeMeshLogPrefs
import org.meshtastic.proto.Data
import org.meshtastic.proto.EnvironmentMetrics
import org.meshtastic.proto.FromRadio
import org.meshtastic.proto.MeshPacket
import org.meshtastic.proto.PortNum
import org.meshtastic.proto.Telemetry
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
abstract class CommonMeshLogRepositoryTest {
protected lateinit var dbProvider: FakeDatabaseProvider
protected lateinit var meshLogPrefs: FakeMeshLogPrefs
protected lateinit var nodeInfoReadDataSource: NodeInfoReadDataSource
private val testDispatcher = UnconfinedTestDispatcher()
private val dispatchers = CoroutineDispatchers(main = testDispatcher, io = testDispatcher, default = testDispatcher)
protected lateinit var repository: MeshLogRepositoryImpl
private val nowMillis = 1000000000L
fun setupRepo() {
dbProvider = FakeDatabaseProvider()
meshLogPrefs = FakeMeshLogPrefs()
meshLogPrefs.setLoggingEnabled(true)
nodeInfoReadDataSource = mock(MockMode.autofill)
every { nodeInfoReadDataSource.myNodeInfoFlow() } returns MutableStateFlow(null)
repository = MeshLogRepositoryImpl(dbProvider, dispatchers, meshLogPrefs, nodeInfoReadDataSource)
}
@AfterTest
fun tearDown() {
dbProvider.close()
}
@Test
fun `parseTelemetryLog preserves zero temperature`() = runTest(testDispatcher) {
val zeroTemp = 0.0f
val telemetry = Telemetry(environment_metrics = EnvironmentMetrics(temperature = zeroTemp))
val meshPacket =
MeshPacket(decoded = Data(payload = telemetry.encode().toByteString(), portnum = PortNum.TELEMETRY_APP))
val meshLog =
MeshLog(
uuid = "123",
message_type = "telemetry",
received_date = nowMillis,
raw_message = "",
fromNum = 0,
portNum = PortNum.TELEMETRY_APP.value,
fromRadio = FromRadio(packet = meshPacket),
)
repository.insert(meshLog)
val result = repository.getTelemetryFrom(0).first()
assertNotNull(result)
assertEquals(1, result.size)
val resultMetrics = result[0].environment_metrics
assertNotNull(resultMetrics)
assertEquals(zeroTemp, resultMetrics.temperature ?: 0f, 0.01f)
}
@Test
fun `deleteLogs redirects local node number to NODE_NUM_LOCAL`() = runTest(testDispatcher) {
val localNodeNum = 999
val port = PortNum.TEXT_MESSAGE_APP.value
val myNodeEntity =
MyNodeEntity(
myNodeNum = localNodeNum,
model = "model",
firmwareVersion = "1.0",
couldUpdate = false,
shouldUpdate = false,
currentPacketId = 0L,
messageTimeoutMsec = 0,
minAppVersion = 0,
maxChannels = 0,
hasWifi = false,
)
every { nodeInfoReadDataSource.myNodeInfoFlow() } returns MutableStateFlow(myNodeEntity)
val log =
MeshLog(
uuid = "123",
message_type = "TEXT",
received_date = nowMillis,
raw_message = "",
fromNum =
0, // asEntity will map it if we pass localNodeNum to asEntity, but here we set it manually
portNum = port,
fromRadio =
FromRadio(
packet = MeshPacket(from = localNodeNum, decoded = Data(portnum = PortNum.TEXT_MESSAGE_APP)),
),
)
repository.insert(log)
// Verify it's there
assertEquals(1, repository.getAllLogsUnbounded().first().size)
repository.deleteLogs(localNodeNum, port)
val logs = repository.getAllLogsUnbounded().first()
assertTrue(logs.isEmpty())
}
}

View file

@ -0,0 +1,123 @@
/*
* 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.data.repository
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import dev.mokkery.MockMode
import dev.mokkery.answering.returns
import dev.mokkery.every
import dev.mokkery.mock
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.meshtastic.core.data.datasource.NodeInfoReadDataSource
import org.meshtastic.core.data.datasource.NodeInfoWriteDataSource
import org.meshtastic.core.database.entity.MyNodeEntity
import org.meshtastic.core.database.entity.NodeWithRelations
import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.model.MeshLog
import org.meshtastic.core.testing.FakeLocalStatsDataSource
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertEquals
abstract class CommonNodeRepositoryTest {
protected lateinit var lifecycleOwner: LifecycleOwner
protected lateinit var readDataSource: NodeInfoReadDataSource
protected lateinit var writeDataSource: NodeInfoWriteDataSource
protected lateinit var localStatsDataSource: FakeLocalStatsDataSource
private val testDispatcher = UnconfinedTestDispatcher()
private val dispatchers = CoroutineDispatchers(main = testDispatcher, io = testDispatcher, default = testDispatcher)
private val myNodeInfoFlow = MutableStateFlow<MyNodeEntity?>(null)
protected lateinit var repository: NodeRepositoryImpl
fun setupRepo() {
Dispatchers.setMain(testDispatcher)
lifecycleOwner =
object : LifecycleOwner {
override val lifecycle = LifecycleRegistry(this)
}
(lifecycleOwner.lifecycle as LifecycleRegistry).handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
readDataSource = mock(MockMode.autofill)
writeDataSource = mock(MockMode.autofill)
localStatsDataSource = FakeLocalStatsDataSource()
every { readDataSource.myNodeInfoFlow() } returns myNodeInfoFlow
every { readDataSource.nodeDBbyNumFlow() } returns MutableStateFlow<Map<Int, NodeWithRelations>>(emptyMap())
repository =
NodeRepositoryImpl(
lifecycleOwner.lifecycle,
readDataSource,
writeDataSource,
dispatchers,
localStatsDataSource,
)
}
@AfterTest
fun tearDown() {
// Essential to stop background jobs in NodeRepositoryImpl
(lifecycleOwner.lifecycle as LifecycleRegistry).handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
Dispatchers.resetMain()
}
private fun createMyNodeEntity(nodeNum: Int) = MyNodeEntity(
myNodeNum = nodeNum,
model = "model",
firmwareVersion = "1.0",
couldUpdate = false,
shouldUpdate = false,
currentPacketId = 0L,
messageTimeoutMsec = 0,
minAppVersion = 0,
maxChannels = 0,
hasWifi = false,
)
@Test
fun `effectiveLogNodeId maps local node number to NODE_NUM_LOCAL`() = runTest(testDispatcher) {
val myNodeNum = 12345
myNodeInfoFlow.value = createMyNodeEntity(myNodeNum)
val result = repository.effectiveLogNodeId(myNodeNum).filter { it == MeshLog.NODE_NUM_LOCAL }.first()
assertEquals(MeshLog.NODE_NUM_LOCAL, result)
}
@Test
fun `effectiveLogNodeId preserves remote node numbers`() = runTest(testDispatcher) {
val myNodeNum = 12345
val remoteNodeNum = 67890
myNodeInfoFlow.value = createMyNodeEntity(myNodeNum)
val result = repository.effectiveLogNodeId(remoteNodeNum).first()
assertEquals(remoteNodeNum, result)
}
}

View file

@ -0,0 +1,84 @@
/*
* Copyright (c) 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.data.repository
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.meshtastic.core.database.entity.MyNodeEntity
import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.testing.FakeDatabaseProvider
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertEquals
abstract class CommonPacketRepositoryTest {
protected lateinit var dbProvider: FakeDatabaseProvider
private val testDispatcher = UnconfinedTestDispatcher()
private val dispatchers = CoroutineDispatchers(main = testDispatcher, io = testDispatcher, default = testDispatcher)
protected lateinit var repository: PacketRepositoryImpl
fun setupRepo() {
dbProvider = FakeDatabaseProvider()
repository = PacketRepositoryImpl(dbProvider, dispatchers)
}
@AfterTest
fun tearDown() {
dbProvider.close()
}
@Test
fun `savePacket persists and retrieves waypoints`() = runTest(testDispatcher) {
val myNodeNum = 1
val contact = "contact"
// Ensure my_node is present so getMessageCount finds the packet
dbProvider.currentDb.value
.nodeInfoDao()
.setMyNodeInfo(
MyNodeEntity(
myNodeNum = myNodeNum,
model = "model",
firmwareVersion = "1.0",
couldUpdate = false,
shouldUpdate = false,
currentPacketId = 0L,
messageTimeoutMsec = 0,
minAppVersion = 0,
maxChannels = 0,
hasWifi = false,
),
)
val packet = DataPacket(to = "0!ffffffff", bytes = okio.ByteString.EMPTY, dataType = 1, id = 123)
repository.savePacket(myNodeNum, contact, packet, 1000L)
// Verify it was saved.
val count = repository.getMessageCount(contact)
assertEquals(1, count)
}
@Test
fun `clearAllUnreadCounts works with real DB`() = runTest(testDispatcher) {
repository.clearAllUnreadCounts()
// No exception thrown
}
}

View file

@ -1,195 +0,0 @@
/*
* 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.data.repository
class MeshLogRepositoryTest {
/*
private val dbManager: DatabaseProvider = mock()
private val appDatabase: MeshtasticDatabase = mock()
private val meshLogDao: MeshLogDao = mock()
private val meshLogPrefs: MeshLogPrefs = mock()
private val nodeInfoReadDataSource: NodeInfoReadDataSource = mock()
private val testDispatcher = UnconfinedTestDispatcher()
private val dispatchers = CoroutineDispatchers(main = testDispatcher, io = testDispatcher, default = testDispatcher)
private val repository = MeshLogRepositoryImpl(dbManager, dispatchers, meshLogPrefs, nodeInfoReadDataSource)
init {
every { dbManager.currentDb } returns MutableStateFlow(appDatabase)
every { appDatabase.meshLogDao() } returns meshLogDao
every { nodeInfoReadDataSource.myNodeInfoFlow() } returns MutableStateFlow(null)
}
@Test
fun `parseTelemetryLog preserves zero temperature`() = runTest(testDispatcher) {
val zeroTemp = 0.0f
val telemetry = Telemetry(environment_metrics = EnvironmentMetrics(temperature = zeroTemp))
val meshPacket =
MeshPacket(decoded = Data(payload = telemetry.encode().toByteString(), portnum = PortNum.TELEMETRY_APP))
val meshLog =
MeshLog(
uuid = Uuid.random().toString(),
message_type = "telemetry",
received_date = nowMillis,
raw_message = "",
fromRadio = FromRadio(packet = meshPacket),
)
// Using reflection to test private method parseTelemetryLog
val method = MeshLogRepositoryImpl::class.java.getDeclaredMethod("parseTelemetryLog", MeshLog::class.java)
method.isAccessible = true
val result = method.invoke(repository, meshLog) as Telemetry?
assertNotNull(result)
val resultMetrics = result?.environment_metrics
assertNotNull(resultMetrics)
assertEquals(zeroTemp, resultMetrics?.temperature ?: 0f, 0.01f)
}
@Test
fun `parseTelemetryLog maps missing temperature to NaN`() = runTest(testDispatcher) {
val telemetry = Telemetry(environment_metrics = EnvironmentMetrics(temperature = null))
val meshPacket =
MeshPacket(decoded = Data(payload = telemetry.encode().toByteString(), portnum = PortNum.TELEMETRY_APP))
val meshLog =
MeshLog(
uuid = Uuid.random().toString(),
message_type = "telemetry",
received_date = nowMillis,
raw_message = "",
fromRadio = FromRadio(packet = meshPacket),
)
val method = MeshLogRepositoryImpl::class.java.getDeclaredMethod("parseTelemetryLog", MeshLog::class.java)
method.isAccessible = true
val result = method.invoke(repository, meshLog) as Telemetry?
assertNotNull(result)
val resultMetrics = result?.environment_metrics
// Should be NaN as per repository logic for missing fields
assertEquals(Float.NaN, resultMetrics?.temperature ?: 0f, 0.01f)
}
@Test
fun `getRequestLogs filters correctly`() = runTest(testDispatcher) {
val targetNode = 123
val otherNode = 456
val port = PortNum.TRACEROUTE_APP
val logs =
listOf(
// Valid request
MeshLogEntity(
uuid = "1",
message_type = "Packet",
received_date = nowMillis,
raw_message = "",
fromNum = 0,
portNum = port.value,
fromRadio =
FromRadio(
packet =
MeshPacket(to = targetNode, decoded = Data(portnum = port, want_response = true)),
),
),
// Wrong target
MeshLogEntity(
uuid = "2",
message_type = "Packet",
received_date = nowMillis,
raw_message = "",
fromNum = 0,
portNum = port.value,
fromRadio =
FromRadio(
packet =
MeshPacket(to = otherNode, decoded = Data(portnum = port, want_response = true)),
),
),
// Not a request (want_response = false)
MeshLogEntity(
uuid = "3",
message_type = "Packet",
received_date = nowMillis,
raw_message = "",
fromNum = 0,
portNum = port.value,
fromRadio =
FromRadio(
packet =
MeshPacket(to = targetNode, decoded = Data(portnum = port, want_response = false)),
),
),
// Wrong fromNum
MeshLogEntity(
uuid = "4",
message_type = "Packet",
received_date = nowMillis,
raw_message = "",
fromNum = 789,
portNum = port.value,
fromRadio =
FromRadio(
packet =
MeshPacket(to = targetNode, decoded = Data(portnum = port, want_response = true)),
),
),
)
val result = repository.getRequestLogs(targetNode, port).first()
assertEquals(1, result.size)
assertEquals("1", result[0].uuid)
}
@Test
fun `deleteLogs redirects local node number to NODE_NUM_LOCAL`() = runTest(testDispatcher) {
val localNodeNum = 999
val port = 100
val myNodeEntity = mock<MyNodeEntity>()
every { myNodeEntity.myNodeNum } returns localNodeNum
every { nodeInfoReadDataSource.myNodeInfoFlow() } returns MutableStateFlow(myNodeEntity)
repository.deleteLogs(localNodeNum, port)
verifySuspend { meshLogDao.deleteLogs(MeshLog.NODE_NUM_LOCAL, port) }
}
@Test
fun `deleteLogs preserves remote node numbers`() = runTest(testDispatcher) {
val localNodeNum = 999
val remoteNodeNum = 888
val port = 100
val myNodeEntity = mock<MyNodeEntity>()
every { myNodeEntity.myNodeNum } returns localNodeNum
every { nodeInfoReadDataSource.myNodeInfoFlow() } returns MutableStateFlow(myNodeEntity)
repository.deleteLogs(remoteNodeNum, port)
verifySuspend { meshLogDao.deleteLogs(remoteNodeNum, port) }
}
*/
}

View file

@ -1,119 +0,0 @@
/*
* 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.data.repository
import kotlinx.coroutines.ExperimentalCoroutinesApi
@OptIn(ExperimentalCoroutinesApi::class)
class NodeRepositoryTest {
/*
private val lifecycleScope: LifecycleCoroutineScope = mock()
private val testDispatcher = StandardTestDispatcher()
private val dispatchers = CoroutineDispatchers(main = testDispatcher, io = testDispatcher, default = testDispatcher)
private val myNodeInfoFlow = MutableStateFlow<MyNodeEntity?>(null)
@Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
mockkStatic("androidx.lifecycle.LifecycleKt")
every { lifecycleScope.coroutineContext } returns testDispatcher + Job()
every { lifecycle.coroutineScope } returns lifecycleScope
every { readDataSource.myNodeInfoFlow() } returns myNodeInfoFlow
every { readDataSource.nodeDBbyNumFlow() } returns MutableStateFlow(emptyMap())
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
private fun createMyNodeEntity(nodeNum: Int) = MyNodeEntity(
myNodeNum = nodeNum,
model = "model",
firmwareVersion = "1.0",
couldUpdate = false,
shouldUpdate = false,
currentPacketId = 0L,
messageTimeoutMsec = 0,
minAppVersion = 0,
maxChannels = 0,
hasWifi = false,
)
@Test
fun `effectiveLogNodeId maps local node number to NODE_NUM_LOCAL`() = runTest(testDispatcher) {
val myNodeNum = 12345
myNodeInfoFlow.value = createMyNodeEntity(myNodeNum)
val repository =
NodeRepositoryImpl(lifecycle, readDataSource, writeDataSource, dispatchers, localStatsDataSource)
testScheduler.runCurrent()
val result = repository.effectiveLogNodeId(myNodeNum).filter { it == MeshLog.NODE_NUM_LOCAL }.first()
assertEquals(MeshLog.NODE_NUM_LOCAL, result)
}
@Test
fun `effectiveLogNodeId preserves remote node numbers`() = runTest(testDispatcher) {
val myNodeNum = 12345
val remoteNodeNum = 67890
myNodeInfoFlow.value = createMyNodeEntity(myNodeNum)
val repository =
NodeRepositoryImpl(lifecycle, readDataSource, writeDataSource, dispatchers, localStatsDataSource)
testScheduler.runCurrent()
val result = repository.effectiveLogNodeId(remoteNodeNum).first()
assertEquals(remoteNodeNum, result)
}
@Test
fun `effectiveLogNodeId updates when local node number changes`() = runTest(testDispatcher) {
val firstNodeNum = 111
val secondNodeNum = 222
val targetNodeNum = 111
myNodeInfoFlow.value = createMyNodeEntity(firstNodeNum)
val repository =
NodeRepositoryImpl(lifecycle, readDataSource, writeDataSource, dispatchers, localStatsDataSource)
testScheduler.runCurrent()
// Initially should be mapped to LOCAL because it matches
assertEquals(
MeshLog.NODE_NUM_LOCAL,
repository.effectiveLogNodeId(targetNodeNum).filter { it == MeshLog.NODE_NUM_LOCAL }.first(),
)
// Change local node num
myNodeInfoFlow.value = createMyNodeEntity(secondNodeNum)
testScheduler.runCurrent()
// Now it shouldn't match, so should return the original num
assertEquals(
targetNodeNum,
repository.effectiveLogNodeId(targetNodeNum).filter { it == targetNodeNum }.first(),
)
}
*/
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 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.data.repository
import kotlin.test.BeforeTest
class MeshLogRepositoryTest : CommonMeshLogRepositoryTest() {
@BeforeTest
fun setup() {
setupRepo()
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 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.data.repository
import kotlin.test.BeforeTest
class NodeRepositoryTest : CommonNodeRepositoryTest() {
@BeforeTest
fun setup() {
setupRepo()
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 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.data.repository
import kotlin.test.BeforeTest
class PacketRepositoryTest : CommonPacketRepositoryTest() {
@BeforeTest
fun setup() {
setupRepo()
}
}