chore: import upstream snapshot with attribution
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:10:34 +08:00
commit a789495a98
1551 changed files with 718128 additions and 0 deletions
@@ -0,0 +1,265 @@
import Foundation
import Testing
@testable import JCodeKit
/// Scriptable in-memory transport for Connection tests.
actor FakeTransport: WebSocketTransport {
enum Behavior {
case succeed
case failConnect
case unauthorized
}
let behavior: Behavior
private(set) var sentLines: [String] = []
private var incoming: [String] = []
private var waiters: [CheckedContinuation<String?, Never>] = []
private var closed = false
init(behavior: Behavior = .succeed) {
self.behavior = behavior
}
func connect(url: URL, authToken: String) async throws {
if behavior == .failConnect {
throw TransportError.notConnected
}
if behavior == .unauthorized {
throw TransportError.unauthorized
}
}
func send(text: String) async throws {
if closed { throw TransportError.notConnected }
sentLines.append(text)
}
func receiveText() async throws -> String? {
if closed { return nil }
if !incoming.isEmpty {
return incoming.removeFirst()
}
return await withCheckedContinuation { continuation in
waiters.append(continuation)
}
}
func close() async {
closed = true
for waiter in waiters {
waiter.resume(returning: nil)
}
waiters.removeAll()
}
/// Test helper: push a server frame to the client.
func push(_ line: String) {
if let waiter = waiters.first {
waiters.removeFirst()
waiter.resume(returning: line)
} else {
incoming.append(line)
}
}
}
private func makeConnection(transport: FakeTransport) -> Connection {
Connection(
configuration: .init(
gateway: Gateway(host: "test.local"),
authToken: "tok",
maxReconnectAttempts: 1,
baseBackoffSeconds: 0.01
),
makeTransport: { transport }
)
}
@Test func connectSubscribesAndSyncsHistory() async throws {
let transport = FakeTransport()
let connection = makeConnection(transport: transport)
let stream = await connection.start()
var iterator = stream.makeAsyncIterator()
// connecting -> connected
#expect(await iterator.next() == .phase(.connecting))
#expect(await iterator.next() == .phase(.connected))
// Wait for the subscribe + get_history requests to land.
var sent: [String] = []
for _ in 0..<50 {
sent = await transport.sentLines
if sent.count >= 2 { break }
try await Task.sleep(nanoseconds: 10_000_000)
}
#expect(sent.count == 2)
#expect(sent[0].contains("\"type\":\"subscribe\""))
#expect(sent[1].contains("\"type\":\"get_history\""))
await connection.stop()
}
@Test func decodesPushedEvents() async throws {
let transport = FakeTransport()
let connection = makeConnection(transport: transport)
let stream = await connection.start()
var iterator = stream.makeAsyncIterator()
#expect(await iterator.next() == .phase(.connecting))
#expect(await iterator.next() == .phase(.connected))
await transport.push(#"{"type":"text_delta","text":"hi"}"#)
#expect(await iterator.next() == .event(.textDelta(text: "hi")))
// Multiple newline-delimited events in one frame.
await transport.push("{\"type\":\"message_end\"}\n{\"type\":\"done\",\"id\":1}")
#expect(await iterator.next() == .event(.messageEnd))
#expect(await iterator.next() == .event(.done(id: 1)))
await connection.stop()
}
@Test func sendAssignsMonotonicRequestIDs() async throws {
let transport = FakeTransport()
let connection = makeConnection(transport: transport)
let stream = await connection.start()
var iterator = stream.makeAsyncIterator()
_ = await iterator.next()
_ = await iterator.next()
// Wait until the automatic subscribe/get_history sends (IDs 1-2) finish
// so they cannot interleave with our test sends.
for _ in 0..<100 {
if await transport.sentLines.count >= 2 { break }
try await Task.sleep(nanoseconds: 5_000_000)
}
let first = try await connection.send { .message(id: $0, content: "a") }
let second = try await connection.send { .ping(id: $0) }
#expect(second == first + 1)
await connection.stop()
}
@Test func failedConnectReportsFailureAfterRetries() async throws {
let transport = FakeTransport(behavior: .failConnect)
let connection = makeConnection(transport: transport)
let stream = await connection.start()
var phases: [ConnectionPhase] = []
for await output in stream {
if case let .phase(phase) = output {
phases.append(phase)
if case .failed = phase { break }
}
}
#expect(phases.first == .connecting)
#expect(phases.contains(.reconnecting(attempt: 1)))
if case .failed = phases.last {
} else {
Issue.record("expected failed phase, got \(phases)")
}
await connection.stop()
}
@Test func trackSessionIDForResubscribe() async throws {
let transport = FakeTransport()
let connection = makeConnection(transport: transport)
let stream = await connection.start()
var iterator = stream.makeAsyncIterator()
_ = await iterator.next() // connecting
_ = await iterator.next() // connected
await transport.push(#"{"type":"session","session_id":"sess_42"}"#)
#expect(await iterator.next() == .event(.sessionID(sessionID: "sess_42")))
await connection.stop()
}
@Test func sessionCloseRequestStopsReconnecting() async throws {
let transport = FakeTransport()
let connection = makeConnection(transport: transport)
let stream = await connection.start()
var iterator = stream.makeAsyncIterator()
#expect(await iterator.next() == .phase(.connecting))
#expect(await iterator.next() == .phase(.connected))
await transport.push(#"{"type":"session_close_requested","reason":"taken over"}"#)
#expect(
await iterator.next()
== .event(.sessionCloseRequested(reason: "taken over")))
// The loop must terminate with a failed phase instead of reconnecting.
var sawFailed = false
while let output = await iterator.next() {
if case .phase(.reconnecting) = output {
Issue.record("must not reconnect after session_close_requested")
break
}
if case .phase(.failed(let reason)) = output {
#expect(reason == "taken over")
sawFailed = true
break
}
}
#expect(sawFailed)
await connection.stop()
}
@Test func reloadingTriggersFastReconnect() async throws {
let transport = FakeTransport()
let connection = makeConnection(transport: transport)
let stream = await connection.start()
var iterator = stream.makeAsyncIterator()
#expect(await iterator.next() == .phase(.connecting))
#expect(await iterator.next() == .phase(.connected))
await transport.push(#"{"type":"reloading"}"#)
#expect(await iterator.next() == .event(.reloading(newSocket: nil)))
// Simulate the server dropping the socket for the restart.
await transport.close()
// The connection reconnects (the shared FakeTransport accepts again).
var phases: [ConnectionPhase] = []
while let output = await iterator.next() {
if case let .phase(phase) = output {
phases.append(phase)
if phase == .connected { break }
}
}
#expect(phases.contains(.reconnecting(attempt: 1)))
#expect(phases.last == .connected)
await connection.stop()
}
@Test func unauthorizedStopsReconnectingAndAsksForRePair() async throws {
let transport = FakeTransport(behavior: .unauthorized)
let connection = Connection(
configuration: .init(
gateway: Gateway(host: "test.local"),
authToken: "tok",
maxReconnectAttempts: nil, // would retry forever without the 401 short-circuit
baseBackoffSeconds: 0.01
),
makeTransport: { transport }
)
let stream = await connection.start()
var iterator = stream.makeAsyncIterator()
#expect(await iterator.next() == .phase(.connecting))
let final = await iterator.next()
guard case .phase(.failed(let reason))? = final else {
Issue.record("expected .failed phase, got \(String(describing: final))")
return
}
#expect(reason.contains("Re-pair"))
await connection.stop()
}
@@ -0,0 +1,547 @@
import Foundation
import Testing
@testable import JCodeKit
private func run(_ events: [ConnectionOutput], from state: SessionState = SessionState())
-> SessionState
{
events.reduce(state) { SessionReducer.reduce($0, $1) }
}
private func event(_ line: String) -> ConnectionOutput {
.event(try! ServerEvent.decode(line: line))
}
// MARK: - Streaming text
@Test func ackDoesNotMarkProcessing() {
let state = run([event(#"{"type":"ack","id":1}"#)])
#expect(state.isProcessing == false)
}
@Test func streamsAssistantText() {
let state = run([
.phase(.connected),
event(#"{"type":"ack","id":1}"#),
event(#"{"type":"text_delta","text":"Hel"}"#),
event(#"{"type":"text_delta","text":"lo"}"#),
event(#"{"type":"done","id":1}"#),
])
#expect(state.transcript.count == 1)
#expect(state.transcript[0].role == .assistant)
#expect(state.transcript[0].text == "Hello")
#expect(state.transcript[0].isStreaming == false)
#expect(state.isProcessing == false)
}
@Test func userMessageThenResponse() {
var state = SessionState()
state = SessionReducer.reduce(state, intent: .userSentMessage("hi"))
#expect(state.isProcessing)
state = run(
[
event(#"{"type":"text_delta","text":"hey"}"#),
event(#"{"type":"done","id":1}"#),
], from: state)
#expect(state.transcript.map(\.role) == [.user, .assistant])
#expect(state.transcript[1].text == "hey")
}
@Test func textReplaceOverwritesStreamedText() {
let state = run([
event(#"{"type":"text_delta","text":"garbled{{tool"}"#),
event(#"{"type":"text_replace","text":"clean prefix"}"#),
])
#expect(state.transcript[0].text == "clean prefix")
}
@Test func messageEndSplitsTurns() {
let state = run([
event(#"{"type":"text_delta","text":"first"}"#),
event(#"{"type":"message_end"}"#),
event(#"{"type":"text_delta","text":"second"}"#),
event(#"{"type":"done","id":1}"#),
])
#expect(state.transcript.map(\.text) == ["first", "second"])
}
// MARK: - Reasoning
@Test func reasoningStreamsSeparately() {
var state = run([
event(#"{"type":"reasoning_delta","text":"thinking..."}"#)
])
#expect(state.isReasoning)
#expect(state.transcript[0].reasoning == "thinking...")
#expect(state.transcript[0].text == "")
state = run(
[
event(#"{"type":"reasoning_done","duration_secs":2.0}"#),
event(#"{"type":"text_delta","text":"answer"}"#),
], from: state)
#expect(state.isReasoning == false)
#expect(state.transcript[0].text == "answer")
}
// MARK: - Tool lifecycle
@Test func toolLifecycleTransitions() {
var state = run([
event(#"{"type":"tool_start","id":"t1","name":"bash"}"#),
event(#"{"type":"tool_input","delta":"{\"command\":"}"#),
event(#"{"type":"tool_input","delta":"\"ls\"}"}"#),
])
#expect(state.transcript[0].toolCalls.count == 1)
#expect(state.transcript[0].toolCalls[0].status == .streamingInput)
#expect(state.transcript[0].toolCalls[0].input == #"{"command":"ls"}"#)
state = run([event(#"{"type":"tool_exec","id":"t1","name":"bash"}"#)], from: state)
#expect(state.transcript[0].toolCalls[0].status == .running)
state = run(
[event(#"{"type":"tool_done","id":"t1","name":"bash","output":"file.txt"}"#)],
from: state)
#expect(state.transcript[0].toolCalls[0].status == .succeeded)
#expect(state.transcript[0].toolCalls[0].output == "file.txt")
}
@Test func toolFailureRecorded() {
let state = run([
event(#"{"type":"tool_start","id":"t1","name":"bash"}"#),
event(#"{"type":"tool_exec","id":"t1","name":"bash"}"#),
event(#"{"type":"tool_done","id":"t1","name":"bash","output":"","error":"exit 1"}"#),
])
#expect(state.transcript[0].toolCalls[0].status == .failed("exit 1"))
}
@Test func multipleToolsInOneTurn() {
let state = run([
event(#"{"type":"tool_start","id":"t1","name":"read"}"#),
event(#"{"type":"tool_done","id":"t1","name":"read","output":"a"}"#),
event(#"{"type":"tool_start","id":"t2","name":"bash"}"#),
event(#"{"type":"tool_done","id":"t2","name":"bash","output":"b"}"#),
event(#"{"type":"text_delta","text":"done"}"#),
event(#"{"type":"done","id":1}"#),
])
#expect(state.transcript.count == 1)
#expect(state.transcript[0].toolCalls.map(\.name) == ["read", "bash"])
#expect(state.transcript[0].text == "done")
}
// MARK: - Errors and interrupts
@Test func errorSetsBannerAndStopsProcessing() {
var state = SessionReducer.reduce(SessionState(), intent: .userSentMessage("go"))
state = run(
[
event(
#"{"type":"error","id":1,"message":"overloaded","retry_after_secs":15}"#)
], from: state)
#expect(state.errorBanner == "overloaded (retry in 15s)")
#expect(state.isProcessing == false)
state = SessionReducer.reduce(state, intent: .dismissError)
#expect(state.errorBanner == nil)
}
@Test func interruptStopsProcessingAndNotes() {
var state = SessionReducer.reduce(SessionState(), intent: .userSentMessage("go"))
state = run(
[
event(#"{"type":"text_delta","text":"partial"}"#),
event(#"{"type":"interrupted"}"#),
], from: state)
#expect(state.isProcessing == false)
#expect(state.transcript.last?.text == "partial")
#expect(state.transcript.last?.isStreaming == false)
#expect(state.notices.contains { $0.message == "Interrupted" })
}
// MARK: - Notices (notifications / compaction / dismissal)
@Test func notificationBecomesDismissibleNotice() {
let state = run([
event(#"{"type":"notification","from_name":"swarm","message":"build done"}"#)
])
#expect(state.notices.count == 1)
#expect(state.notices[0].kind == .notification)
#expect(state.notices[0].message == "swarm: build done")
}
@Test func notificationWithoutSenderHasNoPrefix() {
let state = run([
event(#"{"type":"notification","message":"heads up"}"#)
])
#expect(state.notices[0].message == "heads up")
}
@Test func foregroundCompactionSurfacesNotice() {
let state = run([
event(#"{"type":"compaction","trigger":"manual","tokens_saved":1234}"#)
])
#expect(state.notices.count == 1)
#expect(state.notices[0].kind == .compaction)
#expect(state.notices[0].message.contains("1234"))
}
@Test func backgroundCompactionIsSilent() {
let state = run([
event(#"{"type":"compaction","trigger":"background","tokens_saved":50}"#)
])
#expect(state.notices.isEmpty)
}
@Test func dismissNoticeRemovesOnlyThatNotice() {
var state = run([
event(#"{"type":"notification","message":"first"}"#),
event(#"{"type":"notification","message":"second"}"#),
])
#expect(state.notices.count == 2)
let firstID = state.notices[0].id
state = SessionReducer.reduce(state, intent: .dismissNotice(firstID))
#expect(state.notices.count == 1)
#expect(state.notices[0].message == "second")
}
@Test func clearedConversationWipesTranscriptButKeepsSession() {
var state = run([
event(#"{"type":"session","session_id":"sess_keep"}"#),
event(#"{"type":"text_delta","text":"stale"}"#),
event(#"{"type":"done","id":1}"#),
])
#expect(!state.transcript.isEmpty)
state = SessionReducer.reduce(state, intent: .clearedConversation)
#expect(state.transcript.isEmpty)
#expect(state.isProcessing == false)
#expect(state.sessionID == "sess_keep")
}
@Test func emptyStreamingStubIsDropped() {
let state = run([
event(#"{"type":"ack","id":1}"#),
event(#"{"type":"interrupted"}"#),
])
#expect(state.transcript.isEmpty)
}
// MARK: - History sync
@Test func historyReplacesTranscript() {
var state = run([
event(#"{"type":"text_delta","text":"stale"}"#)
])
let history = """
{"type":"history","id":2,"session_id":"sess_1","messages":[\
{"role":"user","content":"question"},\
{"role":"assistant","content":"answer"}\
],"provider_model":"claude-sonnet-4","available_models":["a","b"],\
"all_sessions":["sess_1","sess_2"],"total_tokens":[100,50],\
"server_version":"v1","display_title":"T"}
"""
state = run([event(history)], from: state)
#expect(state.transcript.map(\.text) == ["question", "answer"])
#expect(state.sessionID == "sess_1")
#expect(state.modelName == "claude-sonnet-4")
#expect(state.availableModels == ["a", "b"])
#expect(state.allSessions == ["sess_1", "sess_2"])
#expect(state.tokenInput == 100)
#expect(state.tokenOutput == 50)
#expect(state.sessionTitle == "T")
}
@Test func historySkipsEmptyAssistantStubs() {
let history = """
{"type":"history","id":1,"session_id":"s","messages":[\
{"role":"user","content":"q"},\
{"role":"assistant","content":""},\
{"role":"tool","content":"raw tool output"}\
]}
"""
let state = run([event(history)])
#expect(state.transcript.count == 1)
#expect(state.transcript[0].role == .user)
}
@Test func historyMapsToolData() {
let history = """
{"type":"history","id":1,"session_id":"s","messages":[\
{"role":"assistant","content":"used a tool","tool_data":\
{"id":"t1","name":"bash","input":"{}","output":"ok"}}\
]}
"""
let state = run([event(history)])
#expect(state.transcript[0].toolCalls.count == 1)
#expect(state.transcript[0].toolCalls[0].status == .succeeded)
}
// MARK: - Connection phases
@Test func disconnectFinishesStreamingEntries() {
let state = run([
.phase(.connected),
event(#"{"type":"text_delta","text":"part"}"#),
.phase(.reconnecting(attempt: 1)),
])
#expect(state.phase == .reconnecting(attempt: 1))
#expect(state.transcript[0].isStreaming == false)
#expect(state.isProcessing == false)
}
@Test func failureSetsBanner() {
let state = run([.phase(.failed(reason: "Could not reach server"))])
#expect(state.errorBanner == "Could not reach server")
}
@Test func reconnectClearsBannerOnConnected() {
var state = run([.phase(.failed(reason: "down"))])
state = run([.phase(.connected)], from: state)
#expect(state.errorBanner == nil)
}
// MARK: - Session metadata
@Test func sessionRenameAppliesToCurrentSession() {
var state = run([event(#"{"type":"session","session_id":"sess_1"}"#)])
state = run(
[
event(
#"{"type":"session_renamed","session_id":"sess_1","display_title":"Renamed"}"#)
], from: state)
#expect(state.sessionTitle == "Renamed")
state = run(
[
event(
#"{"type":"session_renamed","session_id":"other","display_title":"Nope"}"#)
], from: state)
#expect(state.sessionTitle == "Renamed")
}
@Test func modelChangeUpdatesOrErrors() {
var state = run([event(#"{"type":"model_changed","id":1,"model":"gpt-5"}"#)])
#expect(state.modelName == "gpt-5")
state = run(
[
event(
#"{"type":"model_changed","id":2,"model":"","error":"unknown model"}"#)
], from: state)
#expect(state.errorBanner == "unknown model")
#expect(state.modelName == "gpt-5")
}
@Test func resetClearsEverything() {
var state = run([
event(#"{"type":"text_delta","text":"data"}"#),
event(#"{"type":"tokens","input":5,"output":6}"#),
])
state = SessionReducer.reduce(state, intent: .reset)
#expect(state == SessionState())
}
// MARK: - Queued soft-interrupts
@Test func queuedInterruptTracksPendingState() {
var state = SessionReducer.reduce(SessionState(), intent: .userSentMessage("go"))
state = SessionReducer.reduce(state, intent: .userQueuedInterrupt("also do y"))
#expect(state.hasPendingInterrupts)
#expect(state.pendingInterrupts == ["also do y"])
#expect(state.transcript.last?.isQueued == true)
#expect(state.transcript.last?.text == "also do y")
}
@Test func injectionClearsMatchingQueuedInterrupt() {
var state = SessionReducer.reduce(SessionState(), intent: .userSentMessage("go"))
state = SessionReducer.reduce(state, intent: .userQueuedInterrupt("also do y"))
state = run(
[
event(
#"{"type":"soft_interrupt_injected","content":"also do y","point":"C"}"#)
], from: state)
#expect(state.pendingInterrupts.isEmpty)
#expect(state.transcript.last?.isQueued == false)
#expect(state.transcript.last?.text == "also do y")
}
@Test func injectionFromAnotherClientAppendsEntry() {
let state = run([
event(
#"{"type":"soft_interrupt_injected","content":"external note","display_role":"system","point":"A"}"#
)
])
#expect(state.transcript.count == 1)
#expect(state.transcript[0].role == .system)
#expect(state.transcript[0].text == "external note")
#expect(state.transcript[0].isQueued == false)
}
@Test func cancelledQueuedInterruptsRemovesOptimisticBubbles() {
var state = SessionReducer.reduce(SessionState(), intent: .userSentMessage("go"))
state = SessionReducer.reduce(state, intent: .userQueuedInterrupt("nevermind"))
state = SessionReducer.reduce(state, intent: .cancelledQueuedInterrupts)
#expect(state.pendingInterrupts.isEmpty)
#expect(!state.transcript.contains { $0.isQueued })
#expect(state.transcript.map(\.text) == ["go"])
}
@Test func turnCompletionDrainsPendingInterrupts() {
var state = SessionReducer.reduce(SessionState(), intent: .userSentMessage("go"))
state = SessionReducer.reduce(state, intent: .userQueuedInterrupt("late note"))
state = run(
[
event(#"{"type":"text_delta","text":"answer"}"#),
event(#"{"type":"done","id":1}"#),
], from: state)
#expect(state.pendingInterrupts.isEmpty)
#expect(!state.transcript.contains { $0.isQueued })
}
@Test func historyResyncClearsPendingInterrupts() {
var state = SessionReducer.reduce(SessionState(), intent: .userQueuedInterrupt("queued"))
#expect(state.hasPendingInterrupts)
state = run(
[
event(
#"{"type":"history","id":1,"session_id":"s","messages":[{"role":"user","content":"q"}]}"#
)
], from: state)
#expect(state.pendingInterrupts.isEmpty)
}
// MARK: - Turn-level connection phase
@Test func connectionPhaseTracksAndClears() {
var state = run([event(#"{"type":"connection_phase","phase":"authenticating"}"#)])
#expect(state.serverPhase == "authenticating")
state = run([event(#"{"type":"done","id":1}"#)], from: state)
#expect(state.serverPhase == nil)
state = run([event(#"{"type":"connection_phase","phase":"waiting"}"#)], from: state)
state = run([.phase(.reconnecting(attempt: 1))], from: state)
#expect(state.serverPhase == nil)
}
// MARK: - Retry rollback
@Test func retryRollbackDiscardsPartialOutput() {
var state = SessionReducer.reduce(SessionState(), intent: .userSentMessage("go"))
state = run(
[
event(#"{"type":"reasoning_delta","text":"thinking"}"#),
event(#"{"type":"text_delta","text":"partial answer"}"#),
event(#"{"type":"retry_rollback","attempt":1,"max":5}"#),
], from: state)
// Only the user message survives; the partial assistant entry is discarded.
#expect(state.transcript.map(\.role) == [.user])
#expect(state.isReasoning == false)
#expect(state.statusDetail == "Retrying (1/5)")
// The replayed response streams into a fresh entry without duplication.
state = run(
[
event(#"{"type":"text_delta","text":"clean answer"}"#),
event(#"{"type":"done","id":1}"#),
], from: state)
#expect(state.transcript.map(\.text) == ["go", "clean answer"])
}
@Test func retryRollbackKeepsCommittedTurns() {
let state = run([
event(#"{"type":"text_delta","text":"first"}"#),
event(#"{"type":"message_end"}"#),
event(#"{"type":"text_delta","text":"second partial"}"#),
event(#"{"type":"retry_rollback","attempt":1,"max":3}"#),
])
#expect(state.transcript.map(\.text) == ["first"])
}
// MARK: - Session titles
@Test func renameOfOtherSessionStillRecordsTitle() {
var state = run([event(#"{"type":"session","session_id":"sess_1"}"#)])
state = run(
[
event(
#"{"type":"session_renamed","session_id":"other","display_title":"Other work"}"#)
], from: state)
#expect(state.sessionTitle == nil)
#expect(state.title(forSession: "other") == "Other work")
}
@Test func historyRecordsTitleForSessionList() {
let state = run([
event(
#"{"type":"history","id":1,"session_id":"sess_1","messages":[],"all_sessions":["sess_1","sess_2"],"display_title":"Main"}"#
)
])
#expect(state.title(forSession: "sess_1") == "Main")
#expect(state.title(forSession: "sess_2") == nil)
#expect(state.sessionTitle == "Main")
}
// MARK: - Reasoning effort
@Test func reasoningEffortChangeUpdatesOrErrors() {
var state = run([
event(#"{"type":"reasoning_effort_changed","id":1,"effort":"high"}"#)
])
#expect(state.reasoningEffort == "high")
state = run(
[
event(#"{"type":"reasoning_effort_changed","id":2,"error":"unsupported"}"#)
], from: state)
#expect(state.errorBanner == "unsupported")
#expect(state.reasoningEffort == "high")
}
@Test func historyCarriesReasoningEffortIntoState() {
let state = run([
event(
#"{"type":"history","id":1,"session_id":"s","messages":[],"reasoning_effort":"medium"}"#
)
])
#expect(state.reasoningEffort == "medium")
}
// MARK: - Compaction result
@Test func compactResultSuccessBecomesNotice() {
let state = run([
event(#"{"type":"compact_result","id":1,"message":"Compaction started","success":true}"#)
])
#expect(state.notices.count == 1)
#expect(state.notices[0].kind == .compaction)
#expect(state.errorBanner == nil)
}
@Test func compactResultFailureBecomesError() {
let state = run([
event(#"{"type":"compact_result","id":1,"message":"Nothing to compact","success":false}"#)
])
#expect(state.notices.isEmpty)
#expect(state.errorBanner == "Nothing to compact")
}
// MARK: - Server lifecycle events
@Test func reloadingSurfacesNotice() {
let state = run([event(#"{"type":"reloading"}"#)])
#expect(state.notices.count == 1)
#expect(state.notices[0].message.contains("updating"))
}
@Test func sessionCloseRequestedStopsProcessingWithReason() {
var state = SessionReducer.reduce(SessionState(), intent: .userSentMessage("go"))
state = run(
[
event(#"{"type":"text_delta","text":"partial"}"#),
event(#"{"type":"session_close_requested","reason":"taken over by TUI"}"#),
], from: state)
#expect(state.isProcessing == false)
#expect(state.errorBanner == "taken over by TUI")
#expect(state.transcript.last?.isStreaming == false)
}
+272
View File
@@ -0,0 +1,272 @@
import Foundation
import Testing
@testable import JCodeKit
// MARK: - Gateway / PairURI
@Test func gatewayBuildsEndpoints() {
let gateway = Gateway(host: "devbox.tailnet.ts.net")
#expect(gateway.healthURL.absoluteString == "http://devbox.tailnet.ts.net:7643/health")
#expect(gateway.pairURL.absoluteString == "http://devbox.tailnet.ts.net:7643/pair")
#expect(gateway.webSocketURL.absoluteString == "ws://devbox.tailnet.ts.net:7643/ws")
}
@Test func pairURIParsesQRPayload() {
let payload = PairURI.parse("jcode://pair?host=mybox.ts.net&port=7643&code=123456")
#expect(payload?.gateway.host == "mybox.ts.net")
#expect(payload?.gateway.port == 7643)
#expect(payload?.code == "123456")
}
@Test func pairURIDefaultsPort() {
let payload = PairURI.parse("jcode://pair?host=mybox&code=987654")
#expect(payload?.gateway.port == Gateway.defaultPort)
}
@Test func pairURIRejectsGarbage() {
#expect(PairURI.parse("https://example.com/pair?host=x&code=1") == nil)
#expect(PairURI.parse("jcode://pair?host=&code=1") == nil)
#expect(PairURI.parse("jcode://pair?host=x") == nil)
#expect(PairURI.parse("not a uri") == nil)
}
// MARK: - Request encoding (must match crates/jcode-protocol/src/wire.rs)
private func encodedObject(_ request: Request) throws -> [String: Any] {
let line = try request.encodedLine()
let data = line.data(using: .utf8)!
return try JSONSerialization.jsonObject(with: data) as! [String: Any]
}
@Test func encodesMessageRequest() throws {
let object = try encodedObject(.message(id: 7, content: "hello"))
#expect(object["type"] as? String == "message")
#expect(object["id"] as? UInt64 == 7)
#expect(object["content"] as? String == "hello")
}
@Test func encodesSubscribeWithTargetSession() throws {
let object = try encodedObject(.subscribe(id: 1, targetSessionID: "sess_abc"))
#expect(object["type"] as? String == "subscribe")
#expect(object["target_session_id"] as? String == "sess_abc")
let bare = try encodedObject(.subscribe(id: 2, targetSessionID: nil))
#expect(bare["target_session_id"] == nil)
}
@Test func encodesControlRequests() throws {
#expect(try encodedObject(.cancel(id: 3))["type"] as? String == "cancel")
#expect(try encodedObject(.ping(id: 4))["type"] as? String == "ping")
#expect(try encodedObject(.getHistory(id: 5))["type"] as? String == "get_history")
#expect(try encodedObject(.clear(id: 6))["type"] as? String == "clear")
#expect(
try encodedObject(.cancelSoftInterrupts(id: 8))["type"] as? String
== "cancel_soft_interrupts")
let soft = try encodedObject(.softInterrupt(id: 9, content: "also do x", urgent: true))
#expect(soft["type"] as? String == "soft_interrupt")
#expect(soft["content"] as? String == "also do x")
#expect(soft["urgent"] as? Bool == true)
let resume = try encodedObject(.resumeSession(id: 10, sessionID: "sess_x"))
#expect(resume["type"] as? String == "resume_session")
#expect(resume["session_id"] as? String == "sess_x")
let model = try encodedObject(.setModel(id: 11, model: "claude-sonnet-4"))
#expect(model["type"] as? String == "set_model")
#expect(model["model"] as? String == "claude-sonnet-4")
let rename = try encodedObject(.renameSession(id: 12, title: "My session"))
#expect(rename["type"] as? String == "rename_session")
#expect(rename["title"] as? String == "My session")
}
@Test func encodesReasoningEffortAndCompact() throws {
let effort = try encodedObject(.setReasoningEffort(id: 13, effort: "high"))
#expect(effort["type"] as? String == "set_reasoning_effort")
#expect(effort["id"] as? UInt64 == 13)
#expect(effort["effort"] as? String == "high")
let compact = try encodedObject(.compact(id: 14))
#expect(compact["type"] as? String == "compact")
#expect(compact["id"] as? UInt64 == 14)
}
// MARK: - ServerEvent decoding (fixtures mirror real server output)
@Test func decodesStreamingEvents() throws {
#expect(
try ServerEvent.decode(line: #"{"type":"text_delta","text":"Hel"}"#)
== .textDelta(text: "Hel"))
#expect(
try ServerEvent.decode(line: #"{"type":"reasoning_delta","text":"hmm"}"#)
== .reasoningDelta(text: "hmm"))
#expect(
try ServerEvent.decode(line: #"{"type":"reasoning_done","duration_secs":1.5}"#)
== .reasoningDone(durationSecs: 1.5))
#expect(
try ServerEvent.decode(line: #"{"type":"text_replace","text":"clean"}"#)
== .textReplace(text: "clean"))
#expect(try ServerEvent.decode(line: #"{"type":"message_end"}"#) == .messageEnd)
#expect(try ServerEvent.decode(line: #"{"type":"done","id":3}"#) == .done(id: 3))
#expect(try ServerEvent.decode(line: #"{"type":"interrupted"}"#) == .interrupted)
}
@Test func decodesToolLifecycle() throws {
#expect(
try ServerEvent.decode(line: #"{"type":"tool_start","id":"t1","name":"bash"}"#)
== .toolStart(id: "t1", name: "bash"))
#expect(
try ServerEvent.decode(line: #"{"type":"tool_input","delta":"{\"cmd\""}"#)
== .toolInput(delta: "{\"cmd\""))
#expect(
try ServerEvent.decode(line: #"{"type":"tool_exec","id":"t1","name":"bash"}"#)
== .toolExec(id: "t1", name: "bash"))
#expect(
try ServerEvent.decode(
line: #"{"type":"tool_done","id":"t1","name":"bash","output":"ok"}"#)
== .toolDone(id: "t1", name: "bash", output: "ok", error: nil))
#expect(
try ServerEvent.decode(
line: #"{"type":"tool_done","id":"t2","name":"bash","output":"","error":"boom"}"#)
== .toolDone(id: "t2", name: "bash", output: "", error: "boom"))
}
@Test func decodesErrorAndStatus() throws {
#expect(
try ServerEvent.decode(
line: #"{"type":"error","id":1,"message":"rate limited","retry_after_secs":30}"#)
== .error(id: 1, message: "rate limited", retryAfterSecs: 30))
#expect(
try ServerEvent.decode(line: #"{"type":"tokens","input":1200,"output":340}"#)
== .tokenUsage(input: 1200, output: 340))
#expect(
try ServerEvent.decode(line: #"{"type":"status_detail","detail":"thinking"}"#)
== .statusDetail(detail: "thinking"))
#expect(
try ServerEvent.decode(
line:
#"{"type":"state","id":2,"session_id":"s1","message_count":4,"is_processing":true}"#
) == .state(id: 2, sessionID: "s1", messageCount: 4, isProcessing: true))
}
@Test func decodesSessionEvents() throws {
#expect(
try ServerEvent.decode(line: #"{"type":"session","session_id":"sess_1"}"#)
== .sessionID(sessionID: "sess_1"))
#expect(
try ServerEvent.decode(
line:
#"{"type":"session_renamed","session_id":"sess_1","display_title":"Fix bug"}"#)
== .sessionRenamed(sessionID: "sess_1", displayTitle: "Fix bug"))
#expect(
try ServerEvent.decode(
line: #"{"type":"model_changed","id":5,"model":"gpt-5","provider_name":"openai"}"#)
== .modelChanged(id: 5, model: "gpt-5", error: nil))
}
@Test func decodesHistoryPayload() throws {
let line = """
{"type":"history","id":2,"session_id":"sess_9","messages":[\
{"role":"user","content":"hi"},\
{"role":"assistant","content":"hello!","tool_calls":["bash"]},\
{"role":"assistant","content":"","tool_data":{"id":"t1","name":"read","input":"{}","output":"data"}}\
],"provider_name":"anthropic","provider_model":"claude-sonnet-4",\
"available_models":["claude-sonnet-4","claude-opus-4"],\
"total_tokens":[1500,800],"all_sessions":["sess_9","sess_8"],\
"server_version":"v0.26.11","display_title":"My chat"}
"""
guard case let .history(payload) = try ServerEvent.decode(line: line) else {
Issue.record("expected history event")
return
}
#expect(payload.sessionID == "sess_9")
#expect(payload.messages.count == 3)
#expect(payload.messages[0].role == "user")
#expect(payload.messages[1].toolCalls == ["bash"])
#expect(payload.messages[2].toolData?.name == "read")
#expect(payload.providerModel == "claude-sonnet-4")
#expect(payload.availableModels.count == 2)
#expect(payload.totalTokens == .init(input: 1500, output: 800))
#expect(payload.allSessions == ["sess_9", "sess_8"])
#expect(payload.serverVersion == "v0.26.11")
#expect(payload.displayTitle == "My chat")
}
@Test func unknownEventTypesAreTolerated() throws {
let event = try ServerEvent.decode(
line: #"{"type":"some_future_event","payload":{"x":1}}"#)
#expect(event == .unknown(type: "some_future_event"))
}
@Test func decodesTurnLifecycleSignals() throws {
#expect(
try ServerEvent.decode(line: #"{"type":"connection_phase","phase":"authenticating"}"#)
== .connectionPhase(phase: "authenticating"))
#expect(
try ServerEvent.decode(line: #"{"type":"retry_rollback","attempt":2,"max":5}"#)
== .retryRollback(attempt: 2, max: 5))
#expect(
try ServerEvent.decode(
line:
#"{"type":"soft_interrupt_injected","content":"also fix y","point":"C","tools_skipped":1}"#
)
== .softInterruptInjected(
content: "also fix y", displayRole: nil, point: "C", toolsSkipped: 1))
#expect(
try ServerEvent.decode(
line:
#"{"type":"soft_interrupt_injected","content":"note","display_role":"system","point":"A"}"#
)
== .softInterruptInjected(
content: "note", displayRole: "system", point: "A", toolsSkipped: nil))
}
@Test func decodesEffortAndCompactResponses() throws {
#expect(
try ServerEvent.decode(
line: #"{"type":"reasoning_effort_changed","id":3,"effort":"high"}"#)
== .reasoningEffortChanged(id: 3, effort: "high", error: nil))
#expect(
try ServerEvent.decode(
line: #"{"type":"reasoning_effort_changed","id":4,"error":"unsupported"}"#)
== .reasoningEffortChanged(id: 4, effort: nil, error: "unsupported"))
#expect(
try ServerEvent.decode(
line: #"{"type":"compact_result","id":5,"message":"Compaction started","success":true}"#
)
== .compactResult(id: 5, message: "Compaction started", success: true))
}
@Test func decodesServerLifecycleEvents() throws {
#expect(
try ServerEvent.decode(line: #"{"type":"reloading"}"#)
== .reloading(newSocket: nil))
#expect(
try ServerEvent.decode(line: #"{"type":"reloading","new_socket":"/tmp/jcode.sock"}"#)
== .reloading(newSocket: "/tmp/jcode.sock"))
#expect(
try ServerEvent.decode(
line: #"{"type":"session_close_requested","reason":"taken over"}"#)
== .sessionCloseRequested(reason: "taken over"))
}
@Test func historyCarriesReasoningEffort() throws {
let line =
#"{"type":"history","id":1,"session_id":"s","messages":[],"reasoning_effort":"medium"}"#
guard case let .history(payload) = try ServerEvent.decode(line: line) else {
Issue.record("expected history event")
return
}
#expect(payload.reasoningEffort == "medium")
}
@Test func malformedLinesThrow() {
#expect(throws: WireError.self) {
try ServerEvent.decode(line: "not json")
}
#expect(throws: WireError.self) {
try ServerEvent.decode(line: #"{"no_type":true}"#)
}
}