// // AIProviderTests.swift // oAITests // // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 // Copyright (C) 2026 Rune Olsen import Testing import Foundation @testable import oAI @Suite("ChatResponse / Usage decoding") struct AIProviderTests { @Test("Usage decodes token counts and cache fields from snake_case keys") func usageDecodesTokenCounts() throws { let json = """ { "prompt_tokens": 100, "completion_tokens": 50, "total_tokens": 150, "cache_creation_input_tokens": 20, "cache_read_input_tokens": 10 } """.data(using: .utf8)! let usage = try JSONDecoder().decode(ChatResponse.Usage.self, from: json) #expect(usage.promptTokens == 100) #expect(usage.completionTokens == 50) #expect(usage.totalTokens == 150) #expect(usage.cacheCreationInputTokens == 20) #expect(usage.cacheReadInputTokens == 10) } @Test("Usage.rawCostUSD is always nil after decoding, even if a matching key were present") func usageRawCostNeverDecoded() throws { // rawCostUSD has no CodingKeys entry at all -- it's only ever set via the // memberwise init, never from API JSON. Confirm decode always yields nil, // which is exactly the "$0.0000 cost" regression this guards against. let json = """ { "prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2, "raw_cost_u_s_d": 5.0 } """.data(using: .utf8)! let usage = try JSONDecoder().decode(ChatResponse.Usage.self, from: json) #expect(usage.rawCostUSD == nil) } @Test("Usage cache fields are nil when absent from the response") func usageMissingCacheFields() throws { let json = #"{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}"#.data(using: .utf8)! let usage = try JSONDecoder().decode(ChatResponse.Usage.self, from: json) #expect(usage.cacheCreationInputTokens == nil) #expect(usage.cacheReadInputTokens == nil) } @Test("ChatResponse decodes core fields and forces toolCalls/generatedImages to nil") func chatResponseDecodesCoreFieldsOnly() throws { let json = """ { "id": "resp-1", "model": "test-model", "content": "hello there", "role": "assistant", "finishReason": "stop", "created": 1700000000 } """.data(using: .utf8)! let response = try JSONDecoder().decode(ChatResponse.self, from: json) #expect(response.id == "resp-1") #expect(response.model == "test-model") #expect(response.content == "hello there") #expect(response.role == "assistant") #expect(response.finishReason == "stop") // Not part of CodingKeys -- always nil straight out of decode. #expect(response.toolCalls == nil) #expect(response.generatedImages == nil) } }