test: cover external chat completion
This commit is contained in:
@@ -117,29 +117,6 @@ private extension TestChatTransportState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suite struct ChatViewModelTests {
|
@Suite struct ChatViewModelTests {
|
||||||
@Test func dedupesDuplicateHistoryMessages() async throws {
|
|
||||||
let ts = Date().timeIntervalSince1970 * 1000
|
|
||||||
let duplicate = AnyCodable([
|
|
||||||
"role": "assistant",
|
|
||||||
"content": [["type": "text", "text": "Same message"]],
|
|
||||||
"timestamp": ts,
|
|
||||||
])
|
|
||||||
let history = ClawdisChatHistoryPayload(
|
|
||||||
sessionKey: "main",
|
|
||||||
sessionId: "sess-main",
|
|
||||||
messages: [duplicate, duplicate],
|
|
||||||
thinkingLevel: "off")
|
|
||||||
|
|
||||||
let transport = TestChatTransport(historyResponses: [history])
|
|
||||||
let vm = await MainActor.run { ClawdisChatViewModel(sessionKey: "main", transport: transport) }
|
|
||||||
|
|
||||||
await MainActor.run { vm.load() }
|
|
||||||
try await waitUntil("bootstrap") { await MainActor.run { !vm.messages.isEmpty } }
|
|
||||||
|
|
||||||
#expect(await MainActor.run { vm.messages.count } == 1)
|
|
||||||
#expect(await MainActor.run { vm.messages.first?.role } == "assistant")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test func streamsAssistantAndClearsOnFinal() async throws {
|
@Test func streamsAssistantAndClearsOnFinal() async throws {
|
||||||
let sessionId = "sess-main"
|
let sessionId = "sess-main"
|
||||||
let history1 = ClawdisChatHistoryPayload(
|
let history1 = ClawdisChatHistoryPayload(
|
||||||
@@ -214,6 +191,94 @@ private extension TestChatTransportState {
|
|||||||
#expect(await MainActor.run { vm.pendingToolCalls.isEmpty })
|
#expect(await MainActor.run { vm.pendingToolCalls.isEmpty })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test func clearsStreamingOnExternalFinalEvent() async throws {
|
||||||
|
let sessionId = "sess-main"
|
||||||
|
let history = ClawdisChatHistoryPayload(
|
||||||
|
sessionKey: "main",
|
||||||
|
sessionId: sessionId,
|
||||||
|
messages: [],
|
||||||
|
thinkingLevel: "off")
|
||||||
|
let transport = TestChatTransport(historyResponses: [history, history])
|
||||||
|
let vm = await MainActor.run { ClawdisChatViewModel(sessionKey: "main", transport: transport) }
|
||||||
|
|
||||||
|
await MainActor.run { vm.load() }
|
||||||
|
try await waitUntil("bootstrap") { await MainActor.run { vm.healthOK && vm.sessionId == sessionId } }
|
||||||
|
|
||||||
|
transport.emit(
|
||||||
|
.agent(
|
||||||
|
ClawdisAgentEventPayload(
|
||||||
|
runId: sessionId,
|
||||||
|
seq: 1,
|
||||||
|
stream: "assistant",
|
||||||
|
ts: Int(Date().timeIntervalSince1970 * 1000),
|
||||||
|
data: ["text": AnyCodable("external stream")])))
|
||||||
|
|
||||||
|
transport.emit(
|
||||||
|
.agent(
|
||||||
|
ClawdisAgentEventPayload(
|
||||||
|
runId: sessionId,
|
||||||
|
seq: 2,
|
||||||
|
stream: "tool",
|
||||||
|
ts: Int(Date().timeIntervalSince1970 * 1000),
|
||||||
|
data: [
|
||||||
|
"phase": AnyCodable("start"),
|
||||||
|
"name": AnyCodable("demo"),
|
||||||
|
"toolCallId": AnyCodable("t1"),
|
||||||
|
"args": AnyCodable(["x": 1]),
|
||||||
|
])))
|
||||||
|
|
||||||
|
try await waitUntil("streaming active") { await MainActor.run { vm.streamingAssistantText == "external stream" } }
|
||||||
|
try await waitUntil("tool call pending") { await MainActor.run { vm.pendingToolCalls.count == 1 } }
|
||||||
|
|
||||||
|
transport.emit(
|
||||||
|
.chat(
|
||||||
|
ClawdisChatEventPayload(
|
||||||
|
runId: "other-run",
|
||||||
|
sessionKey: "main",
|
||||||
|
state: "final",
|
||||||
|
message: nil,
|
||||||
|
errorMessage: nil)))
|
||||||
|
|
||||||
|
try await waitUntil("streaming cleared") { await MainActor.run { vm.streamingAssistantText == nil } }
|
||||||
|
#expect(await MainActor.run { vm.pendingToolCalls.isEmpty })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func clearsStreamingOnExternalErrorEvent() async throws {
|
||||||
|
let sessionId = "sess-main"
|
||||||
|
let history = ClawdisChatHistoryPayload(
|
||||||
|
sessionKey: "main",
|
||||||
|
sessionId: sessionId,
|
||||||
|
messages: [],
|
||||||
|
thinkingLevel: "off")
|
||||||
|
let transport = TestChatTransport(historyResponses: [history, history])
|
||||||
|
let vm = await MainActor.run { ClawdisChatViewModel(sessionKey: "main", transport: transport) }
|
||||||
|
|
||||||
|
await MainActor.run { vm.load() }
|
||||||
|
try await waitUntil("bootstrap") { await MainActor.run { vm.healthOK && vm.sessionId == sessionId } }
|
||||||
|
|
||||||
|
transport.emit(
|
||||||
|
.agent(
|
||||||
|
ClawdisAgentEventPayload(
|
||||||
|
runId: sessionId,
|
||||||
|
seq: 1,
|
||||||
|
stream: "assistant",
|
||||||
|
ts: Int(Date().timeIntervalSince1970 * 1000),
|
||||||
|
data: ["text": AnyCodable("external stream")])))
|
||||||
|
|
||||||
|
try await waitUntil("streaming active") { await MainActor.run { vm.streamingAssistantText == "external stream" } }
|
||||||
|
|
||||||
|
transport.emit(
|
||||||
|
.chat(
|
||||||
|
ClawdisChatEventPayload(
|
||||||
|
runId: "other-run",
|
||||||
|
sessionKey: "main",
|
||||||
|
state: "error",
|
||||||
|
message: nil,
|
||||||
|
errorMessage: "boom")))
|
||||||
|
|
||||||
|
try await waitUntil("streaming cleared") { await MainActor.run { vm.streamingAssistantText == nil } }
|
||||||
|
}
|
||||||
|
|
||||||
@Test func abortRequestsDoNotClearPendingUntilAbortedEvent() async throws {
|
@Test func abortRequestsDoNotClearPendingUntilAbortedEvent() async throws {
|
||||||
let sessionId = "sess-main"
|
let sessionId = "sess-main"
|
||||||
let history = ClawdisChatHistoryPayload(
|
let history = ClawdisChatHistoryPayload(
|
||||||
|
|||||||
Reference in New Issue
Block a user