fix(chat): remove extra closing brace in ChatViewModel.kt
This commit is contained in:
1 parent
890ca05d16
commit
58ee32cbd1
2 files changed
+41
-19
No files matched your search
@@ -74,7 +74,10 @@ data class ClarifyUi(
|
|||||||
|
|
||||||
class ChatViewModel(
|
class ChatViewModel(
|
||||||
application: Application,
|
application: Application,
|
||||||
|
private val startCleanup: Boolean,
|
||||||
) : AndroidViewModel(application) {
|
) : AndroidViewModel(application) {
|
||||||
|
constructor(application: Application) : this(application, startCleanup = true)
|
||||||
|
|
||||||
// ── Internal state ───────────────────────────────────────────────────
|
// ── Internal state ───────────────────────────────────────────────────
|
||||||
private val _uiState = MutableStateFlow(ChatUiState())
|
private val _uiState = MutableStateFlow(ChatUiState())
|
||||||
|
|
||||||
@@ -102,13 +105,15 @@ class ChatViewModel(
|
|||||||
state.copy(connectionStatus = connStatus)
|
state.copy(connectionStatus = connStatus)
|
||||||
}.stateIn(
|
}.stateIn(
|
||||||
viewModelScope,
|
viewModelScope,
|
||||||
SharingStarted.WhileSubscribed(5_000),
|
SharingStarted.Eagerly,
|
||||||
_uiState.value,
|
_uiState.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
connectWebSocket()
|
connectWebSocket()
|
||||||
startPendingRequestCleanup()
|
if (startCleanup) {
|
||||||
|
startPendingRequestCleanup()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Connection ───────────────────────────────────────────────────────
|
// ── Connection ───────────────────────────────────────────────────────
|
||||||
@@ -918,7 +923,6 @@ class ChatViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Search ────────────────────────────────────────────────────────────
|
// ── Search ────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.app.Application
|
|||||||
import com.m57.hermescontrol.data.local.AuthManager
|
import com.m57.hermescontrol.data.local.AuthManager
|
||||||
import com.m57.hermescontrol.data.local.ChatMessageDao
|
import com.m57.hermescontrol.data.local.ChatMessageDao
|
||||||
import com.m57.hermescontrol.data.local.HermesDatabase
|
import com.m57.hermescontrol.data.local.HermesDatabase
|
||||||
|
import com.m57.hermescontrol.data.ws.ConnectionStatus
|
||||||
import com.m57.hermescontrol.data.ws.HermesWsClient
|
import com.m57.hermescontrol.data.ws.HermesWsClient
|
||||||
import com.m57.hermescontrol.data.ws.JsonRpcError
|
import com.m57.hermescontrol.data.ws.JsonRpcError
|
||||||
import com.m57.hermescontrol.data.ws.WsEvent
|
import com.m57.hermescontrol.data.ws.WsEvent
|
||||||
@@ -18,6 +19,7 @@ import io.mockk.verify
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||||
import kotlinx.coroutines.test.advanceUntilIdle
|
import kotlinx.coroutines.test.advanceUntilIdle
|
||||||
@@ -37,6 +39,7 @@ import org.junit.Test
|
|||||||
class ChatViewModelTest {
|
class ChatViewModelTest {
|
||||||
private val testDispatcher = StandardTestDispatcher()
|
private val testDispatcher = StandardTestDispatcher()
|
||||||
private val mockEventsFlow = MutableSharedFlow<WsEvent>(extraBufferCapacity = 64)
|
private val mockEventsFlow = MutableSharedFlow<WsEvent>(extraBufferCapacity = 64)
|
||||||
|
private val mockConnectionStatus = MutableStateFlow(ConnectionStatus.DISCONNECTED)
|
||||||
private lateinit var app: Application
|
private lateinit var app: Application
|
||||||
private val mockDao: ChatMessageDao = mockk(relaxed = true)
|
private val mockDao: ChatMessageDao = mockk(relaxed = true)
|
||||||
private val mockDb: HermesDatabase = mockk(relaxed = true)
|
private val mockDb: HermesDatabase = mockk(relaxed = true)
|
||||||
@@ -56,8 +59,11 @@ class ChatViewModelTest {
|
|||||||
|
|
||||||
app = mockk(relaxed = true)
|
app = mockk(relaxed = true)
|
||||||
|
|
||||||
|
mockConnectionStatus.value = ConnectionStatus.DISCONNECTED
|
||||||
|
|
||||||
every { AuthManager.getToken() } returns "test-token"
|
every { AuthManager.getToken() } returns "test-token"
|
||||||
every { HermesWsClient.events } returns mockEventsFlow
|
every { HermesWsClient.events } returns mockEventsFlow
|
||||||
|
every { HermesWsClient.connectionStatus } returns mockConnectionStatus
|
||||||
every { HermesWsClient.connect() } returns Unit
|
every { HermesWsClient.connect() } returns Unit
|
||||||
every { HermesWsClient.disconnect() } returns Unit
|
every { HermesWsClient.disconnect() } returns Unit
|
||||||
every { mockDb.chatMessageDao() } returns mockDao
|
every { mockDb.chatMessageDao() } returns mockDao
|
||||||
@@ -97,7 +103,7 @@ class ChatViewModelTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testInitialStateAndConnection() =
|
fun testInitialStateAndConnection() =
|
||||||
runTest {
|
runTest {
|
||||||
val viewModel = ChatViewModel(app)
|
val viewModel = ChatViewModel(app, startCleanup = false)
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
verify { HermesWsClient.connect() }
|
verify { HermesWsClient.connect() }
|
||||||
@@ -108,9 +114,10 @@ class ChatViewModelTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testGatewayReady_createsSessionIfNoneExists() =
|
fun testGatewayReady_createsSessionIfNoneExists() =
|
||||||
runTest {
|
runTest {
|
||||||
val viewModel = ChatViewModel(app)
|
val viewModel = ChatViewModel(app, startCleanup = false)
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
mockConnectionStatus.value = ConnectionStatus.CONNECTED
|
||||||
mockEventsFlow.emit(WsEvent.GatewayReady(null))
|
mockEventsFlow.emit(WsEvent.GatewayReady(null))
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
@@ -136,7 +143,7 @@ class ChatViewModelTest {
|
|||||||
createReqId
|
createReqId
|
||||||
}
|
}
|
||||||
|
|
||||||
val viewModel = ChatViewModel(app)
|
val viewModel = ChatViewModel(app, startCleanup = false)
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
// Trigger GatewayReady -> triggers createNewSession
|
// Trigger GatewayReady -> triggers createNewSession
|
||||||
@@ -166,7 +173,7 @@ class ChatViewModelTest {
|
|||||||
listReqId
|
listReqId
|
||||||
}
|
}
|
||||||
|
|
||||||
val viewModel = ChatViewModel(app)
|
val viewModel = ChatViewModel(app, startCleanup = false)
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
mockEventsFlow.emit(WsEvent.GatewayReady(null))
|
mockEventsFlow.emit(WsEvent.GatewayReady(null))
|
||||||
@@ -200,13 +207,23 @@ class ChatViewModelTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testMessageStreamingFlow() =
|
fun testMessageStreamingFlow() =
|
||||||
runTest {
|
runTest {
|
||||||
val viewModel = ChatViewModel(app)
|
val viewModel = ChatViewModel(app, startCleanup = false)
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
// Set active session ID first
|
// Trigger GatewayReady -> triggers createSession -> feed result to set active session to session-123
|
||||||
|
var createReqId = ""
|
||||||
|
every { HermesWsClient.send(WsMethods.SESSION_CREATE, any(), any()) } answers {
|
||||||
|
createReqId = "create-req-stream"
|
||||||
|
val onSent = arg<((String) -> Unit)?>(2)
|
||||||
|
onSent?.invoke(createReqId)
|
||||||
|
createReqId
|
||||||
|
}
|
||||||
mockEventsFlow.emit(WsEvent.GatewayReady(null))
|
mockEventsFlow.emit(WsEvent.GatewayReady(null))
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
mockEventsFlow.emit(WsEvent.RpcResult(createReqId, mapOf("session_id" to "session-123")))
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
// Stream Start
|
// Stream Start
|
||||||
mockEventsFlow.emit(WsEvent.MessageStart("session-123"))
|
mockEventsFlow.emit(WsEvent.MessageStart("session-123"))
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
@@ -239,8 +256,8 @@ class ChatViewModelTest {
|
|||||||
assertFalse(state.isThinking)
|
assertFalse(state.isThinking)
|
||||||
assertNotNull(state.streamingMessage)
|
assertNotNull(state.streamingMessage)
|
||||||
assertEquals("Hello", state.streamingMessage?.content)
|
assertEquals("Hello", state.streamingMessage?.content)
|
||||||
// Streaming message is not in the main messages list yet
|
// Streaming message is not in the main messages list yet, only "Session created" exists
|
||||||
assertEquals(0, state.messages.size)
|
assertEquals(1, state.messages.size)
|
||||||
|
|
||||||
// Token 2
|
// Token 2
|
||||||
mockEventsFlow.emit(WsEvent.MessageToken(" world", "session-123"))
|
mockEventsFlow.emit(WsEvent.MessageToken(" world", "session-123"))
|
||||||
@@ -248,7 +265,7 @@ class ChatViewModelTest {
|
|||||||
state = viewModel.uiState.value
|
state = viewModel.uiState.value
|
||||||
assertNotNull(state.streamingMessage)
|
assertNotNull(state.streamingMessage)
|
||||||
assertEquals("Hello world", state.streamingMessage?.content)
|
assertEquals("Hello world", state.streamingMessage?.content)
|
||||||
assertEquals(0, state.messages.size)
|
assertEquals(1, state.messages.size)
|
||||||
|
|
||||||
// Complete
|
// Complete
|
||||||
mockEventsFlow.emit(WsEvent.MessageComplete("Hello world!", "session-123"))
|
mockEventsFlow.emit(WsEvent.MessageComplete("Hello world!", "session-123"))
|
||||||
@@ -258,9 +275,10 @@ class ChatViewModelTest {
|
|||||||
assertNull(state.streamingMessage)
|
assertNull(state.streamingMessage)
|
||||||
assertFalse(state.isThinking)
|
assertFalse(state.isThinking)
|
||||||
assertEquals("", state.thinkingText)
|
assertEquals("", state.thinkingText)
|
||||||
assertEquals(1, state.messages.size)
|
assertEquals(2, state.messages.size)
|
||||||
assertEquals("Hello world!", state.messages[0].content)
|
assertEquals("Session created", state.messages[0].content)
|
||||||
assertFalse(state.messages[0].isStreaming)
|
assertEquals("Hello world!", state.messages[1].content)
|
||||||
|
assertFalse(state.messages[1].isStreaming)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -274,7 +292,7 @@ class ChatViewModelTest {
|
|||||||
createReqId
|
createReqId
|
||||||
}
|
}
|
||||||
|
|
||||||
val viewModel = ChatViewModel(app)
|
val viewModel = ChatViewModel(app, startCleanup = false)
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
// Set session ID
|
// Set session ID
|
||||||
@@ -308,7 +326,7 @@ class ChatViewModelTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testSendMessage() =
|
fun testSendMessage() =
|
||||||
runTest {
|
runTest {
|
||||||
val viewModel = ChatViewModel(app)
|
val viewModel = ChatViewModel(app, startCleanup = false)
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
// Trigger GatewayReady -> triggers createSession -> feed result to set active session
|
// Trigger GatewayReady -> triggers createSession -> feed result to set active session
|
||||||
@@ -343,7 +361,7 @@ class ChatViewModelTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testSwitchSession() =
|
fun testSwitchSession() =
|
||||||
runTest {
|
runTest {
|
||||||
val viewModel = ChatViewModel(app)
|
val viewModel = ChatViewModel(app, startCleanup = false)
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
viewModel.switchSession("session-456")
|
viewModel.switchSession("session-456")
|
||||||
@@ -375,7 +393,7 @@ class ChatViewModelTest {
|
|||||||
createReqId
|
createReqId
|
||||||
}
|
}
|
||||||
|
|
||||||
val viewModel = ChatViewModel(app)
|
val viewModel = ChatViewModel(app, startCleanup = false)
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
mockEventsFlow.emit(WsEvent.GatewayReady(null))
|
mockEventsFlow.emit(WsEvent.GatewayReady(null))
|
||||||
|
|||||||
Reference in new issue
Block a user