SPB Git

spb/poche Public

Agent personnel 100 % on-device — SwiftUI + Apple Foundation Models + EventKit + SwiftData. Aucune API, aucun serveur.

Swift 100%
3.5 KB · 101 lines swift
Raw Blame History
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