spb/zyquo-cloud Public MIT
Native macOS AI chat client for 12 cloud providers — your keys, every cloud model, one beautiful chat.
Swift 97.4%
Shell 1.7%
Makefile 1%
1//2// ModelTests.swift3// Zyquo Cloud4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//89import Foundation10import Testing11@testable import ZyquoCloud1213@Suite struct ModelTests {14 @Test func conversationJSONRoundTrip() throws {15 // ISO8601 persistence has whole-second precision; use round dates.16 let date = Date(timeIntervalSince1970: 1_785_400_000)17 var message = Message(role: .user, text: "Hello", createdAt: date)18 message.attachments = [19 Attachment(kind: .image, fileName: "pic.png", data: Data([1, 2, 3]), mimeType: "image/png")20 ]21 var reply = Message(role: .assistant, text: "Hi!", reasoning: "thinking…", createdAt: date)22 reply.usage = TokenUsage(inputTokens: 10, outputTokens: 5)23 reply.citations = [Citation(index: 1, url: URL(string: "https://example.com")!, title: "Example")]2425 let conversation = Conversation(26 title: "Test",27 messages: [message, reply],28 modelID: "gpt-4o-mini",29 provider: .openai,30 systemPrompt: "Be brief.",31 createdAt: date,32 updatedAt: date33 )3435 let encoder = JSONEncoder()36 encoder.dateEncodingStrategy = .iso860137 let decoder = JSONDecoder()38 decoder.dateDecodingStrategy = .iso860139 let data = try encoder.encode(conversation)40 let decoded = try decoder.decode(Conversation.self, from: data)4142 #expect(decoded == conversation)43 #expect(decoded.totalUsage.totalTokens == 15)44 }4546 @Test func pricingCost() {47 let pricing = ModelPricing(inputPerMTok: 2, outputPerMTok: 10)48 #expect(abs(pricing.cost(inputTokens: 1_000_000, outputTokens: 500_000) - 7.0) < 0.0001)49 }5051 @Test func contextBadge() {52 func model(_ ctx: Int) -> AIModel {53 AIModel(54 id: "m", provider: .openai, displayName: "M", contextWindow: ctx,55 maxOutputTokens: nil, capabilities: ModelCapabilities(),56 pricing: nil, parameterSupport: ParameterSupport()57 )58 }59 #expect(model(1_000_000).contextBadge == "1M ctx")60 #expect(model(128_000).contextBadge == "128K ctx")61 }6263 @Test func providerErrorMapping() {64 let unauthorized = ProviderError.from(status: 401, body: Data(), provider: .mistral)65 guard case .invalidAPIKey(let provider) = unauthorized else {66 Issue.record("expected invalidAPIKey")67 return68 }69 #expect(provider == .mistral)7071 let body = #"{"error":{"message":"model not found"}}"#.data(using: .utf8)!72 let notFound = ProviderError.from(status: 404, body: body, provider: .openai)73 guard case .badRequest(_, let message) = notFound else {74 Issue.record("expected badRequest")75 return76 }77 #expect(message == "model not found")78 }7980 @Test @MainActor func cheapestModelPrefersNonReasoning() {81 let catalog = ModelCatalog()82 for provider in ProviderID.builtIn {83 guard let cheapest = catalog.cheapestModel(for: provider) else { continue }84 let plainExists = catalog.models(for: provider)85 .contains { !$0.isLegacy && !$0.capabilities.reasoning }86 if plainExists {87 #expect(!cheapest.capabilities.reasoning,88 "\(provider): utility calls must not use a reasoning model")89 }90 }91 }9293 @Test func everyBuiltInProviderHasBaseURLAndFormat() {94 for provider in ProviderID.builtIn {95 #expect(provider.defaultBaseURL != nil, "\(provider) missing base URL")96 }97 #expect(ProviderID.anthropic.wireFormat == .anthropicMessages)98 #expect(ProviderID.builtIn.count == 12)99 }100}101