Skip to content

Commit cb669c1

Browse files
jamesarichCopilot
andcommitted
chore: KDoc cleanup, stale comments, and cruft removal
- Add KDoc to 3 JSON datasource interfaces and 7 repository implementations - Remove IndoorAirQuality stub comments - Remove commented-out WRITE_EXTERNAL_STORAGE permission from manifest - Update foreground service comment to architecture note (Android 14+) - Remove redundant inline comments in PacketDao.kt Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 42e5d08 commit cb669c1

20 files changed

Lines changed: 1573 additions & 44 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,7 @@
6767
-->
6868
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
6969

70-
<!-- Only for debug log writing, disable for production
71-
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
72-
-->
73-
74-
<!-- We run our mesh code as a foreground service - FIXME, find a way to stop doing this -->
70+
<!-- Required: foreground service keeps mesh connection alive per Android 14+ requirements -->
7571
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
7672
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
7773
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/*
2+
* Copyright (c) 2026 Meshtastic LLC
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
package org.meshtastic.core.data.repository
18+
19+
import kotlinx.coroutines.ExperimentalCoroutinesApi
20+
import kotlinx.coroutines.async
21+
import kotlinx.coroutines.coroutineScope
22+
import kotlinx.coroutines.flow.drop
23+
import kotlinx.coroutines.flow.first
24+
import kotlinx.coroutines.launch
25+
import kotlinx.coroutines.test.UnconfinedTestDispatcher
26+
import kotlinx.coroutines.test.advanceUntilIdle
27+
import kotlinx.coroutines.test.runTest
28+
import org.junit.runner.RunWith
29+
import org.meshtastic.core.repository.NodeMetadata
30+
import org.meshtastic.core.testing.FakeDatabaseProvider
31+
import org.meshtastic.core.testing.setupTestContext
32+
import org.robolectric.RobolectricTestRunner
33+
import org.robolectric.annotation.Config
34+
import kotlin.test.AfterTest
35+
import kotlin.test.BeforeTest
36+
import kotlin.test.Test
37+
import kotlin.test.assertEquals
38+
import kotlin.test.assertTrue
39+
40+
@RunWith(RobolectricTestRunner::class)
41+
@Config(sdk = [34])
42+
@OptIn(ExperimentalCoroutinesApi::class)
43+
class AppMetadataRepositoryImplTest {
44+
45+
private lateinit var dbProvider: FakeDatabaseProvider
46+
private lateinit var repository: AppMetadataRepositoryImpl
47+
48+
@BeforeTest
49+
fun setUp() {
50+
setupTestContext()
51+
dbProvider = FakeDatabaseProvider()
52+
repository = AppMetadataRepositoryImpl(dbProvider)
53+
}
54+
55+
@AfterTest
56+
fun tearDown() {
57+
dbProvider.close()
58+
}
59+
60+
@Test
61+
fun `metadataByNum starts empty`() = runTest {
62+
assertTrue(repository.metadataByNum.first().isEmpty())
63+
}
64+
65+
@Test
66+
fun `setFavorite creates missing metadata row`() = runTest {
67+
repository.setFavorite(nodeNum = 101, isFavorite = true)
68+
advanceUntilIdle()
69+
70+
assertEquals(
71+
NodeMetadata(num = 101, isFavorite = true, notes = ""),
72+
repository.metadataByNum.first().getValue(101),
73+
)
74+
}
75+
76+
@Test
77+
fun `setIgnored creates missing metadata row`() = runTest {
78+
repository.setIgnored(nodeNum = 102, isIgnored = true)
79+
advanceUntilIdle()
80+
81+
assertEquals(
82+
NodeMetadata(num = 102, isIgnored = true, notes = ""),
83+
repository.metadataByNum.first().getValue(102),
84+
)
85+
}
86+
87+
@Test
88+
fun `setMuted and setNotes preserve existing flags`() = runTest {
89+
repository.setFavorite(nodeNum = 103, isFavorite = true)
90+
repository.setMuted(nodeNum = 103, isMuted = true)
91+
repository.setNotes(nodeNum = 103, notes = "Portable node")
92+
advanceUntilIdle()
93+
94+
assertEquals(
95+
NodeMetadata(num = 103, isFavorite = true, isMuted = true, notes = "Portable node"),
96+
repository.metadataByNum.first().getValue(103),
97+
)
98+
}
99+
100+
@Test
101+
fun `setManuallyVerified updates verification flag`() = runTest {
102+
repository.setManuallyVerified(nodeNum = 104, verified = true)
103+
advanceUntilIdle()
104+
105+
assertEquals(
106+
NodeMetadata(num = 104, manuallyVerified = true, notes = ""),
107+
repository.metadataByNum.first().getValue(104),
108+
)
109+
}
110+
111+
@Test
112+
fun `repeated updates keep a single metadata entry per node`() = runTest {
113+
repository.setFavorite(nodeNum = 105, isFavorite = true)
114+
repository.setFavorite(nodeNum = 105, isFavorite = false)
115+
repository.setNotes(nodeNum = 105, notes = "Updated")
116+
advanceUntilIdle()
117+
118+
val metadata = repository.metadataByNum.first()
119+
assertEquals(1, metadata.size)
120+
assertEquals(NodeMetadata(num = 105, notes = "Updated"), metadata.getValue(105))
121+
}
122+
123+
@Test
124+
fun `delete removes existing metadata`() = runTest {
125+
repository.setFavorite(nodeNum = 106, isFavorite = true)
126+
advanceUntilIdle()
127+
128+
repository.delete(106)
129+
advanceUntilIdle()
130+
131+
assertTrue(repository.metadataByNum.first().isEmpty())
132+
}
133+
134+
@Test
135+
fun `delete missing metadata is a no-op`() = runTest {
136+
repository.delete(999)
137+
advanceUntilIdle()
138+
139+
assertTrue(repository.metadataByNum.first().isEmpty())
140+
}
141+
142+
@Test
143+
fun `metadataByNum flow reflects create update and delete changes`() = runTest {
144+
val created = backgroundScope.async(UnconfinedTestDispatcher(testScheduler)) {
145+
repository.metadataByNum.drop(1).first { it[107]?.isFavorite == true }
146+
}
147+
148+
repository.setFavorite(nodeNum = 107, isFavorite = true)
149+
advanceUntilIdle()
150+
assertEquals(NodeMetadata(num = 107, isFavorite = true, notes = ""), created.await().getValue(107))
151+
152+
val updated = backgroundScope.async(UnconfinedTestDispatcher(testScheduler)) {
153+
repository.metadataByNum.drop(1).first { it[107]?.notes == "Flow note" }
154+
}
155+
156+
repository.setNotes(nodeNum = 107, notes = "Flow note")
157+
advanceUntilIdle()
158+
assertEquals("Flow note", updated.await().getValue(107).notes)
159+
160+
val deleted = backgroundScope.async(UnconfinedTestDispatcher(testScheduler)) {
161+
repository.metadataByNum.drop(1).first { it.isEmpty() }
162+
}
163+
164+
repository.delete(107)
165+
advanceUntilIdle()
166+
assertTrue(deleted.await().isEmpty())
167+
}
168+
169+
@Test
170+
fun `concurrent updates on same missing node produce one merged row`() = runTest {
171+
coroutineScope {
172+
launch { repository.setFavorite(nodeNum = 108, isFavorite = true) }
173+
launch { repository.setIgnored(nodeNum = 108, isIgnored = true) }
174+
launch { repository.setMuted(nodeNum = 108, isMuted = true) }
175+
launch { repository.setNotes(nodeNum = 108, notes = "Concurrent") }
176+
launch { repository.setManuallyVerified(nodeNum = 108, verified = true) }
177+
}
178+
advanceUntilIdle()
179+
180+
val metadata = repository.metadataByNum.first()
181+
assertEquals(1, metadata.size)
182+
assertEquals(
183+
NodeMetadata(
184+
num = 108,
185+
isFavorite = true,
186+
isIgnored = true,
187+
isMuted = true,
188+
notes = "Concurrent",
189+
manuallyVerified = true,
190+
),
191+
metadata.getValue(108),
192+
)
193+
}
194+
195+
@Test
196+
fun `updates for multiple nodes stay isolated`() = runTest {
197+
repository.setFavorite(nodeNum = 201, isFavorite = true)
198+
repository.setIgnored(nodeNum = 202, isIgnored = true)
199+
repository.setNotes(nodeNum = 203, notes = "Third")
200+
advanceUntilIdle()
201+
202+
val metadata = repository.metadataByNum.first()
203+
assertEquals(3, metadata.size)
204+
assertEquals(NodeMetadata(num = 201, isFavorite = true, notes = ""), metadata.getValue(201))
205+
assertEquals(NodeMetadata(num = 202, isIgnored = true, notes = ""), metadata.getValue(202))
206+
assertEquals(NodeMetadata(num = 203, notes = "Third"), metadata.getValue(203))
207+
}
208+
}

0 commit comments

Comments
 (0)