193 lines
6.8 KiB
Swift
193 lines
6.8 KiB
Swift
import EventKit
|
|
import Foundation
|
|
import MacSyncShared
|
|
import Testing
|
|
@testable import ICalSync
|
|
|
|
@MainActor
|
|
final class FakeApplier: CalendarEventApplying {
|
|
var calendars: [String: EKCalendar] = [:]
|
|
var events: [String: EKEvent] = [:]
|
|
|
|
private(set) var savedCount = 0
|
|
private(set) var removedCount = 0
|
|
private(set) var lastSavedEvent: EKEvent?
|
|
private(set) var lastRemovedEvent: EKEvent?
|
|
var saveError: Error?
|
|
var removeError: Error?
|
|
|
|
let store = EKEventStore()
|
|
|
|
func calendar(withIdentifier id: String) -> EKCalendar? { calendars[id] }
|
|
func event(withIdentifier id: String) -> EKEvent? { events[id] }
|
|
func makeEvent() -> EKEvent { EKEvent(eventStore: store) }
|
|
|
|
func save(_ event: EKEvent, span: EKSpan) throws {
|
|
if let e = saveError { throw e }
|
|
savedCount += 1
|
|
lastSavedEvent = event
|
|
}
|
|
|
|
func remove(_ event: EKEvent, span: EKSpan) throws {
|
|
if let e = removeError { throw e }
|
|
removedCount += 1
|
|
lastRemovedEvent = event
|
|
}
|
|
}
|
|
|
|
@Suite("CalendarSender")
|
|
@MainActor
|
|
struct CalendarSenderTests {
|
|
private func makeItem(action: String, payload: CalendarSendPayload) -> PendingCalendarSend {
|
|
PendingCalendarSend(id: "item-1", action: action, payload: payload, createdAt: "2026-05-13T00:00:00Z")
|
|
}
|
|
|
|
@Test("create_event with valid fields returns .sent")
|
|
func createValid() async {
|
|
let fake = FakeApplier()
|
|
// Seed a calendar
|
|
let cal = EKCalendar(for: .event, eventStore: fake.store)
|
|
fake.calendars["cal-1"] = cal
|
|
|
|
let sender = CalendarSender(applier: fake)
|
|
let item = makeItem(action: "create_event", payload: CalendarSendPayload(
|
|
calendarIdentifier: "cal-1",
|
|
title: "Test",
|
|
startDate: "2026-05-13T10:00:00Z",
|
|
endDate: "2026-05-13T11:00:00Z"
|
|
))
|
|
let result = await sender.apply(item)
|
|
guard case .sent = result else {
|
|
Issue.record("expected .sent, got \(result)")
|
|
return
|
|
}
|
|
#expect(fake.savedCount == 1)
|
|
#expect(fake.lastSavedEvent?.title == "Test")
|
|
}
|
|
|
|
@Test("create_event missing calendarIdentifier returns .failed")
|
|
func createMissingCalendar() async {
|
|
let fake = FakeApplier()
|
|
let sender = CalendarSender(applier: fake)
|
|
let item = makeItem(action: "create_event", payload: CalendarSendPayload(
|
|
title: "Test",
|
|
startDate: "2026-05-13T10:00:00Z",
|
|
endDate: "2026-05-13T11:00:00Z"
|
|
))
|
|
let result = await sender.apply(item)
|
|
guard case .failed(let reason) = result else {
|
|
Issue.record("expected .failed")
|
|
return
|
|
}
|
|
#expect(reason.contains("missing calendarIdentifier"))
|
|
#expect(fake.savedCount == 0)
|
|
}
|
|
|
|
@Test("create_event with unknown calendar returns .failed")
|
|
func createUnknownCalendar() async {
|
|
let fake = FakeApplier()
|
|
let sender = CalendarSender(applier: fake)
|
|
let item = makeItem(action: "create_event", payload: CalendarSendPayload(
|
|
calendarIdentifier: "missing",
|
|
title: "Test",
|
|
startDate: "2026-05-13T10:00:00Z",
|
|
endDate: "2026-05-13T11:00:00Z"
|
|
))
|
|
let result = await sender.apply(item)
|
|
guard case .failed(let reason) = result else {
|
|
Issue.record("expected .failed")
|
|
return
|
|
}
|
|
#expect(reason.contains("calendar not found"))
|
|
}
|
|
|
|
@Test("create_event with invalid startDate returns .failed")
|
|
func createInvalidDate() async {
|
|
let fake = FakeApplier()
|
|
let cal = EKCalendar(for: .event, eventStore: fake.store)
|
|
fake.calendars["cal-1"] = cal
|
|
let sender = CalendarSender(applier: fake)
|
|
let item = makeItem(action: "create_event", payload: CalendarSendPayload(
|
|
calendarIdentifier: "cal-1",
|
|
title: "Test",
|
|
startDate: "not-a-date",
|
|
endDate: "2026-05-13T11:00:00Z"
|
|
))
|
|
let result = await sender.apply(item)
|
|
guard case .failed(let reason) = result else {
|
|
Issue.record("expected .failed")
|
|
return
|
|
}
|
|
#expect(reason.contains("invalid startDate"))
|
|
}
|
|
|
|
@Test("update_event with no eventIdentifier returns .failed")
|
|
func updateMissingId() async {
|
|
let fake = FakeApplier()
|
|
let sender = CalendarSender(applier: fake)
|
|
let item = makeItem(action: "update_event", payload: CalendarSendPayload(title: "Renamed"))
|
|
let result = await sender.apply(item)
|
|
guard case .failed(let reason) = result else {
|
|
Issue.record("expected .failed")
|
|
return
|
|
}
|
|
#expect(reason.contains("missing eventIdentifier"))
|
|
}
|
|
|
|
@Test("update_event with unknown event returns .failed")
|
|
func updateUnknown() async {
|
|
let fake = FakeApplier()
|
|
let sender = CalendarSender(applier: fake)
|
|
let item = makeItem(action: "update_event", payload: CalendarSendPayload(
|
|
eventIdentifier: "ghost",
|
|
title: "Renamed"
|
|
))
|
|
let result = await sender.apply(item)
|
|
guard case .failed(let reason) = result else {
|
|
Issue.record("expected .failed")
|
|
return
|
|
}
|
|
#expect(reason.contains("event not found"))
|
|
}
|
|
|
|
@Test("delete_event with no eventIdentifier returns .failed")
|
|
func deleteMissingId() async {
|
|
let fake = FakeApplier()
|
|
let sender = CalendarSender(applier: fake)
|
|
let item = makeItem(action: "delete_event", payload: CalendarSendPayload())
|
|
let result = await sender.apply(item)
|
|
guard case .failed(let reason) = result else {
|
|
Issue.record("expected .failed")
|
|
return
|
|
}
|
|
#expect(reason.contains("missing eventIdentifier"))
|
|
}
|
|
|
|
@Test("delete_event happy path calls remove")
|
|
func deleteHappy() async {
|
|
let fake = FakeApplier()
|
|
let ev = EKEvent(eventStore: fake.store)
|
|
fake.events["ev-1"] = ev
|
|
let sender = CalendarSender(applier: fake)
|
|
let item = makeItem(action: "delete_event", payload: CalendarSendPayload(eventIdentifier: "ev-1"))
|
|
let result = await sender.apply(item)
|
|
guard case .sent = result else {
|
|
Issue.record("expected .sent, got \(result)")
|
|
return
|
|
}
|
|
#expect(fake.removedCount == 1)
|
|
}
|
|
|
|
@Test("unknown action returns .failed")
|
|
func unknownAction() async {
|
|
let fake = FakeApplier()
|
|
let sender = CalendarSender(applier: fake)
|
|
let item = makeItem(action: "bogus", payload: CalendarSendPayload())
|
|
let result = await sender.apply(item)
|
|
guard case .failed(let reason) = result else {
|
|
Issue.record("expected .failed")
|
|
return
|
|
}
|
|
#expect(reason.contains("unknown action"))
|
|
}
|
|
}
|