// // TranslatorTests.swift // Zyquo Router // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // Fixture tests for the translation layer: recorded Anthropic SSE events → // OpenAI chunks, recorded Gemini SSE → OpenAI chunks, CompatAdjuster // request/response tables, request builders. Fixtures follow the transcripts // in docs/ROUTER-RESEARCH.md §3. // import XCTest @testable import ZyquoRouter final class TranslatorTests: XCTestCase { private func chatRequest(_ dict: [String: Any]) throws -> ChatCompletionRequest { try ChatCompletionRequest(body: JSONSerialization.data(withJSONObject: dict)) } private func model( _ id: String, _ provider: ProviderID, reasoning: Bool = false, maxCompletionTokens: Bool = false ) -> AIModel { AIModel( id: id, provider: provider, displayName: id, contextWindow: 128_000, maxOutputTokens: 8192, capabilities: ModelCapabilities(vision: true, tools: true, reasoning: reasoning), pricing: nil, parameterSupport: ParameterSupport(usesMaxCompletionTokens: maxCompletionTokens) ) } private func json(_ data: Data) -> [String: Any] { (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] ?? [:] } // MARK: - Anthropic request building func testAnthropicRequestTranslation() throws { let request = try chatRequest([ "model": "anthropic/claude-x", "temperature": 1.4, "messages": [ ["role": "system", "content": "You are terse."], ["role": "user", "content": "Weather?"], ["role": "assistant", "content": NSNull(), "tool_calls": [[ "id": "call_abc", "type": "function", "function": ["name": "get_weather", "arguments": "{\"location\": \"Paris\"}"], ]]], ["role": "tool", "tool_call_id": "call_abc", "content": "18°C"], ], "tools": [["type": "function", "function": [ "name": "get_weather", "parameters": ["type": "object", "properties": ["location": ["type": "string"]]], ]]], "parallel_tool_calls": false, ]) let body = AnthropicTranslator.buildRequest(request, model: model("claude-x", .anthropic)) XCTAssertEqual(body["model"] as? String, "claude-x") XCTAssertEqual(body["system"] as? String, "You are terse.") XCTAssertEqual(body["max_tokens"] as? Int, 8192, "synthesized from catalog when omitted") XCTAssertEqual(body["temperature"] as? Double, 1.0, "clamped to Anthropic's 0–1") let messages = body["messages"] as! [[String: Any]] XCTAssertEqual(messages.count, 3) let assistantBlocks = messages[1]["content"] as! [[String: Any]] XCTAssertEqual(assistantBlocks[0]["type"] as? String, "tool_use") XCTAssertEqual((assistantBlocks[0]["input"] as? [String: Any])?["location"] as? String, "Paris") let toolResult = (messages[2]["content"] as! [[String: Any]])[0] XCTAssertEqual(toolResult["type"] as? String, "tool_result") XCTAssertEqual(toolResult["tool_use_id"] as? String, "call_abc") let tools = body["tools"] as! [[String: Any]] XCTAssertNotNil(tools[0]["input_schema"]) let toolChoice = body["tool_choice"] as! [String: Any] XCTAssertEqual(toolChoice["type"] as? String, "auto") XCTAssertEqual(toolChoice["disable_parallel_tool_use"] as? Bool, true) } // MARK: - Anthropic stream machine (fixture from research §3.1.4) func testAnthropicStreamToOpenAIChunks() { var machine = AnthropicTranslator.StreamMachine( emitter: ChunkEmitter(model: "anthropic/claude-x"), includeUsage: true ) func consume(_ event: String, _ data: String) -> [[String: Any]] { machine.consume(SSEEvent(event: event, data: data)).payloads.map(json) } let start = consume("message_start", #"{"type":"message_start","message":{"id":"msg_1","usage":{"input_tokens":472,"cache_read_input_tokens":10,"output_tokens":2}}}"#) XCTAssertEqual((start[0]["choices"] as! [[String: Any]])[0]["delta"] as! [String: String], ["role": "assistant", "content": ""]) XCTAssertTrue(consume("ping", #"{"type":"ping"}"#).isEmpty) XCTAssertTrue(consume("content_block_start", #"{"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}"#).isEmpty) let text = consume("content_block_delta", #"{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Okay,"}}"#) XCTAssertEqual(((text[0]["choices"] as! [[String: Any]])[0]["delta"] as! [String: Any])["content"] as? String, "Okay,") let toolStart = consume("content_block_start", #"{"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_1","name":"get_weather","input":{}}}"#) let toolDelta = ((toolStart[0]["choices"] as! [[String: Any]])[0]["delta"] as! [String: Any])["tool_calls"] as! [[String: Any]] XCTAssertEqual(toolDelta[0]["index"] as? Int, 0) XCTAssertEqual(toolDelta[0]["id"] as? String, "toolu_1") XCTAssertEqual((toolDelta[0]["function"] as! [String: Any])["arguments"] as? String, "") XCTAssertTrue(consume("content_block_delta", #"{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":""}}"#).isEmpty, "empty fragments skipped") let args = consume("content_block_delta", #"{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"location\":\"Paris\"}"}}"#) let argsDelta = ((args[0]["choices"] as! [[String: Any]])[0]["delta"] as! [String: Any])["tool_calls"] as! [[String: Any]] XCTAssertNil(argsDelta[0]["id"], "no id repetition on argument deltas") XCTAssertEqual((argsDelta[0]["function"] as! [String: Any])["arguments"] as? String, #"{"location":"Paris"}"#) _ = consume("content_block_stop", #"{"type":"content_block_stop","index":1}"#) let finish = consume("message_delta", #"{"type":"message_delta","delta":{"stop_reason":"tool_use"},"usage":{"output_tokens":89}}"#) XCTAssertEqual((finish[0]["choices"] as! [[String: Any]])[0]["finish_reason"] as? String, "tool_calls") let (stopPayloads, done) = machine.consume(SSEEvent(event: "message_stop", data: #"{"type":"message_stop"}"#)) XCTAssertTrue(done) let usageChunk = json(stopPayloads[0]) XCTAssertEqual((usageChunk["choices"] as? [Any])?.count, 0, "usage chunk has empty choices") let usage = usageChunk["usage"] as! [String: Any] XCTAssertEqual(usage["prompt_tokens"] as? Int, 482, "input + cache reads") XCTAssertEqual(usage["completion_tokens"] as? Int, 89) } // MARK: - Gemini request building + stream machine func testGeminiRequestTranslation() throws { let request = try chatRequest([ "model": "gemini/gemini-x", "max_tokens": 100, "temperature": 0.7, "stop": "\n\n", "messages": [ ["role": "system", "content": "Be brief."], ["role": "user", "content": "Hi"], ["role": "assistant", "content": NSNull(), "tool_calls": [[ "id": "call_1", "type": "function", "function": ["name": "lookup", "arguments": "{\"q\":\"x\"}"], ]]], ["role": "tool", "tool_call_id": "call_1", "content": "plain text result"], ], ]) let body = GeminiTranslator.buildRequest(request, model: model("gemini-x", .gemini)) XCTAssertNotNil(body["systemInstruction"]) let contents = body["contents"] as! [[String: Any]] XCTAssertEqual(contents[1]["role"] as? String, "model", "assistant → model") let functionResponse = ((contents[2]["parts"] as! [[String: Any]])[0]["functionResponse"] as! [String: Any]) XCTAssertEqual(functionResponse["name"] as? String, "lookup", "name resolved from tool_call_id") XCTAssertEqual((functionResponse["response"] as! [String: Any])["result"] as? String, "plain text result", "non-JSON tool output wrapped in an object") let generation = body["generationConfig"] as! [String: Any] XCTAssertEqual(generation["maxOutputTokens"] as? Int, 100) XCTAssertEqual(generation["stopSequences"] as? [String], ["\n\n"]) } func testGeminiStreamToOpenAIChunks() { var machine = GeminiTranslator.StreamMachine( emitter: ChunkEmitter(model: "gemini/gemini-x"), includeUsage: true ) let first = machine.consume(SSEEvent(event: nil, data: #"{"candidates":[{"content":{"parts":[{"text":"The"}],"role":"model"},"index":0}]}"#)).map(json) XCTAssertEqual((first[0]["choices"] as! [[String: Any]])[0]["delta"] as! [String: String], ["role": "assistant", "content": ""], "role chunk synthesized") XCTAssertEqual(((first[1]["choices"] as! [[String: Any]])[0]["delta"] as! [String: Any])["content"] as? String, "The") // functionCall arrives complete → STOP must map to tool_calls. let tool = machine.consume(SSEEvent(event: nil, data: #"{"candidates":[{"content":{"parts":[{"functionCall":{"name":"get_weather","args":{"location":"Paris"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":57,"candidatesTokenCount":12,"thoughtsTokenCount":88,"totalTokenCount":157}}"#)).map(json) let toolStart = ((tool[0]["choices"] as! [[String: Any]])[0]["delta"] as! [String: Any])["tool_calls"] as! [[String: Any]] XCTAssertEqual((toolStart[0]["function"] as! [String: Any])["name"] as? String, "get_weather") XCTAssertTrue((toolStart[0]["id"] as! String).hasPrefix("call_"), "missing id synthesized") let finish = tool[2] XCTAssertEqual((finish["choices"] as! [[String: Any]])[0]["finish_reason"] as? String, "tool_calls", "STOP + functionCall override") let final = machine.finalPayloads().map(json) XCTAssertEqual(final.count, 1, "finish already sent; only usage remains") let usage = final[0]["usage"] as! [String: Any] XCTAssertEqual(usage["prompt_tokens"] as? Int, 57) XCTAssertEqual(usage["completion_tokens"] as? Int, 100, "candidates + thoughts") XCTAssertEqual((usage["completion_tokens_details"] as! [String: Any])["reasoning_tokens"] as? Int, 88) } // MARK: - CompatAdjuster func testCompatAdjusterRequestTables() { let base: [String: Any] = [ "model": "mistral/mistral-x", "messages": [["role": "user", "content": "hi"]], "seed": 42, "max_tokens": 100, "custom_extra": "kept", ] let mistral = CompatAdjuster.adjustRequest(base, model: model("mistral-x", .mistral), stream: true) XCTAssertEqual(mistral["model"] as? String, "mistral-x", "namespace stripped") XCTAssertEqual(mistral["random_seed"] as? Int, 42, "seed renamed") XCTAssertNil(mistral["seed"]) XCTAssertEqual(mistral["custom_extra"] as? String, "kept", "unknown keys pass through (D5)") XCTAssertEqual((mistral["stream_options"] as? [String: Any])?["include_usage"] as? Bool, true) let kimi = CompatAdjuster.adjustRequest( ["model": "kimi/k", "messages": [["role": "user", "content": "hi"]], "temperature": 1.8], model: model("k", .kimi), stream: false ) XCTAssertEqual(kimi["temperature"] as? Double, 1.0, "Moonshot clamps to [0,1]") let cerebras = CompatAdjuster.adjustRequest( ["model": "cerebras/c", "messages": [["role": "user", "content": "hi"]], "max_tokens": 50], model: model("c", .cerebras, maxCompletionTokens: true), stream: false ) XCTAssertEqual(cerebras["max_completion_tokens"] as? Int, 50) XCTAssertNil(cerebras["max_tokens"], "never send both") let xai = CompatAdjuster.adjustRequest( ["model": "xai/g", "messages": [["role": "user", "content": "hi"]], "presence_penalty": 0.5, "stop": ["x"]], model: model("g", .xai, reasoning: true), stream: false ) XCTAssertNil(xai["presence_penalty"], "Grok reasoning models 400 on penalties") XCTAssertNil(xai["stop"]) } func testCompatAdjusterResponseNormalization() { // Together "eos" + Mistral thinking-array + Perplexity . let together = CompatAdjuster.normalizeResponse( ["model": "m", "choices": [["index": 0, "finish_reason": "eos", "message": ["role": "assistant", "content": "hi"]]]], namespacedModel: "together/m", provider: .together ) XCTAssertEqual(together["model"] as? String, "together/m") XCTAssertEqual((together["choices"] as! [[String: Any]])[0]["finish_reason"] as? String, "stop") let mistral = CompatAdjuster.normalizeResponse( ["choices": [["index": 0, "finish_reason": "stop", "message": [ "role": "assistant", "content": [ ["type": "thinking", "thinking": [["type": "text", "text": "hmm"]]], ["type": "text", "text": "answer"], ], ]]]], namespacedModel: "mistral/m", provider: .mistral ) let mistralMessage = (mistral["choices"] as! [[String: Any]])[0]["message"] as! [String: Any] XCTAssertEqual(mistralMessage["content"] as? String, "answer") XCTAssertEqual(mistralMessage["reasoning_content"] as? String, "hmm") let perplexity = CompatAdjuster.normalizeResponse( ["choices": [["index": 0, "finish_reason": "stop", "message": [ "role": "assistant", "content": "reasoning here\nfinal answer", ]]]], namespacedModel: "perplexity/sonar-reasoning", provider: .perplexity ) let perplexityMessage = (perplexity["choices"] as! [[String: Any]])[0]["message"] as! [String: Any] XCTAssertEqual(perplexityMessage["content"] as? String, "final answer") XCTAssertEqual(perplexityMessage["reasoning_content"] as? String, "reasoning here") } func testUsageChunkSwallowedWhenClientDidNotAsk() { let swallowed = CompatAdjuster.normalizeChunk( ["choices": [] as [Any], "usage": ["prompt_tokens": 1, "completion_tokens": 2, "total_tokens": 3]], namespacedModel: "openai/x", provider: .openai, clientWantsUsage: false ) XCTAssertNil(swallowed) } // MARK: - Request parsing func testRequestParsingErrors() { XCTAssertThrowsError(try chatRequest(["messages": [["role": "user", "content": "hi"]]])) XCTAssertThrowsError(try chatRequest(["model": "x"])) XCTAssertThrowsError(try chatRequest([ "model": "x", "n": 3, "messages": [["role": "user", "content": "hi"]], ]), "n>1 rejected router-wide") } }