// // MessageCodableTests.swift // oAITests // // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 // Copyright (C) 2026 Rune Olsen import Testing import Foundation @testable import oAI @Suite("Message Codable + Equatable") struct MessageCodableTests { @Test("Transient fields don't survive an encode/decode round-trip") func transientFieldsDoNotPersist() throws { var message = Message(role: .assistant, content: "hi", isStreaming: true, generatedImages: [Data([0x01])]) message.isStarred = true message.toolCalls = [ToolCallDetail(name: "test_tool", input: "{}", result: nil)] message.thinkingContent = "reasoning..." let data = try JSONEncoder().encode(message) let decoded = try JSONDecoder().decode(Message.self, from: data) #expect(decoded.content == "hi") #expect(decoded.isStreaming == false) #expect(decoded.isStarred == false) #expect(decoded.generatedImages == nil) #expect(decoded.toolCalls == nil) #expect(decoded.thinkingContent == nil) } @Test("Persisted fields survive an encode/decode round-trip") func persistedFieldsSurvive() throws { let message = Message( role: .user, content: "what's the weather", tokens: 42, cost: 0.0012, responseTime: 1.5, wasInterrupted: true, modelId: "claude-sonnet-5" ) let data = try JSONEncoder().encode(message) let decoded = try JSONDecoder().decode(Message.self, from: data) #expect(decoded.id == message.id) #expect(decoded.role == message.role) #expect(decoded.content == message.content) #expect(decoded.tokens == message.tokens) #expect(decoded.cost == message.cost) #expect(decoded.responseTime == message.responseTime) #expect(decoded.wasInterrupted == message.wasInterrupted) #expect(decoded.modelId == message.modelId) } @Test("Custom == ignores role, timestamp, attachments, and modelId") func equalityIgnoresSomeFields() { let base = Message(id: UUID(), role: .user, content: "same", timestamp: Date()) let differentRoleAndModel = Message( id: base.id, role: .assistant, content: "same", timestamp: Date(timeIntervalSince1970: 0), modelId: "some-other-model" ) // Documents actual current behavior: these fields are intentionally // excluded from Message's custom == -- surprising if you don't know it. #expect(base == differentRoleAndModel) } @Test("Custom == does distinguish on content") func equalityDistinguishesContent() { let a = Message(id: UUID(), role: .user, content: "one") let b = Message(id: a.id, role: .user, content: "two") #expect(a != b) } @Test("FileAttachment.typeFromExtension recognizes common types") func attachmentTypeFromExtension() { #expect(FileAttachment.typeFromExtension("photo.PNG") == .image) #expect(FileAttachment.typeFromExtension("photo.jpg") == .image) #expect(FileAttachment.typeFromExtension("doc.pdf") == .pdf) #expect(FileAttachment.typeFromExtension("notes.md") == .text) #expect(FileAttachment.typeFromExtension("noextension") == .text) } @Test("FileAttachment.mimeType matches the detected extension") func attachmentMimeType() { let attachment = FileAttachment(path: "image.webp", type: .image, data: nil) #expect(attachment.mimeType == "image/webp") } }