// // OpenRouterModelsTests.swift // oAITests // // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 // Copyright (C) 2026 Rune Olsen import Testing import Foundation @testable import oAI @Suite("OpenRouterModels decoding") struct OpenRouterModelsTests { // MARK: - Request-side: APIMessage.MessageContent (string-or-array union) @Test("Request message content decodes a plain string") func requestContentPlainString() throws { let json = #"{"role":"user","content":"hello"}"#.data(using: .utf8)! let message = try JSONDecoder().decode(OpenRouterChatRequest.APIMessage.self, from: json) guard case .string(let text) = message.content else { Issue.record("Expected .string case") return } #expect(text == "hello") } @Test("Request message content decodes an array of content items") func requestContentArray() throws { let json = #"{"role":"user","content":[{"type":"text","text":"hi"}]}"#.data(using: .utf8)! let message = try JSONDecoder().decode(OpenRouterChatRequest.APIMessage.self, from: json) guard case .array(let items) = message.content else { Issue.record("Expected .array case") return } #expect(items.count == 1) } // MARK: - Request-side: ContentItem (text / image union, with string fallback) @Test("ContentItem decodes a text block") func contentItemText() throws { let json = #"{"type":"text","text":"hi there"}"#.data(using: .utf8)! let item = try JSONDecoder().decode(OpenRouterChatRequest.APIMessage.ContentItem.self, from: json) guard case .text(let text) = item else { Issue.record("Expected .text case") return } #expect(text == "hi there") } @Test("ContentItem decodes an image_url block") func contentItemImage() throws { let json = #"{"type":"image_url","image_url":{"url":"https://example.com/x.png"}}"#.data(using: .utf8)! let item = try JSONDecoder().decode(OpenRouterChatRequest.APIMessage.ContentItem.self, from: json) guard case .image(let image) = item else { Issue.record("Expected .image case") return } #expect(image.imageUrl.url == "https://example.com/x.png") } @Test("ContentItem falls back to .text for a bare string") func contentItemBareStringFallback() throws { let json = #""just text""#.data(using: .utf8)! let item = try JSONDecoder().decode(OpenRouterChatRequest.APIMessage.ContentItem.self, from: json) guard case .text(let text) = item else { Issue.record("Expected .text fallback case") return } #expect(text == "just text") } // MARK: - Response-side: Choice.MessageContent (plain string vs content-block array) @Test("Response message content decodes a plain string with no image blocks") func responseContentPlainString() throws { let json = #"{"role":"assistant","content":"the answer"}"#.data(using: .utf8)! let content = try JSONDecoder().decode(OpenRouterChatResponse.Choice.MessageContent.self, from: json) #expect(content.content == "the answer") #expect(content.contentBlockImages.isEmpty) } @Test("Response message content extracts text and images from a content-block array") func responseContentBlockArrayWithImage() throws { let json = """ { "role": "assistant", "content": [ {"type": "text", "text": "here you go"}, {"type": "image_url", "image_url": {"url": "https://example.com/out.png"}} ] } """.data(using: .utf8)! let content = try JSONDecoder().decode(OpenRouterChatResponse.Choice.MessageContent.self, from: json) #expect(content.content == "here you go") #expect(content.contentBlockImages.count == 1) #expect(content.contentBlockImages.first?.imageUrl.url == "https://example.com/out.png") } @Test("Response message content with no content field at all decodes to nil content, no images") func responseContentMissing() throws { let json = #"{"role":"assistant"}"#.data(using: .utf8)! let content = try JSONDecoder().decode(OpenRouterChatResponse.Choice.MessageContent.self, from: json) #expect(content.content == nil) #expect(content.contentBlockImages.isEmpty) } // MARK: - Streaming: StreamChoice.Delta (same string-or-block-array shape, streamed) @Test("Stream delta decodes a plain string content chunk") func streamDeltaPlainString() throws { let json = #"{"role":"assistant","content":"partial"}"#.data(using: .utf8)! let delta = try JSONDecoder().decode(OpenRouterStreamChunk.StreamChoice.Delta.self, from: json) #expect(delta.content == "partial") #expect(delta.contentBlockImages.isEmpty) } @Test("Stream delta extracts images from a content-block array") func streamDeltaContentBlockArrayWithImage() throws { let json = """ { "content": [ {"type": "image_url", "image_url": {"url": "https://example.com/stream.png"}} ] } """.data(using: .utf8)! let delta = try JSONDecoder().decode(OpenRouterStreamChunk.StreamChoice.Delta.self, from: json) #expect(delta.contentBlockImages.count == 1) #expect(delta.contentBlockImages.first?.imageUrl.url == "https://example.com/stream.png") } }