110 lines
4.5 KiB
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])
|
|
}
|
|
}
|