spb/poche Public
Agent personnel 100 % on-device — SwiftUI + Apple Foundation Models + EventKit + SwiftData. Aucune API, aucun serveur.
Swift 100%
1//2// CreateReminderToolTests.swift3// PocheTests4//5// Author: Simon-Pierre Boucher6// Contact: contact@spboucher.ai7//89import Foundation10import SwiftData11import Testing12@testable import Poche1314/// The pattern tool, tested with invalid, missing and hostile arguments15/// (CLAUDE.md §10). Nothing here may touch EventKit: the tool only16/// proposes; execution happens after user confirmation.17@MainActor18struct CreateReminderToolTests {19 private func makeFixture() throws -> (tool: CreateReminderTool, confirm: ConfirmCenter) {20 let container = try ModelContainer(21 for: Note.self, TaskItem.self, ConversationRecord.self, TurnRecord.self,22 configurations: ModelConfiguration(isStoredInMemoryOnly: true)23 )24 let store = PocheStore(container: container)25 let executor = ActionExecutor(bridge: EventKitBridge(), store: store)26 let confirm = ConfirmCenter(executor: executor)27 return (CreateReminderTool(confirm: confirm), confirm)28 }2930 @Test func validArgumentsProposeButNeverWrite() async throws {31 let (tool, confirm) = try makeFixture()32 let output = try await tool.call(33 arguments: .init(title: "Appeler le dentiste", dueDate: "2030-06-01T10:00:00", list: nil)34 )3536 #expect(confirm.pending.count == 1)37 #expect(confirm.pending.first?.status == .waiting)38 // The tool must never let the model believe the write happened.39 #expect(output.contains("confirmer"))40 #expect(output.contains("Rien n'est encore créé"))41 }4243 @Test func emptyTitleIsRejected() async throws {44 let (tool, confirm) = try makeFixture()45 let output = try await tool.call(46 arguments: .init(title: " ", dueDate: "2030-06-01T10:00:00", list: nil)47 )4849 #expect(confirm.pending.isEmpty)50 #expect(output.contains("Erreur"))51 }5253 @Test func pastDateIsRejected() async throws {54 let (tool, confirm) = try makeFixture()55 let output = try await tool.call(56 arguments: .init(title: "Trop tard", dueDate: "2001-01-01T10:00:00", list: nil)57 )5859 #expect(confirm.pending.isEmpty)60 #expect(output.contains("Erreur"))61 }6263 @Test func garbageDateIsRejected() async throws {64 let (tool, confirm) = try makeFixture()65 let output = try await tool.call(66 arguments: .init(title: "Rappel", dueDate: "mardi prochain", list: nil)67 )6869 #expect(confirm.pending.isEmpty)70 #expect(output.contains("Erreur"))71 }7273 @Test func hostileTitleIsCapped() async throws {74 let (tool, confirm) = try makeFixture()75 let hostile = String(repeating: "A", count: 10_000)76 _ = try await tool.call(77 arguments: .init(title: hostile, dueDate: "2030-06-01T10:00:00", list: nil)78 )7980 guard case .reminder(let draft)? = confirm.pending.first?.payload else {81 Issue.record("Expected a reminder proposal")82 return83 }84 #expect(draft.title.count <= ToolInput.maxTitleLength)85 }8687 @Test func dismissingRemovesTheProposal() async throws {88 let (tool, confirm) = try makeFixture()89 _ = try await tool.call(90 arguments: .init(title: "À annuler", dueDate: "2030-06-01T10:00:00", list: nil)91 )92 guard let action = confirm.pending.first else {93 Issue.record("Expected a pending proposal")94 return95 }9697 confirm.dismiss(action)98 #expect(confirm.pending.isEmpty)99 }100}101