Files
Termind/FrameworkTests/AssistantChatCollaborationPersistenceTests.swift

110 lines
4.5 KiB
Swift

// SPDX-FileCopyrightText: 2026 Kaede Akino
// SPDX-License-Identifier: GPL-3.0-only
import AgentKit
import Foundation
import SwiftData
import Testing
@testable import TermindApplication
@testable import TermindPersistence
@Suite
@MainActor
struct AssistantChatCollaborationPersistenceTests {
@Test
func inMemoryDeliveredReceiptRejectsStaleSave() throws {
let store = InMemoryAssistantChatCollaborationStore()
let receipt = AssistantChatDeliveryReceipt(
sourceConversationID: UUID(), targetConversationID: UUID(),
sourceRunID: UUID(), sourceToolCallID: "send", messageID: UUID(),
state: .queued)
try store.save(receipt)
try store.updateReceipt(messageID: receipt.messageID, state: .delivered, failure: nil)
try store.save(receipt)
let saved = try #require(
try store.receipt(
sourceRunID: receipt.sourceRunID, sourceToolCallID: receipt.sourceToolCallID))
#expect(saved.state == .delivered)
#expect(saved.failure == nil)
}
@Test
func messagePersistenceConfirmsReceiptAcrossContextsWithoutLaterDowngrade() async throws {
let container = try TermindPersistence.makeContainer(inMemory: true)
let receiptStore = SwiftDataAssistantChatCollaborationStore(
context: ModelContext(container))
let repository = SwiftDataAgentRunRepository(context: ModelContext(container))
let sourceConversationID = UUID()
let targetConversationID = UUID()
let sourceRunID = UUID()
let message = AgentTranscriptMessage(
role: .user, text: "Routed message",
origin: AgentMessageOrigin(
sourceConversationID: sourceConversationID, sourceRunID: sourceRunID,
sourceToolCallID: "send"))
let queued = AssistantChatDeliveryReceipt(
sourceConversationID: sourceConversationID,
targetConversationID: targetConversationID,
sourceRunID: sourceRunID,
sourceToolCallID: "send",
messageID: message.id,
state: .queued)
try receiptStore.save(queued)
let now = Date.now
try await repository.save(
AgentRunSnapshot(
conversationID: targetConversationID, state: .completed,
permissionMode: .askForApproval, messages: [message], startedAt: now,
finishedAt: now))
#expect(
try receiptStore.receipt(
sourceRunID: sourceRunID, sourceToolCallID: "send"
)?.state == .delivered)
// A caller may still hold the queued reservation it inserted before the
// repository transaction. Saving that stale value must remain monotonic.
try receiptStore.save(queued)
try receiptStore.updateReceipt(
messageID: message.id, state: .undelivered, failure: "stale callback")
let final = try #require(
try receiptStore.receipt(sourceRunID: sourceRunID, sourceToolCallID: "send"))
#expect(final.state == .delivered)
#expect(final.failure == nil)
}
@Test
func creationRelationsSurviveAStoreReopenAndRemainScopedToRequestedChats() throws {
let directory = FileManager.default.temporaryDirectory.appending(path: UUID().uuidString)
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: directory) }
let url = directory.appending(path: "Termind.store")
let requested = AssistantChatCreationRelation(
conversationID: UUID(), sourceConversationID: UUID(),
sourceRunID: UUID(), sourceToolCallID: "requested")
let unrelated = AssistantChatCreationRelation(
conversationID: UUID(), sourceConversationID: UUID(),
sourceRunID: UUID(), sourceToolCallID: "unrelated")
do {
let container = try TermindPersistence.makeContainer(url: url)
let store = SwiftDataAssistantChatCollaborationStore(
context: ModelContext(container))
try store.save(requested)
try store.save(unrelated)
}
let reopened = try TermindPersistence.makeContainer(url: url)
let restored = SwiftDataAssistantChatCollaborationStore(
context: ModelContext(reopened))
let relations = try restored.creationRelations(
conversationIDs: [requested.conversationID, UUID()])
#expect(relations == [requested.conversationID: requested])
}
}