import Foundation
// MARK: - Test harness
@main
enum ChatParsingTests {
static var failures = 0
static func check(_ label: String, _ got: String, _ expected: String) {
if got == expected {
print(" ✓ \(label)")
} else {
print(" ✗ \(label)")
print(" got: \(got.debugDescription)")
print(" expected: \(expected.debugDescription)")
failures += 1
}
}
static func checkTrue(_ label: String, _ value: Bool) {
if value { print(" ✓ \(label)") }
else { print(" ✗ \(label)"); failures += 1 }
}
// MARK: - Entry point
static func main() async {
// ── Unit tests (no network) ──────────────────────────────────────────
print("LocalChat.normaliseURL")
check("strips trailing slash", LocalChat.normaliseURL("http://localhost:11434/"), "http://localhost:11434")
check("strips /api suffix", LocalChat.normaliseURL("http://localhost:11434/api"), "http://localhost:11434")
check("strips /v1 suffix", LocalChat.normaliseURL("http://localhost:1234/v1"), "http://localhost:1234")
check("no-op clean URL", LocalChat.normaliseURL("http://localhost:11434"), "http://localhost:11434")
check("trims whitespace", LocalChat.normaliseURL(" http://localhost:11434 "), "http://localhost:11434")
print("LocalChat.parseSSEDelta")
let sseData = #"data: {"id":"1","choices":[{"delta":{"content":"hello"}}]}"#
check("parses delta", LocalChat.parseSSEDelta(sseData) ?? "", "hello")
checkTrue("ignores [DONE]", LocalChat.parseSSEDelta("data: [DONE]") == nil)
checkTrue("ignores non-data", LocalChat.parseSSEDelta(": heartbeat") == nil)
checkTrue("ignores null content",
LocalChat.parseSSEDelta(#"data: {"choices":[{"delta":{"content":null}}]}"#) == nil)
checkTrue("ignores missing content",
LocalChat.parseSSEDelta(#"data: {"choices":[{"delta":{}}]}"#) == nil)
print("LocalChat.filterThinkingBlocks")
check("removes closed block",
LocalChat.filterThinkingBlocks("internalanswer"), "answer")
check("no-op without block",
LocalChat.filterThinkingBlocks("hello"), "hello")
check("multiline block",
LocalChat.filterThinkingBlocks("\nstep1\nstep2\nresult"), "result")
print("LocalChat.progressiveFilter")
check("open block → hide",
LocalChat.progressiveFilter("\nstep one"), "")
check("open after text → keep prefix",
LocalChat.progressiveFilter("visiblehidden"), "visible")
check("closed block removed",
LocalChat.progressiveFilter("doneanswer"), "answer")
print("ChatMarkdown.parse")
let blocks = ChatMarkdown.parse(
"## Hello\n\nThis is a paragraph.\n\n- item 1\n- item 2\n\n```swift\nlet x = 1\n```")
checkTrue("heading count", blocks.filter { if case .heading = $0 { return true }; return false }.count == 1)
checkTrue("paragraph count", blocks.filter { if case .paragraph = $0 { return true }; return false }.count == 1)
checkTrue("list item count", blocks.filter { if case .listItem = $0 { return true }; return false }.count == 2)
checkTrue("code block count", blocks.filter { if case .codeBlock = $0 { return true }; return false }.count == 1)
if case .heading(let level, let text) =
blocks.first(where: { if case .heading = $0 { return true }; return false })! {
checkTrue("heading level 2", level == 2)
checkTrue("heading text", text == "Hello")
} else { print(" ✗ heading not found"); failures += 1 }
print("ChatMarkdown.parse — extended")
// Numbered list preserves number
let numBlocks = ChatMarkdown.parse("1. first\n2. second")
let numItems = numBlocks.filter { if case .listItem = $0 { return true }; return false }
checkTrue("ordered list count", numItems.count == 2)
if case .listItem(let prefix, _, _) = numItems.first! {
checkTrue("ordered prefix is '1.'", prefix == "1.")
}
// Heading requires space after #
checkTrue("heading with space", ChatMarkdown.parse("## Hi").contains { if case .heading = $0 { return true }; return false })
checkTrue("#nospace is paragraph", ChatMarkdown.parse("#nospace").contains { if case .paragraph = $0 { return true }; return false })
// Nested list indent
let nested = ChatMarkdown.parse("- top\n - nested")
let items = nested.filter { if case .listItem = $0 { return true }; return false }
checkTrue("nested list count", items.count == 2)
if case .listItem(_, _, let indent) = items[1] { checkTrue("nested indent = 1", indent == 1) }
// Blockquote
let qBlocks = ChatMarkdown.parse("> quoted text")
checkTrue("blockquote parsed", qBlocks.contains { if case .quote = $0 { return true }; return false })
if case .quote(let text) = qBlocks.first! { checkTrue("quote text", text == "quoted text") }
// Paragraph stops before ordered list
let mixBlocks = ChatMarkdown.parse("intro\n1. item")
checkTrue("paragraph + ordered list", mixBlocks.filter { if case .paragraph = $0 { return true }; return false }.count == 1
&& mixBlocks.filter { if case .listItem = $0 { return true }; return false }.count == 1)
// progressiveFilter hides open think block
check("open think → empty", LocalChat.progressiveFilter("\nhalf"), "")
check("open after text", LocalChat.progressiveFilter("answerhidden"), "answer")
check("closed think removed", LocalChat.progressiveFilter("doneresult"), "result")
// ── End-to-end tests (fake server) ───────────────────────────────────
let baseURL: String = {
guard CommandLine.arguments.count > 1 else { return "" }
return "http://127.0.0.1:\(CommandLine.arguments[1])"
}()
guard !baseURL.isEmpty else {
print("\n(Skipping end-to-end tests — no server port provided.)")
finish()
}
print("LocalChat.fetchModels (fake server)")
let models = await LocalChat.fetchModels(baseURL: baseURL)
checkTrue("models list non-empty", !models.isEmpty)
checkTrue("llama3.2 present", models.contains { $0.id == "llama3.2" })
checkTrue("nomic-embed-text filtered", !models.contains { $0.id == "nomic-embed-text" })
print("LocalChat.streamChat — happy path (fake server)")
var tokens: [String] = []
do {
let response = try await LocalChat.streamChat(
baseURL: baseURL,
model: "llama3.2",
messages: [["role": "user", "content": "hello"]],
onToken: { visible in tokens.append(visible) }
)
checkTrue("sent multiple tokens", tokens.count > 1)
checkTrue("intermediate tokens non-empty", tokens.contains { !$0.isEmpty })
checkTrue("think block removed from response", !response.contains(""))
checkTrue("markdown heading in response", response.contains("## Answer"))
checkTrue("list item in response", response.contains("- **item 1**"))
checkTrue("code block in response", response.contains("```python"))
} catch {
print(" ✗ unexpected error: \(error)")
failures += 1
}
print("LocalChat.streamChat — unknown model (fake server)")
do {
_ = try await LocalChat.streamChat(
baseURL: baseURL,
model: "unknown-model",
messages: [["role": "user", "content": "hello"]],
onToken: { _ in }
)
print(" ✗ should have thrown for unknown model")
failures += 1
} catch let e as LocalChatError {
if case .modelNotFound(let m) = e {
checkTrue("model name in error", m == "unknown-model")
} else {
print(" ✗ wrong error case: \(e)")
failures += 1
}
} catch {
print(" ✗ unexpected error type: \(error)")
failures += 1
}
print("LocalChat.streamChat — unreachable server")
do {
_ = try await LocalChat.streamChat(
baseURL: "http://127.0.0.1:1", // nothing on port 1
model: "llama3.2",
messages: [["role": "user", "content": "hello"]],
onToken: { _ in }
)
print(" ✗ should have thrown for unreachable server")
failures += 1
} catch let e as LocalChatError {
if case .serverUnreachable = e {
print(" ✓ serverUnreachable error")
} else {
print(" ✗ wrong error case: \(e)")
failures += 1
}
} catch {
print(" ✗ unexpected error type: \(error)")
failures += 1
}
finish()
}
// MARK: - Finish
private static func finish() -> Never {
if failures == 0 {
print("\nAll tests passed.")
exit(0)
} else {
print("\n\(failures) test(s) failed.")
exit(1)
}
}
}