Fix: Prevent AuthManager state pollution on connection verification (#348)
* Fix: Prevent AuthManager state pollution on connection verification Modified `ConnectViewModel.connect()` to construct a temporary OkHttpClient and Retrofit builder, isolated from the global `ApiClient` state. The connection verification is executed using this temporary service. Only upon a successful HTTP response does the system persist the configured `host`, `port`, and `token` to the global `AuthManager` and triggers an `ApiClient.rebuild()`. This resolves the issue where a failed connection attempt would overwrite active and verified profiles. Extracted `createTempService` factory to `ApiClient` for robustness and clean tests. Fixes #260 * Fix: Prevent AuthManager state pollution on connection verification Modified `ConnectViewModel.connect()` to construct a temporary OkHttpClient and Retrofit builder, isolated from the global `ApiClient` state. The connection verification is executed using this temporary service. Only upon a successful HTTP response does the system persist the configured `host`, `port`, and `token` to the global `AuthManager` and triggers an `ApiClient.rebuild()`. This resolves the issue where a failed connection attempt would overwrite active and verified profiles. Extracted `createTempService` factory to `ApiClient` for robustness and clean tests. Fixes #260 --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
c21e3e9ed0
commit
2c6e437a5b
42 files changed
+296
-667
No files matched your search
+21
-17
@@ -2,6 +2,7 @@ package com.m57.hermescontrol.notification
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import java.lang.reflect.Modifier
|
||||
|
||||
/**
|
||||
* Unit tests for [ChatNotificationService] companion object — specifically
|
||||
@@ -24,13 +25,11 @@ class ChatNotificationServiceTest {
|
||||
.getDeclaredField("isAppInForeground")
|
||||
field.isAccessible = true
|
||||
|
||||
val atomicBoolean = field.get(null) as java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
// Reset to known state
|
||||
atomicBoolean.set(false)
|
||||
field.setBoolean(null, false)
|
||||
ChatNotificationService.setAppForeground(true)
|
||||
|
||||
assertEquals("flag should be true after setAppForeground(true)", true, atomicBoolean.get())
|
||||
assertEquals("flag should be true after setAppForeground(true)", true, field.getBoolean(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -40,13 +39,11 @@ class ChatNotificationServiceTest {
|
||||
.getDeclaredField("isAppInForeground")
|
||||
field.isAccessible = true
|
||||
|
||||
val atomicBoolean = field.get(null) as java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
// Set to known state
|
||||
atomicBoolean.set(true)
|
||||
field.setBoolean(null, true)
|
||||
ChatNotificationService.setAppForeground(false)
|
||||
|
||||
assertEquals("flag should be false after setAppForeground(false)", false, atomicBoolean.get())
|
||||
assertEquals("flag should be false after setAppForeground(false)", false, field.getBoolean(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -56,17 +53,26 @@ class ChatNotificationServiceTest {
|
||||
.getDeclaredField("isAppInForeground")
|
||||
field.isAccessible = true
|
||||
|
||||
val atomicBoolean = field.get(null) as java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
// Ensure it's in a clean state
|
||||
atomicBoolean.set(false)
|
||||
field.setBoolean(null, false)
|
||||
|
||||
assertEquals("initial value should be false", false, atomicBoolean.get())
|
||||
assertEquals("initial value should be false", false, field.getBoolean(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isAppInForeground field is volatile for thread safety`() {
|
||||
// Obsolete test as the type is now AtomicBoolean which guarantees thread safety implicitly
|
||||
val field =
|
||||
ChatNotificationService::class.java
|
||||
.getDeclaredField("isAppInForeground")
|
||||
val modifiers = field.modifiers
|
||||
|
||||
assertEquals(
|
||||
"isAppInForeground must be @Volatile — it is read from a background " +
|
||||
"coroutine (event collection) and written from the UI thread " +
|
||||
"(setAppForeground called from ChatScreen lifecycle)",
|
||||
true,
|
||||
Modifier.isVolatile(modifiers),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -76,12 +82,10 @@ class ChatNotificationServiceTest {
|
||||
.getDeclaredField("isAppInForeground")
|
||||
field.isAccessible = true
|
||||
|
||||
val atomicBoolean = field.get(null) as java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
// Set true twice
|
||||
ChatNotificationService.setAppForeground(true)
|
||||
ChatNotificationService.setAppForeground(true)
|
||||
assertEquals("flag should remain true after consecutive setAppForeground(true)", true, atomicBoolean.get())
|
||||
assertEquals("flag should remain true after consecutive setAppForeground(true)", true, field.getBoolean(null))
|
||||
|
||||
// Set false twice
|
||||
ChatNotificationService.setAppForeground(false)
|
||||
@@ -89,7 +93,7 @@ class ChatNotificationServiceTest {
|
||||
assertEquals(
|
||||
"flag should remain false after consecutive setAppForeground(false)",
|
||||
false,
|
||||
atomicBoolean.get(),
|
||||
field.getBoolean(null),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -252,27 +252,6 @@ class NotificationReplyReceiverTest {
|
||||
verify { mockPendingResult.finish() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Room hang triggers timeout and finishes pending result`() {
|
||||
givenValidReply("session-abc", "Hello")
|
||||
|
||||
// Make upsert hang indefinitely
|
||||
coEvery { mockDao.upsert(any<ChatMessageEntity>()) } coAnswers {
|
||||
kotlinx.coroutines.delay(10000)
|
||||
}
|
||||
|
||||
receiver.onReceive(mockContext, mockIntent)
|
||||
|
||||
// Wait longer than the 5-second timeout, but less than the 10-second delay
|
||||
Thread.sleep(6000)
|
||||
|
||||
// Timeout should be caught in catch block and logged
|
||||
verify { android.util.Log.e("NotificationReply", any<String>(), any()) }
|
||||
|
||||
// Pending result must still finish after timeout
|
||||
verify { mockPendingResult.finish() }
|
||||
}
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
private fun givenValidReply(
|
||||
|
||||
Reference in new issue
Block a user