// // CreateReminderToolTests.swift // PocheTests // // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // import Foundation import SwiftData import Testing @testable import Poche /// The pattern tool, tested with invalid, missing and hostile arguments /// (CLAUDE.md §10). Nothing here may touch EventKit: the tool only /// proposes; execution happens after user confirmation. @MainActor struct CreateReminderToolTests { private func makeFixture() throws -> (tool: CreateReminderTool, confirm: ConfirmCenter) { let container = try ModelContainer( for: Note.self, TaskItem.self, ConversationRecord.self, TurnRecord.self, configurations: ModelConfiguration(isStoredInMemoryOnly: true) ) let store = PocheStore(container: container) let executor = ActionExecutor(bridge: EventKitBridge(), store: store) let confirm = ConfirmCenter(executor: executor) return (CreateReminderTool(confirm: confirm), confirm) } @Test func validArgumentsProposeButNeverWrite() async throws { let (tool, confirm) = try makeFixture() let output = try await tool.call( arguments: .init(title: "Appeler le dentiste", dueDate: "2030-06-01T10:00:00", list: nil) ) #expect(confirm.pending.count == 1) #expect(confirm.pending.first?.status == .waiting) // The tool must never let the model believe the write happened. #expect(output.contains("confirmer")) #expect(output.contains("Rien n'est encore créé")) } @Test func emptyTitleIsRejected() async throws { let (tool, confirm) = try makeFixture() let output = try await tool.call( arguments: .init(title: " ", dueDate: "2030-06-01T10:00:00", list: nil) ) #expect(confirm.pending.isEmpty) #expect(output.contains("Erreur")) } @Test func pastDateIsRejected() async throws { let (tool, confirm) = try makeFixture() let output = try await tool.call( arguments: .init(title: "Trop tard", dueDate: "2001-01-01T10:00:00", list: nil) ) #expect(confirm.pending.isEmpty) #expect(output.contains("Erreur")) } @Test func garbageDateIsRejected() async throws { let (tool, confirm) = try makeFixture() let output = try await tool.call( arguments: .init(title: "Rappel", dueDate: "mardi prochain", list: nil) ) #expect(confirm.pending.isEmpty) #expect(output.contains("Erreur")) } @Test func hostileTitleIsCapped() async throws { let (tool, confirm) = try makeFixture() let hostile = String(repeating: "A", count: 10_000) _ = try await tool.call( arguments: .init(title: hostile, dueDate: "2030-06-01T10:00:00", list: nil) ) guard case .reminder(let draft)? = confirm.pending.first?.payload else { Issue.record("Expected a reminder proposal") return } #expect(draft.title.count <= ToolInput.maxTitleLength) } @Test func dismissingRemovesTheProposal() async throws { let (tool, confirm) = try makeFixture() _ = try await tool.call( arguments: .init(title: "À annuler", dueDate: "2030-06-01T10:00:00", list: nil) ) guard let action = confirm.pending.first else { Issue.record("Expected a pending proposal") return } confirm.dismiss(action) #expect(confirm.pending.isEmpty) } }