// // DatabaseServiceTests.swift // oAITests // // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 // Copyright (C) 2026 Rune Olsen import Testing import Foundation @testable import oAI @Suite("DatabaseService migrations, against a throwaway in-memory queue") struct DatabaseServiceMigrationTests { @Test("All v1-v8 tables exist after migration") func allTablesExist() { let db = DatabaseService.makeInMemory() let expectedTables = [ "conversations", "messages", "settings", "command_history", "email_logs", "message_metadata", "message_embeddings", "conversation_embeddings", "conversation_summaries", ] for table in expectedTables { #expect(db.tableExists(table), "expected table \(table) to exist") } } @Test("v4 adds modelId to messages and primaryModel to conversations") func v4AddsModelColumns() { let db = DatabaseService.makeInMemory() #expect(db.columnNames(in: "messages").contains("modelId")) #expect(db.columnNames(in: "conversations").contains("primaryModel")) } @Test("messages table has the expected v1 columns") func messagesTableColumns() { let db = DatabaseService.makeInMemory() let columns = Set(db.columnNames(in: "messages")) let expected: Set = ["id", "conversationId", "role", "content", "tokens", "cost", "timestamp", "sortOrder"] #expect(expected.isSubset(of: columns)) } @Test("message_metadata table has the expected v6 columns") func messageMetadataColumns() { let db = DatabaseService.makeInMemory() let columns = Set(db.columnNames(in: "message_metadata")) #expect(columns == ["message_id", "importance_score", "user_starred", "summary", "chunk_index"]) } @Test("conversation_summaries table has the expected v8 columns") func conversationSummariesColumns() { let db = DatabaseService.makeInMemory() let columns = Set(db.columnNames(in: "conversation_summaries")) #expect(columns == ["id", "conversation_id", "start_message_index", "end_message_index", "summary", "token_count", "created_at", "summary_model"]) } @Test("A nonexistent table reports as absent, not a crash") func nonexistentTableReportsAbsent() { let db = DatabaseService.makeInMemory() #expect(db.tableExists("not_a_real_table") == false) } @Test("Two in-memory instances are isolated from each other") func instancesAreIsolated() throws { let dbA = DatabaseService.makeInMemory() let dbB = DatabaseService.makeInMemory() _ = try dbA.saveConversation(name: "only in A", messages: [Message(role: .user, content: "hi")]) #expect(try dbA.listConversations().count == 1) #expect(try dbB.listConversations().count == 0) } } @Suite("DatabaseService settings CRUD, against a throwaway in-memory queue") struct DatabaseServiceSettingsTests { @Test("Round-trips a plain setting") func roundTripsSetting() { let db = DatabaseService.makeInMemory() db.setSetting(key: "theme", value: "dark") #expect((try? db.loadAllSettings()["theme"]) == "dark") } @Test("Deletes a setting") func deletesSetting() { let db = DatabaseService.makeInMemory() db.setSetting(key: "temp", value: "1") db.deleteSetting(key: "temp") #expect((try? db.loadAllSettings()["temp"]) == nil) } } @Suite("DatabaseService conversation + message persistence, against a throwaway in-memory queue") struct DatabaseServiceConversationTests { @Test("Saving a conversation round-trips its messages via loadConversation") func savesAndLoadsConversation() throws { let db = DatabaseService.makeInMemory() let messages = [ Message(role: .user, content: "hello"), Message(role: .assistant, content: "hi there"), ] let saved = try db.saveConversation(name: "Test Chat", messages: messages) let loaded = try db.loadConversation(id: saved.id) #expect(loaded?.0.name == "Test Chat") #expect(loaded?.1.map(\.content) == ["hello", "hi there"]) } @Test("System messages are excluded from persistence") func systemMessagesExcluded() throws { let db = DatabaseService.makeInMemory() let messages = [ Message(role: .system, content: "tool call"), Message(role: .user, content: "hello"), ] let saved = try db.saveConversation(name: "Test", messages: messages) let loaded = try db.loadConversation(id: saved.id) #expect(loaded?.1.count == 1) #expect(loaded?.1.first?.content == "hello") } }