Files
Termind/TFMTests/MCPTransferTicketTests.swift
T
2026-09-19 08:12:31 +08:00

93 lines
4.2 KiB
Swift

import AgentKit
import Foundation
import Testing
@testable import Termind
@Suite @MainActor
struct MCPTransferTicketTests {
private let date = Date(timeIntervalSince1970: 1_000_000)
@Test
func ticketMetadataBindsTheInvocationAndExpiresAtExactlyTenMinutes() throws {
let invocation = AgentToolInvocation(
runID: UUID(), call: AgentToolCall(id: "ticket", name: "sftp_upload", arguments: .object([:])))
let first = try MCPTransferTicket(invocation, clientName: "Client", now: date)
let second = try MCPTransferTicket(invocation, clientName: "Client", now: date)
#expect(first.runID == invocation.runID)
#expect(first.invocationID == invocation.id)
#expect(first.clientName == "Client")
#expect(first.id != second.id)
#expect(first.token != second.token)
#expect(first.expiresAt == date.addingTimeInterval(600))
#expect(first.state == "waiting")
#expect(first.finishedAt == nil)
}
@Test(arguments: ["waiting", "paused", "connecting", "transferring", "cleaning", "succeeded"])
func expiryOnlySelectsUnclaimedOrPausedTicketsAtTheBoundary(state: String) throws {
let entry = try ticket(state: state)
let tickets = [entry.info.id: entry]
#expect(tickets.expiredTransferIDs(at: entry.info.expiresAt.addingTimeInterval(-0.001)).isEmpty)
let expired = tickets.expiredTransferIDs(at: entry.info.expiresAt)
#expect(expired == ((state == "waiting" || state == "paused") ? [entry.info.id] : []))
}
@Test
func activityRequiresTheOwningSessionAndRetainedAuditedCompletion() throws {
var entry = try ticket(state: "succeeded")
entry.info.finishedAt = date
var tickets = [entry.info.id: entry]
#expect(!tickets.hasTransferActivity(runID: entry.info.runID, at: date))
entry.info.auditID = UUID()
tickets[entry.info.id] = entry
#expect(!tickets.hasTransferActivity(runID: UUID(), at: date))
#expect(tickets.hasTransferActivity(runID: entry.info.runID, at: date.addingTimeInterval(1_799.999)))
#expect(!tickets.hasTransferActivity(runID: entry.info.runID, at: date.addingTimeInterval(1_800)))
entry.info.state = "cleaning"
entry.info.finishedAt = nil
tickets[entry.info.id] = entry
#expect(tickets.hasTransferActivity(runID: entry.info.runID, at: date.addingTimeInterval(9_000)))
entry.info.state = "paused"
tickets[entry.info.id] = entry
#expect(
tickets.hasTransferActivity(runID: entry.info.runID, at: entry.info.expiresAt.addingTimeInterval(-0.001)))
#expect(!tickets.hasTransferActivity(runID: entry.info.runID, at: entry.info.expiresAt))
}
@Test
func retentionRemovesOldestCompletionsAndPreservesLiveTickets() throws {
var tickets: [UUID: Entry] = [:]
var ids: [UUID] = []
for index in 0..<260 {
var entry = try ticket(state: "succeeded")
entry.info.finishedAt = date.addingTimeInterval(Double(index))
tickets[entry.info.id] = entry
ids.append(entry.info.id)
}
let live = try ticket(state: "cleaning")
tickets[live.info.id] = live
tickets.pruneTransferCompletions(at: date.addingTimeInterval(300))
#expect(tickets.count == 257)
#expect(ids.prefix(4).allSatisfy { tickets[$0] == nil })
#expect(ids.dropFirst(4).allSatisfy { tickets[$0] != nil })
tickets.pruneTransferCompletions(at: date.addingTimeInterval(1_804))
#expect(tickets[ids[4]] == nil)
#expect(tickets[ids[5]] != nil)
#expect(tickets[live.info.id] != nil)
}
private func ticket(state: String) throws -> Entry {
let invocation = AgentToolInvocation(
runID: UUID(), call: AgentToolCall(id: "ticket", name: "sftp_upload", arguments: .object([:])))
var info = try MCPTransferTicket(invocation, clientName: "Client", now: date)
info.state = state
return Entry(info: info)
}
private struct Entry: MCPTransferTicketEntry {
var info: MCPTransferTicket
var live: Bool { ["connecting", "transferring", "cleaning"].contains(info.state) }
}
}