16 files changed
+1169
-119
No files matched your search
@@ -59,6 +59,57 @@ class EventParserTest {
|
||||
assertEquals(mapOf("status" to "success"), rpcResult.result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseServerRequest_withIdAndMethod_isNotRpcResult() {
|
||||
val event =
|
||||
EventParser.parse(
|
||||
createJsonRpcResponse(
|
||||
jsonrpc = "2.0",
|
||||
id = "srq-123",
|
||||
method = "clarify",
|
||||
params = mapOf("session_id" to "session-1", "question" to "Continue?"),
|
||||
),
|
||||
)
|
||||
|
||||
assertTrue(event is WsEvent.ServerRequest)
|
||||
assertFalse(event is WsEvent.RpcResult)
|
||||
val request = event as WsEvent.ServerRequest
|
||||
assertEquals("srq-123", request.id)
|
||||
assertEquals("clarify", request.method)
|
||||
assertEquals("session-1", request.params["session_id"])
|
||||
assertEquals("Continue?", request.params["question"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseRequestCancel_notification_returnsCancellationEvent() {
|
||||
val event =
|
||||
EventParser.parse(
|
||||
createJsonRpcResponse(
|
||||
jsonrpc = "2.0",
|
||||
id = null,
|
||||
method = "event",
|
||||
params =
|
||||
mapOf(
|
||||
"type" to "request.cancel",
|
||||
"session_id" to "session-1",
|
||||
"payload" to
|
||||
mapOf(
|
||||
"id" to "srq-123",
|
||||
"method" to "clarify",
|
||||
"reason" to "timeout",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertTrue(event is WsEvent.ServerRequestCancelled)
|
||||
val cancelled = event as WsEvent.ServerRequestCancelled
|
||||
assertEquals("srq-123", cancelled.id)
|
||||
assertEquals("clarify", cancelled.method)
|
||||
assertEquals("timeout", cancelled.reason)
|
||||
assertEquals("session-1", cancelled.sessionId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseRpcError_returnsRpcErrorEvent() {
|
||||
val error = JsonRpcError(code = -32600, message = "Invalid Request")
|
||||
|
||||
@@ -19,13 +19,18 @@ import io.mockk.verify
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.put
|
||||
import okhttp3.Response
|
||||
import okhttp3.WebSocket
|
||||
import okhttp3.WebSocketListener
|
||||
@@ -164,6 +169,106 @@ class HermesWsClientTest {
|
||||
assertTrue(msg.contains(id))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOpenRequestsReplayUsesTheLiveServerRequestDispatcher() =
|
||||
runBlocking {
|
||||
mockWebServer.enqueue(
|
||||
MockResponse().withWebSocketUpgrade(
|
||||
object : WebSocketListener() {
|
||||
override fun onOpen(
|
||||
webSocket: WebSocket,
|
||||
response: okhttp3.Response,
|
||||
) {
|
||||
webSocket.send(
|
||||
"""{"jsonrpc":"2.0","id":"42","result":{"open_requests":[{"id":"srq-secret","method":"secret","params":{"session_id":"session-1","env_var":"API_KEY","prompt":"Enter API key"}}]}}""",
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
val received =
|
||||
async {
|
||||
withTimeout(5_000) {
|
||||
HermesWsClient.events.first {
|
||||
it is WsEvent.ServerRequest && it.id == "srq-secret"
|
||||
}
|
||||
}
|
||||
}
|
||||
HermesWsClient.connect()
|
||||
val event = received.await() as WsEvent.ServerRequest
|
||||
|
||||
assertEquals("secret", event.method)
|
||||
assertEquals("session-1", event.params["session_id"])
|
||||
assertEquals("API_KEY", event.params["env_var"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRespondToServerRequest_preservesIdAndOmitsMethod() {
|
||||
val socket = mockk<WebSocket>()
|
||||
every { socket.send(any<String>()) } returns true
|
||||
every { socket.close(any(), any()) } returns true
|
||||
val socketField = HermesWsClient::class.java.getDeclaredField("webSocket").apply { isAccessible = true }
|
||||
val connectedField = HermesWsClient::class.java.getDeclaredField("connected").apply { isAccessible = true }
|
||||
socketField.set(HermesWsClient, socket)
|
||||
(connectedField.get(HermesWsClient) as java.util.concurrent.atomic.AtomicBoolean).set(true)
|
||||
|
||||
val sent =
|
||||
HermesWsClient.respondToServerRequest(
|
||||
"srq-abc123",
|
||||
buildJsonObject { put("value", "hello") },
|
||||
)
|
||||
|
||||
assertTrue(sent)
|
||||
verify {
|
||||
socket.send(
|
||||
match<String> { raw ->
|
||||
val frame = Json.parseToJsonElement(raw).jsonObject
|
||||
frame["jsonrpc"]?.jsonPrimitive?.content == "2.0" &&
|
||||
frame["id"]?.jsonPrimitive?.content == "srq-abc123" &&
|
||||
frame["result"]
|
||||
?.jsonObject
|
||||
?.get("value")
|
||||
?.jsonPrimitive
|
||||
?.content == "hello" &&
|
||||
!frame.containsKey("method")
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRespondToServerRequestError_preservesIdAndErrorShape() {
|
||||
val socket = mockk<WebSocket>()
|
||||
every { socket.send(any<String>()) } returns true
|
||||
every { socket.close(any(), any()) } returns true
|
||||
val socketField = HermesWsClient::class.java.getDeclaredField("webSocket").apply { isAccessible = true }
|
||||
val connectedField = HermesWsClient::class.java.getDeclaredField("connected").apply { isAccessible = true }
|
||||
socketField.set(HermesWsClient, socket)
|
||||
(connectedField.get(HermesWsClient) as java.util.concurrent.atomic.AtomicBoolean).set(true)
|
||||
|
||||
assertTrue(HermesWsClient.respondToServerRequestError("srq-abc123", -32601, "unsupported"))
|
||||
verify {
|
||||
socket.send(
|
||||
match<String> { raw ->
|
||||
val frame = Json.parseToJsonElement(raw).jsonObject
|
||||
frame["id"]?.jsonPrimitive?.content == "srq-abc123" &&
|
||||
frame["error"]
|
||||
?.jsonObject
|
||||
?.get("code")
|
||||
?.jsonPrimitive
|
||||
?.content == "-32601" &&
|
||||
frame["error"]
|
||||
?.jsonObject
|
||||
?.get("message")
|
||||
?.jsonPrimitive
|
||||
?.content == "unsupported" &&
|
||||
!frame.containsKey("method")
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFailedSocketSendQueuesMessageForReconnect() {
|
||||
val staleSocket = mockk<WebSocket>(relaxed = true)
|
||||
|
||||
@@ -8,8 +8,12 @@ import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
@@ -196,4 +200,111 @@ class ChatApprovalsDelegateTest {
|
||||
delegate.handleApprovalRespondResult(mapOf("resolved" to 1))
|
||||
assertEquals(listOf("Approval submitted"), systemMessages)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cancelServerRequest_matchesServerRequestIdOnly() {
|
||||
delegate.handleApprovalRequest(
|
||||
WsEvent.ApprovalRequest(
|
||||
command = "rm -rf /tmp/x",
|
||||
description = "dangerous",
|
||||
patternKeys = null,
|
||||
sessionId = "session-1",
|
||||
requestId = "approval-1",
|
||||
serverRequestId = "srq-approval-1",
|
||||
),
|
||||
)
|
||||
|
||||
delegate.cancelServerRequest("srq-other")
|
||||
assertNotNull(
|
||||
uiState.value.messages
|
||||
.last()
|
||||
.approvalInfo,
|
||||
)
|
||||
|
||||
delegate.cancelServerRequest("srq-approval-1")
|
||||
assertNull(
|
||||
uiState.value.messages
|
||||
.last()
|
||||
.approvalInfo,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun liveServerApproval_upgradesMatchingPendingApprovalWithoutDuplicating() {
|
||||
delegate.handleApprovalRequest(
|
||||
WsEvent.ApprovalRequest(
|
||||
command = "git status",
|
||||
description = "inspect",
|
||||
patternKeys = null,
|
||||
sessionId = "session-1",
|
||||
requestId = "approval-business-id",
|
||||
),
|
||||
)
|
||||
delegate.handleApprovalRequest(
|
||||
WsEvent.ApprovalRequest(
|
||||
command = "git status",
|
||||
description = "inspect",
|
||||
patternKeys = null,
|
||||
sessionId = "session-1",
|
||||
requestId = "approval-business-id",
|
||||
serverRequestId = "srq-approval",
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(1, uiState.value.messages.size)
|
||||
assertEquals(
|
||||
"srq-approval",
|
||||
uiState.value.messages
|
||||
.single()
|
||||
.approvalInfo
|
||||
?.serverRequestId,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serverApproval_usesChoiceAndAllResultPayload() {
|
||||
val responses = mutableListOf<Pair<String, JsonElement>>()
|
||||
val serverDelegate =
|
||||
ChatApprovalsDelegate(
|
||||
scope = testScope,
|
||||
ioDispatcher = testDispatcher,
|
||||
uiState = uiState,
|
||||
runtimeSessionId = { "session-1" },
|
||||
wsSend = { _, _, _ -> error("new approval must not use legacy response") },
|
||||
trackRequest = { _, _ -> },
|
||||
addSystemMessage = { },
|
||||
respondToServerRequest = { id, result -> responses += id to result },
|
||||
)
|
||||
serverDelegate.handleApprovalRequest(
|
||||
WsEvent.ApprovalRequest(
|
||||
command = "rm -rf /tmp/x",
|
||||
description = "dangerous",
|
||||
patternKeys = null,
|
||||
sessionId = "session-1",
|
||||
requestId = "approval-1",
|
||||
serverRequestId = "srq-approval",
|
||||
),
|
||||
)
|
||||
|
||||
serverDelegate.respondToApproval("approve")
|
||||
testDispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
assertEquals("srq-approval", responses.single().first)
|
||||
assertEquals(
|
||||
"once",
|
||||
responses
|
||||
.single()
|
||||
.second.jsonObject["choice"]
|
||||
?.jsonPrimitive
|
||||
?.content,
|
||||
)
|
||||
assertEquals(
|
||||
"false",
|
||||
responses
|
||||
.single()
|
||||
.second.jsonObject["all"]
|
||||
?.jsonPrimitive
|
||||
?.content,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package com.m57.hermescontrol.ui.chat
|
||||
|
||||
import com.m57.hermescontrol.data.ws.WsEvent
|
||||
import com.m57.hermescontrol.data.ws.WsMethods
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class ChatPromptProtocolTest {
|
||||
private val dispatcher = StandardTestDispatcher()
|
||||
|
||||
@Test
|
||||
fun clarifyServerResponse_mergesLockedAnswers_withoutEmptyOverwrite() {
|
||||
val scope = TestScope(dispatcher)
|
||||
val uiState =
|
||||
MutableStateFlow(
|
||||
ChatUiState(
|
||||
currentSessionId = "session-1",
|
||||
clarifyRequest =
|
||||
ClarifyUi(
|
||||
text = "",
|
||||
questions =
|
||||
listOf(
|
||||
ClarifyQuestionUi(qid = "q0", question = "First?"),
|
||||
ClarifyQuestionUi(qid = "q1", question = "Second?"),
|
||||
),
|
||||
serverRequestId = "srq-clarify",
|
||||
lockedAnswers = mapOf("q0" to "yes"),
|
||||
),
|
||||
),
|
||||
)
|
||||
var responseId: String? = null
|
||||
var response: JsonElement? = null
|
||||
|
||||
val delegate =
|
||||
ChatClarifyDelegate(
|
||||
uiState = uiState,
|
||||
scope = scope,
|
||||
ioDispatcher = dispatcher,
|
||||
persistMessage = { _, _ -> },
|
||||
respondToServerRequest = { id, result ->
|
||||
responseId = id
|
||||
response = result
|
||||
},
|
||||
)
|
||||
|
||||
delegate.respondToClarifyBatch(mapOf("q0" to "", "q1" to "new answer"))
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
assertEquals("srq-clarify", responseId)
|
||||
val answers = response?.jsonObject?.get("answers")?.jsonObject
|
||||
assertNotNull(answers)
|
||||
assertEquals("yes", answers?.get("q0")?.jsonPrimitive?.content)
|
||||
assertEquals("new answer", answers?.get("q1")?.jsonPrimitive?.content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun credentialPrompt_serverRequestId_controls_response_and_cancellation() {
|
||||
val scope = TestScope(dispatcher)
|
||||
val uiState = MutableStateFlow(ChatUiState(currentSessionId = "session-1"))
|
||||
val sentMethods = mutableListOf<String>()
|
||||
var responseId: String? = null
|
||||
var response: JsonElement? = null
|
||||
val delegate =
|
||||
ChatCredentialPromptsDelegate(
|
||||
scope = scope,
|
||||
ioDispatcher = dispatcher,
|
||||
uiState = uiState,
|
||||
wsSend = { method, _, _ -> sentMethods += method },
|
||||
trackRequest = { _, _ -> },
|
||||
respondToServerRequest = { id, result ->
|
||||
responseId = id
|
||||
response = result
|
||||
},
|
||||
)
|
||||
|
||||
delegate.handleSudoRequest(
|
||||
WsEvent.SudoRequest(
|
||||
requestId = "legacy-sudo-id",
|
||||
sessionId = "session-1",
|
||||
serverRequestId = "srq-sudo",
|
||||
),
|
||||
)
|
||||
delegate.handleSudoExpire(WsEvent.SudoExpire(null, "session-1", serverRequestId = "srq-other"))
|
||||
assertNotNull(uiState.value.sudoPrompt)
|
||||
|
||||
delegate.respondToSudo("password")
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
assertEquals("srq-sudo", responseId)
|
||||
assertEquals(
|
||||
"password",
|
||||
response
|
||||
?.jsonObject
|
||||
?.get("value")
|
||||
?.jsonPrimitive
|
||||
?.content,
|
||||
)
|
||||
assertNull(uiState.value.sudoPrompt)
|
||||
assertEquals(emptyList<String>(), sentMethods)
|
||||
|
||||
delegate.handleSudoRequest(WsEvent.SudoRequest("legacy-sudo-2", "session-1"))
|
||||
delegate.respondToSudo("legacy-password")
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
assertEquals(listOf(WsMethods.SUDO_RESPOND), sentMethods)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun secretAndVaultServerRequests_useValueResultPayloads() {
|
||||
val scope = TestScope(dispatcher)
|
||||
val uiState = MutableStateFlow(ChatUiState(currentSessionId = "session-1"))
|
||||
val responses = mutableListOf<Pair<String, JsonElement>>()
|
||||
val delegate =
|
||||
ChatCredentialPromptsDelegate(
|
||||
scope = scope,
|
||||
ioDispatcher = dispatcher,
|
||||
uiState = uiState,
|
||||
wsSend = { _, _, _ -> error("new prompt must not use legacy response") },
|
||||
trackRequest = { _, _ -> },
|
||||
respondToServerRequest = { id, result -> responses += id to result },
|
||||
)
|
||||
|
||||
delegate.handleSecretRequest(WsEvent.SecretRequest(null, "session-1", serverRequestId = "srq-secret"))
|
||||
delegate.respondToSecret("secret-value")
|
||||
delegate.handleVaultCodeRequest(WsEvent.VaultCodeRequest(null, "session-1", serverRequestId = "srq-code"))
|
||||
delegate.respondToVaultCode("123456")
|
||||
delegate.handleVaultUnlockRequest(
|
||||
WsEvent.VaultUnlockRequest(null, "session-1", serverRequestId = "srq-unlock"),
|
||||
)
|
||||
delegate.respondToVaultUnlock("vault-password")
|
||||
delegate.handleVaultSaveLoginRequest(
|
||||
WsEvent.VaultSaveLoginRequest(null, "session-1", serverRequestId = "srq-save"),
|
||||
)
|
||||
delegate.respondToVaultSaveLogin("alice", "login-password")
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
assertEquals(listOf("srq-secret", "srq-code", "srq-unlock", "srq-save"), responses.map { it.first })
|
||||
assertEquals(
|
||||
"secret-value",
|
||||
responses[0]
|
||||
.second.jsonObject["value"]
|
||||
?.jsonPrimitive
|
||||
?.content,
|
||||
)
|
||||
assertEquals(
|
||||
"123456",
|
||||
responses[1]
|
||||
.second.jsonObject["value"]
|
||||
?.jsonPrimitive
|
||||
?.content,
|
||||
)
|
||||
assertEquals(
|
||||
"vault-password",
|
||||
responses[2]
|
||||
.second.jsonObject["value"]
|
||||
?.jsonPrimitive
|
||||
?.content,
|
||||
)
|
||||
assertEquals(
|
||||
"{\"identifier\":\"alice\",\"password\":\"login-password\"}",
|
||||
responses[3]
|
||||
.second.jsonObject["value"]
|
||||
?.jsonPrimitive
|
||||
?.content,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,8 @@ import kotlinx.coroutines.test.setMain
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.put
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
@@ -188,6 +190,8 @@ class ChatViewModelTest {
|
||||
arg<((String) -> Unit)?>(2)?.invoke(id)
|
||||
id
|
||||
}
|
||||
every { HermesWsClient.respondToServerRequest(any(), any()) } returns true
|
||||
every { HermesWsClient.respondToServerRequestError(any(), any(), any()) } returns true
|
||||
|
||||
// Stub model-options so preloadModelOptions() (fired at GatewayReady) is safe.
|
||||
val mockApi = mockk<com.m57.hermescontrol.data.remote.HermesApiService>(relaxed = true)
|
||||
@@ -1494,6 +1498,100 @@ class ChatViewModelTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testServerRequestClarifyReplay_restoresLockedAnswersAndMergesRemainingAnswer() =
|
||||
runTest {
|
||||
val (viewModel, sessionId) = createViewModelWithSession()
|
||||
|
||||
mockEventsFlow.emit(
|
||||
WsEvent.ServerRequest(
|
||||
id = "srq-clarify-replay",
|
||||
method = "clarify",
|
||||
params =
|
||||
mapOf(
|
||||
"session_id" to sessionId,
|
||||
"questions" to
|
||||
listOf(
|
||||
mapOf("qid" to "q0", "question" to "First?", "choices" to listOf("yes")),
|
||||
mapOf("qid" to "q1", "question" to "Second?", "choices" to emptyList<String>()),
|
||||
),
|
||||
"answers" to mapOf("q0" to "yes", "invalid" to 42),
|
||||
),
|
||||
replayed = true,
|
||||
),
|
||||
)
|
||||
advanceUntilIdle()
|
||||
|
||||
val clarify = viewModel.uiState.value.clarifyRequest
|
||||
assertEquals("srq-clarify-replay", clarify?.serverRequestId)
|
||||
assertEquals(mapOf("q0" to "yes"), clarify?.lockedAnswers)
|
||||
assertEquals(2, clarify?.resolvedQuestions?.size)
|
||||
|
||||
viewModel.respondToClarifyBatch(mapOf("q0" to "", "q1" to "new answer"))
|
||||
advanceUntilIdle()
|
||||
|
||||
verify {
|
||||
HermesWsClient.respondToServerRequest(
|
||||
"srq-clarify-replay",
|
||||
withArg { result ->
|
||||
val answers = result.jsonObject["answers"]?.jsonObject
|
||||
assertEquals("yes", answers?.get("q0")?.jsonPrimitive?.content)
|
||||
assertEquals("new answer", answers?.get("q1")?.jsonPrimitive?.content)
|
||||
assertFalse(answers?.containsKey("invalid") == true)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serverRequestCancel_clearsOnlyMatchingClarifyPrompt() =
|
||||
runTest {
|
||||
val (viewModel, sessionId) = createViewModelWithSession()
|
||||
mockEventsFlow.emit(
|
||||
WsEvent.ServerRequest(
|
||||
id = "srq-clarify-cancel",
|
||||
method = "clarify",
|
||||
params =
|
||||
mapOf(
|
||||
"session_id" to sessionId,
|
||||
"question" to "Continue?",
|
||||
),
|
||||
),
|
||||
)
|
||||
advanceUntilIdle()
|
||||
assertEquals(
|
||||
"srq-clarify-cancel",
|
||||
viewModel.uiState.value.clarifyRequest
|
||||
?.serverRequestId,
|
||||
)
|
||||
|
||||
mockEventsFlow.emit(
|
||||
WsEvent.ServerRequestCancelled(
|
||||
id = "srq-other",
|
||||
method = "clarify",
|
||||
reason = "timeout",
|
||||
sessionId = sessionId,
|
||||
),
|
||||
)
|
||||
advanceUntilIdle()
|
||||
assertEquals(
|
||||
"srq-clarify-cancel",
|
||||
viewModel.uiState.value.clarifyRequest
|
||||
?.serverRequestId,
|
||||
)
|
||||
|
||||
mockEventsFlow.emit(
|
||||
WsEvent.ServerRequestCancelled(
|
||||
id = "srq-clarify-cancel",
|
||||
method = "clarify",
|
||||
reason = "timeout",
|
||||
sessionId = sessionId,
|
||||
),
|
||||
)
|
||||
advanceUntilIdle()
|
||||
assertNull(viewModel.uiState.value.clarifyRequest)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClarifyRequestCustomResponse() =
|
||||
runTest {
|
||||
|
||||
Reference in new issue
Block a user