6 files changed
+113
-56
No files matched your search
@@ -342,8 +342,6 @@ class RawRpcParamsRatchetTest {
|
||||
"ui/chat/ChatViewModel.kt::FILE_ATTACH",
|
||||
"ui/chat/ChatViewModel.kt::IMAGE_ATTACH_BYTES",
|
||||
"ui/chat/ChatViewModel.kt::SLASH_EXEC",
|
||||
"ui/process/ProcessesViewModel.kt::PROCESS_KILL",
|
||||
"ui/process/ProcessesViewModel.kt::PROCESS_LIST",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.m57.hermescontrol.data.ws.contract
|
||||
|
||||
import com.m57.hermescontrol.data.remote.OkHttpProvider
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class ProcessTypesTest {
|
||||
@Test
|
||||
fun killEncodesExactlySessionAndProcessIds() {
|
||||
val encoded =
|
||||
OkHttpProvider.json.encodeToJsonElement(
|
||||
ProcessKillParams.serializer(),
|
||||
ProcessKillParams(sessionId = "s1", processId = "p1"),
|
||||
)
|
||||
assertEquals(
|
||||
buildJsonObject {
|
||||
put("session_id", "s1")
|
||||
put("process_id", "p1")
|
||||
},
|
||||
encoded,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun descriptorsAreRegisteredWithContractNames() {
|
||||
assertTrue(RpcMethods.PROCESS_LIST in RpcMethods.all)
|
||||
assertTrue(RpcMethods.PROCESS_KILL in RpcMethods.all)
|
||||
assertEquals("process.list", RpcMethods.PROCESS_LIST.name)
|
||||
assertEquals("process.kill", RpcMethods.PROCESS_KILL.name)
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,23 @@ package com.m57.hermescontrol.ui.process
|
||||
import com.m57.hermescontrol.data.model.ProcessInfo
|
||||
import com.m57.hermescontrol.data.session.ActiveSessionHolder
|
||||
import com.m57.hermescontrol.data.ws.HermesWsClient
|
||||
import com.m57.hermescontrol.data.ws.WsMethods
|
||||
import io.mockk.every
|
||||
import com.m57.hermescontrol.data.ws.contract.ProcessKillParams
|
||||
import com.m57.hermescontrol.data.ws.contract.RpcMethods
|
||||
import com.m57.hermescontrol.data.ws.contract.SessionIdParams
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockkObject
|
||||
import io.mockk.slot
|
||||
import io.mockk.unmockkAll
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
@@ -42,34 +48,40 @@ class ProcessesViewModelTest {
|
||||
|
||||
@Test
|
||||
fun `process list parses running and exited entries`() {
|
||||
val raw =
|
||||
mapOf(
|
||||
"processes" to
|
||||
listOf(
|
||||
mapOf(
|
||||
"session_id" to "p1",
|
||||
"command" to "python train.py --epochs 10",
|
||||
"pid" to 1234,
|
||||
"status" to "running",
|
||||
"uptime_seconds" to 95,
|
||||
"cwd" to "/work",
|
||||
),
|
||||
mapOf(
|
||||
"session_id" to "p2",
|
||||
"command" to "echo done",
|
||||
"status" to "exited",
|
||||
"exit_code" to 0,
|
||||
),
|
||||
),
|
||||
)
|
||||
val raw: JsonElement =
|
||||
buildJsonObject {
|
||||
put(
|
||||
"processes",
|
||||
buildJsonArray {
|
||||
add(
|
||||
buildJsonObject {
|
||||
put("session_id", "p1")
|
||||
put("command", "python train.py --epochs 10")
|
||||
put("pid", 1234)
|
||||
put("status", "running")
|
||||
put("uptime_seconds", 95)
|
||||
put("cwd", "/work")
|
||||
},
|
||||
)
|
||||
add(
|
||||
buildJsonObject {
|
||||
put("session_id", "p2")
|
||||
put("command", "echo done")
|
||||
put("status", "exited")
|
||||
put("exit_code", 0)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
every {
|
||||
HermesWsClient.request(WsMethods.PROCESS_LIST, any())
|
||||
} returns CompletableDeferred(raw)
|
||||
val listParams = slot<SessionIdParams>()
|
||||
coEvery { HermesWsClient.call(RpcMethods.PROCESS_LIST, capture(listParams), any(), any()) } returns raw
|
||||
|
||||
val vm = ProcessesViewModel()
|
||||
vm.load()
|
||||
|
||||
assertEquals(SessionIdParams("sess-1"), listParams.captured)
|
||||
val procs = vm.uiState.value.processes
|
||||
assertEquals(2, procs.size)
|
||||
|
||||
@@ -90,26 +102,20 @@ class ProcessesViewModelTest {
|
||||
@Test
|
||||
fun `kill issues process kill with session-scoped params then refreshes`() {
|
||||
var listCalls = 0
|
||||
val killParamsSlot = slot<Map<String, Any>>()
|
||||
val killParamsSlot = slot<ProcessKillParams>()
|
||||
|
||||
every {
|
||||
HermesWsClient.request(WsMethods.PROCESS_LIST, any())
|
||||
} answers {
|
||||
coEvery { HermesWsClient.call(RpcMethods.PROCESS_LIST, any(), any(), any()) } answers {
|
||||
listCalls++
|
||||
CompletableDeferred(mapOf("processes" to emptyList<Any>()))
|
||||
buildJsonObject { put("processes", JsonArray(emptyList())) }
|
||||
}
|
||||
every {
|
||||
HermesWsClient.request(WsMethods.PROCESS_KILL, capture(killParamsSlot))
|
||||
} answers {
|
||||
CompletableDeferred(mapOf("killed" to true))
|
||||
coEvery { HermesWsClient.call(RpcMethods.PROCESS_KILL, capture(killParamsSlot), any(), any()) } answers {
|
||||
buildJsonObject { put("killed", true) }
|
||||
}
|
||||
|
||||
val vm = ProcessesViewModel()
|
||||
vm.kill("p1")
|
||||
|
||||
val killParams = killParamsSlot.captured
|
||||
assertEquals("p1", killParams["process_id"])
|
||||
assertEquals("sess-1", killParams["session_id"])
|
||||
assertEquals(ProcessKillParams(sessionId = "sess-1", processId = "p1"), killParamsSlot.captured)
|
||||
// list is called once on init (session emitted) + once after kill refresh
|
||||
assertTrue(listCalls >= 2)
|
||||
assertNull(vm.uiState.value.killingId)
|
||||
|
||||
Reference in new issue
Block a user