9fd04b0ce4
Add the owner-only AF_UNIX Reyna CLI privacy host, strict signed-app installation, and typed native routing for Calendar, Contacts, and Reminders.\n\nAdd bounded system-status paths and config-only direct local-service wrappers. Preserve MacMiniMCP pending explicit cutover approval.\n\nApple Notes is intentionally deferred: no native Notes operations, Apple Events declaration, or Automation helper are included; legacy Notes handling remains untouched.
420 lines
24 KiB
Swift
420 lines
24 KiB
Swift
import XCTest
|
||
@testable import ReynaCLIHostCore
|
||
import Foundation
|
||
|
||
// TDD for calendar.events.list and calendar.event.create – fake providers only, no live EventKit
|
||
|
||
final class CalendarEventsTests: XCTestCase {
|
||
|
||
// MARK: - Helper types
|
||
|
||
struct FakeCalendarListForSelection: CalendarListProviding {
|
||
var calendars: [CalendarListItem]
|
||
func listCalendars() throws -> [CalendarListItem] { calendars }
|
||
}
|
||
|
||
struct FakeEventsProvider: CalendarEventsListProviding {
|
||
var events: [CalendarEventItem]
|
||
var shouldThrow: CalendarProviderError? = nil
|
||
|
||
func listEvents(start: Date, end: Date, calendarId: String?, calendarTitle: String?, limit: Int) throws -> [CalendarEventItem] {
|
||
if let err = shouldThrow { throw err }
|
||
var filtered = events
|
||
if let cid = calendarId {
|
||
filtered = filtered.filter { $0.calendar_id == cid }
|
||
} else if let ctitle = calendarTitle {
|
||
filtered = filtered.filter { $0.calendar_title == ctitle }
|
||
}
|
||
return filtered
|
||
}
|
||
}
|
||
|
||
struct SelectingEventsProvider: CalendarEventsListProviding {
|
||
var availableCalendars: [CalendarListItem]
|
||
var events: [CalendarEventItem]
|
||
|
||
func listEvents(start: Date, end: Date, calendarId: String?, calendarTitle: String?, limit: Int) throws -> [CalendarEventItem] {
|
||
if let cid = calendarId {
|
||
guard availableCalendars.contains(where: { $0.id == cid }) else {
|
||
throw CalendarProviderError.invalidRequest("Calendar not found: \(cid)")
|
||
}
|
||
return events.filter { $0.calendar_id == cid }.prefix(limit).map { $0 }
|
||
}
|
||
if let title = calendarTitle {
|
||
let matched = availableCalendars.filter { $0.title == title }
|
||
if matched.isEmpty {
|
||
throw CalendarProviderError.invalidRequest("Calendar not found: \(title)")
|
||
}
|
||
if matched.count > 1 {
|
||
throw CalendarProviderError.invalidRequest("Ambiguous calendar title: \(title) matches \(matched.count) calendars")
|
||
}
|
||
return events.filter { $0.calendar_title == title }.prefix(limit).map { $0 }
|
||
}
|
||
let sorted = events.sorted { $0.start < $1.start }
|
||
return Array(sorted.prefix(limit))
|
||
}
|
||
}
|
||
|
||
struct FakeCreateProvider: CalendarEventCreateProviding {
|
||
var shouldThrow: CalendarProviderError? = nil
|
||
var willReturn: CalendarEventItem
|
||
|
||
func createEvent(title: String, start: Date, end: Date, allDay: Bool, notes: String?, location: String?, calendarId: String?, calendarTitle: String?) throws -> CalendarEventItem {
|
||
if let err = shouldThrow { throw err }
|
||
return willReturn
|
||
}
|
||
}
|
||
|
||
struct SelectingCreateProvider: CalendarEventCreateProviding {
|
||
var availableCalendars: [CalendarListItem]
|
||
var writableIds: Set<String>
|
||
var willReturn: CalendarEventItem
|
||
|
||
func createEvent(title: String, start: Date, end: Date, allDay: Bool, notes: String?, location: String?, calendarId: String?, calendarTitle: String?) throws -> CalendarEventItem {
|
||
if let cid = calendarId {
|
||
guard let cal = availableCalendars.first(where: { $0.id == cid }) else {
|
||
throw CalendarProviderError.invalidRequest("Calendar not found: \(cid)")
|
||
}
|
||
guard writableIds.contains(cal.id) else {
|
||
throw CalendarProviderError.invalidRequest("Calendar is read-only: \(cal.title)")
|
||
}
|
||
return willReturn
|
||
}
|
||
if let t = calendarTitle {
|
||
let matched = availableCalendars.filter { $0.title == t }
|
||
if matched.isEmpty { throw CalendarProviderError.invalidRequest("Calendar not found: \(t)") }
|
||
if matched.count > 1 { throw CalendarProviderError.invalidRequest("Ambiguous calendar title: \(t) matches \(matched.count) calendars") }
|
||
guard writableIds.contains(matched[0].id) else {
|
||
throw CalendarProviderError.invalidRequest("Calendar is read-only: \(matched[0].title)")
|
||
}
|
||
return willReturn
|
||
}
|
||
throw CalendarProviderError.invalidRequest("Calendar must be specified by id or exact unique title")
|
||
}
|
||
}
|
||
|
||
struct DeniedEventsProvider: CalendarEventsListProviding {
|
||
func listEvents(start: Date, end: Date, calendarId: String?, calendarTitle: String?, limit: Int) throws -> [CalendarEventItem] {
|
||
throw CalendarProviderError.permissionRequired
|
||
}
|
||
}
|
||
|
||
struct FailEventsProvider: CalendarEventsListProviding {
|
||
func listEvents(start: Date, end: Date, calendarId: String?, calendarTitle: String?, limit: Int) throws -> [CalendarEventItem] {
|
||
throw CalendarProviderError.unavailable("disk fail")
|
||
}
|
||
}
|
||
|
||
struct DeniedCreateProvider: CalendarEventCreateProviding {
|
||
func createEvent(title: String, start: Date, end: Date, allDay: Bool, notes: String?, location: String?, calendarId: String?, calendarTitle: String?) throws -> CalendarEventItem {
|
||
throw CalendarProviderError.permissionRequired
|
||
}
|
||
}
|
||
|
||
struct MockAuthProvider: CalendarAuthorizationProviding {
|
||
var status: CalendarAuthorizationStatus
|
||
func authorizationStatus() -> CalendarAuthorizationStatus { status }
|
||
func requestFullAccess() throws -> Bool { false }
|
||
}
|
||
|
||
final class CountingCreate: CalendarEventCreateProviding, @unchecked Sendable {
|
||
var count = 0
|
||
func createEvent(title: String, start: Date, end: Date, allDay: Bool, notes: String?, location: String?, calendarId: String?, calendarTitle: String?) throws -> CalendarEventItem {
|
||
count += 1
|
||
return CalendarEventItem(id: "sample", title: "Sample", start: "2026-01-01T10:00:00Z", end: "2026-01-01T11:00:00Z", all_day: false, calendar_id: "cal1", calendar_title: "Home", notes: nil, location: nil)
|
||
}
|
||
}
|
||
|
||
func sampleEvent() -> CalendarEventItem {
|
||
CalendarEventItem(id: "sample", title: "Sample", start: "2026-01-01T10:00:00Z", end: "2026-01-01T11:00:00Z", all_day: false, calendar_id: "cal1", calendar_title: "Home", notes: nil, location: nil)
|
||
}
|
||
|
||
// MARK: - List tests
|
||
|
||
func testEventsListMissingStartReturnsInvalidRequest() {
|
||
let req = Request(id: "e1", operation: "calendar.events.list", arguments: .object(["end": .string("2026-01-02T00:00:00Z")]))
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: []), eventsProvider: FakeEventsProvider(events: []), createProvider: FakeCreateProvider(willReturn: sampleEvent()), authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(resp.ok)
|
||
XCTAssertEqual(resp.error?.code, "invalid_request")
|
||
}
|
||
|
||
func testEventsListInvalidISODate() {
|
||
let args: JSONValue = .object(["start": .string("not-a-date"), "end": .string("2026-01-02T00:00:00Z")])
|
||
let req = Request(id: "e2", operation: "calendar.events.list", arguments: args)
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: []), eventsProvider: FakeEventsProvider(events: []), createProvider: FakeCreateProvider(willReturn: sampleEvent()), authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(resp.ok)
|
||
XCTAssertEqual(resp.error?.code, "invalid_request")
|
||
}
|
||
|
||
func testEventsListStartAfterEnd() {
|
||
let args: JSONValue = .object(["start": .string("2026-01-03T00:00:00Z"), "end": .string("2026-01-02T00:00:00Z")])
|
||
let req = Request(id: "e3", operation: "calendar.events.list", arguments: args)
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: []), eventsProvider: FakeEventsProvider(events: []), createProvider: FakeCreateProvider(willReturn: sampleEvent()), authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(resp.ok)
|
||
XCTAssertEqual(resp.error?.code, "invalid_request")
|
||
}
|
||
|
||
func testEventsListLimitBounded() {
|
||
let argsLow: JSONValue = .object(["start": .string("2026-01-01T00:00:00Z"), "end": .string("2026-01-02T00:00:00Z"), "limit": .number(0)])
|
||
let reqLow = Request(id: "e4", operation: "calendar.events.list", arguments: argsLow)
|
||
let respLow = dispatch(request: reqLow, calendarProvider: FakeCalendarListForSelection(calendars: []), eventsProvider: FakeEventsProvider(events: []), createProvider: FakeCreateProvider(willReturn: sampleEvent()), authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(respLow.ok)
|
||
XCTAssertEqual(respLow.error?.code, "invalid_request")
|
||
|
||
let argsHigh: JSONValue = .object(["start": .string("2026-01-01T00:00:00Z"), "end": .string("2026-01-02T00:00:00Z"), "limit": .number(251)])
|
||
let reqHigh = Request(id: "e5", operation: "calendar.events.list", arguments: argsHigh)
|
||
let respHigh = dispatch(request: reqHigh, calendarProvider: FakeCalendarListForSelection(calendars: []), eventsProvider: FakeEventsProvider(events: []), createProvider: FakeCreateProvider(willReturn: sampleEvent()), authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(respHigh.ok)
|
||
XCTAssertEqual(respHigh.error?.code, "invalid_request")
|
||
}
|
||
|
||
func testEventsListSuccessSortedAndMinimalFields() {
|
||
let ev1 = CalendarEventItem(id: "2", title: "B", start: "2026-01-01T11:00:00Z", end: "2026-01-01T12:00:00Z", all_day: false, calendar_id: "cal1", calendar_title: "Home", notes: "n2", location: "loc2")
|
||
let ev2 = CalendarEventItem(id: "1", title: "A", start: "2026-01-01T10:00:00Z", end: "2026-01-01T11:00:00Z", all_day: false, calendar_id: "cal1", calendar_title: "Home", notes: nil, location: nil)
|
||
let provider = FakeEventsProvider(events: [ev1, ev2])
|
||
let args: JSONValue = .object(["start": .string("2026-01-01T00:00:00Z"), "end": .string("2026-01-02T00:00:00Z"), "limit": .number(10)])
|
||
let req = Request(id: "e6", operation: "calendar.events.list", arguments: args)
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: []), eventsProvider: provider, createProvider: FakeCreateProvider(willReturn: sampleEvent()), authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertTrue(resp.ok)
|
||
XCTAssertEqual(resp.result?.events?.count, 2)
|
||
XCTAssertEqual(resp.result?.events?.first?.id, "1")
|
||
let encoded = try! JSONEncoder().encode(resp)
|
||
let obj = try! JSONSerialization.jsonObject(with: encoded) as! [String: Any]
|
||
let result = obj["result"] as! [String: Any]
|
||
let events = result["events"] as! [[String: Any]]
|
||
for ev in events {
|
||
XCTAssertNotNil(ev["id"])
|
||
XCTAssertNotNil(ev["title"])
|
||
XCTAssertNotNil(ev["start"])
|
||
XCTAssertNotNil(ev["end"])
|
||
XCTAssertNotNil(ev["calendar_id"])
|
||
XCTAssertNotNil(ev["calendar_title"])
|
||
let allowed = Set(["id","title","start","end","all_day","calendar_id","calendar_title","notes","location"])
|
||
XCTAssertTrue(Set(ev.keys).isSubset(of: allowed), "Unexpected keys: \(ev.keys)")
|
||
}
|
||
}
|
||
|
||
func testEventsListCalendarIdWinsOverTitle() {
|
||
let calendars = [
|
||
CalendarListItem(id: "id1", title: "Home", source: "iCloud", type: "caldav"),
|
||
CalendarListItem(id: "id2", title: "Home", source: "Local", type: "local")
|
||
]
|
||
let ev1 = CalendarEventItem(id: "e1", title: "T", start: "2026-01-01T10:00:00Z", end: "2026-01-01T11:00:00Z", all_day: false, calendar_id: "id1", calendar_title: "Home", notes: nil, location: nil)
|
||
let ev2 = CalendarEventItem(id: "e2", title: "T", start: "2026-01-01T11:00:00Z", end: "2026-01-01T12:00:00Z", all_day: false, calendar_id: "id2", calendar_title: "Home", notes: nil, location: nil)
|
||
let provider = SelectingEventsProvider(availableCalendars: calendars, events: [ev1, ev2])
|
||
let args: JSONValue = .object([
|
||
"start": .string("2026-01-01T00:00:00Z"),
|
||
"end": .string("2026-01-02T00:00:00Z"),
|
||
"calendar_id": .string("id1"),
|
||
"calendar": .string("Home"),
|
||
"limit": .number(10)
|
||
])
|
||
let req = Request(id: "e7", operation: "calendar.events.list", arguments: args)
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: calendars), eventsProvider: provider, createProvider: FakeCreateProvider(willReturn: sampleEvent()), authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertTrue(resp.ok)
|
||
XCTAssertEqual(resp.result?.events?.count, 1)
|
||
XCTAssertEqual(resp.result?.events?.first?.calendar_id, "id1")
|
||
}
|
||
|
||
func testEventsListUnknownCalendarFailsDeterministic() {
|
||
let calendars = [CalendarListItem(id: "id1", title: "Home", source: "iCloud", type: "caldav")]
|
||
let provider = SelectingEventsProvider(availableCalendars: calendars, events: [])
|
||
let args: JSONValue = .object([
|
||
"start": .string("2026-01-01T00:00:00Z"),
|
||
"end": .string("2026-01-02T00:00:00Z"),
|
||
"calendar": .string("Work")
|
||
])
|
||
let req = Request(id: "e8", operation: "calendar.events.list", arguments: args)
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: calendars), eventsProvider: provider, createProvider: FakeCreateProvider(willReturn: sampleEvent()), authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(resp.ok)
|
||
XCTAssertEqual(resp.error?.code, "invalid_request")
|
||
}
|
||
|
||
func testEventsListAmbiguousTitleFails() {
|
||
let calendars = [
|
||
CalendarListItem(id: "id1", title: "Home", source: "iCloud", type: "caldav"),
|
||
CalendarListItem(id: "id2", title: "Home", source: "Local", type: "local")
|
||
]
|
||
let provider = SelectingEventsProvider(availableCalendars: calendars, events: [])
|
||
let args: JSONValue = .object([
|
||
"start": .string("2026-01-01T00:00:00Z"),
|
||
"end": .string("2026-01-02T00:00:00Z"),
|
||
"calendar": .string("Home")
|
||
])
|
||
let req = Request(id: "e9", operation: "calendar.events.list", arguments: args)
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: calendars), eventsProvider: provider, createProvider: FakeCreateProvider(willReturn: sampleEvent()), authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(resp.ok)
|
||
XCTAssertEqual(resp.error?.code, "invalid_request")
|
||
XCTAssertTrue(resp.error?.message.contains("Ambiguous") ?? false)
|
||
}
|
||
|
||
func testEventsListPermissionRequired() {
|
||
let args: JSONValue = .object([
|
||
"start": .string("2026-01-01T00:00:00Z"),
|
||
"end": .string("2026-01-02T00:00:00Z")
|
||
])
|
||
let req = Request(id: "e10", operation: "calendar.events.list", arguments: args)
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: []), eventsProvider: DeniedEventsProvider(), createProvider: FakeCreateProvider(willReturn: sampleEvent()), authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(resp.ok)
|
||
XCTAssertEqual(resp.error?.code, "permission_required")
|
||
}
|
||
|
||
func testEventsListProviderFailureMapsToUnavailable() {
|
||
let args: JSONValue = .object([
|
||
"start": .string("2026-01-01T00:00:00Z"),
|
||
"end": .string("2026-01-02T00:00:00Z")
|
||
])
|
||
let req = Request(id: "e11", operation: "calendar.events.list", arguments: args)
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: []), eventsProvider: FailEventsProvider(), createProvider: FakeCreateProvider(willReturn: sampleEvent()), authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(resp.ok)
|
||
XCTAssertEqual(resp.error?.code, "calendar_unavailable")
|
||
}
|
||
|
||
// MARK: - Create tests
|
||
|
||
func testCreateMissingTitleInvalid() {
|
||
let args: JSONValue = .object([
|
||
"start": .string("2026-01-01T10:00:00Z"),
|
||
"end": .string("2026-01-01T11:00:00Z"),
|
||
"calendar_id": .string("id1")
|
||
])
|
||
let req = Request(id: "c1", operation: "calendar.event.create", arguments: args)
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: []), eventsProvider: FakeEventsProvider(events: []), createProvider: FakeCreateProvider(willReturn: sampleEvent()), authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(resp.ok)
|
||
XCTAssertEqual(resp.error?.code, "invalid_request")
|
||
}
|
||
|
||
func testCreateStartAfterEndInvalid() {
|
||
let args: JSONValue = .object([
|
||
"title": .string("Meeting"),
|
||
"start": .string("2026-01-01T12:00:00Z"),
|
||
"end": .string("2026-01-01T11:00:00Z"),
|
||
"calendar_id": .string("id1")
|
||
])
|
||
let req = Request(id: "c2", operation: "calendar.event.create", arguments: args)
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: []), eventsProvider: FakeEventsProvider(events: []), createProvider: FakeCreateProvider(willReturn: sampleEvent()), authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(resp.ok)
|
||
XCTAssertEqual(resp.error?.code, "invalid_request")
|
||
}
|
||
|
||
func testCreateInvalidISO() {
|
||
let args: JSONValue = .object([
|
||
"title": .string("Meeting"),
|
||
"start": .string("bad-date"),
|
||
"end": .string("2026-01-01T11:00:00Z"),
|
||
"calendar_id": .string("id1")
|
||
])
|
||
let req = Request(id: "c3", operation: "calendar.event.create", arguments: args)
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: []), eventsProvider: FakeEventsProvider(events: []), createProvider: FakeCreateProvider(willReturn: sampleEvent()), authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(resp.ok)
|
||
XCTAssertEqual(resp.error?.code, "invalid_request")
|
||
}
|
||
|
||
func testCreateNoCalendarSpecifiedFailsNoDefault() {
|
||
let args: JSONValue = .object([
|
||
"title": .string("Meeting"),
|
||
"start": .string("2026-01-01T10:00:00Z"),
|
||
"end": .string("2026-01-01T11:00:00Z")
|
||
])
|
||
let req = Request(id: "c4", operation: "calendar.event.create", arguments: args)
|
||
let calendars = [CalendarListItem(id: "id1", title: "Home", source: "iCloud", type: "caldav")]
|
||
let provider = SelectingCreateProvider(availableCalendars: calendars, writableIds: ["id1"], willReturn: sampleEvent())
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: calendars), eventsProvider: FakeEventsProvider(events: []), createProvider: provider, authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(resp.ok)
|
||
XCTAssertEqual(resp.error?.code, "invalid_request")
|
||
XCTAssertTrue(resp.error?.message.contains("must be specified") ?? false)
|
||
}
|
||
|
||
func testCreateUnknownCalendarFails() {
|
||
let args: JSONValue = .object([
|
||
"title": .string("Meeting"),
|
||
"start": .string("2026-01-01T10:00:00Z"),
|
||
"end": .string("2026-01-01T11:00:00Z"),
|
||
"calendar": .string("Nonexistent")
|
||
])
|
||
let req = Request(id: "c5", operation: "calendar.event.create", arguments: args)
|
||
let calendars = [CalendarListItem(id: "id1", title: "Home", source: "iCloud", type: "caldav")]
|
||
let provider = SelectingCreateProvider(availableCalendars: calendars, writableIds: ["id1"], willReturn: sampleEvent())
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: calendars), eventsProvider: FakeEventsProvider(events: []), createProvider: provider, authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(resp.ok)
|
||
XCTAssertEqual(resp.error?.code, "invalid_request")
|
||
}
|
||
|
||
func testCreateAmbiguousTitleFails() {
|
||
let args: JSONValue = .object([
|
||
"title": .string("Meeting"),
|
||
"start": .string("2026-01-01T10:00:00Z"),
|
||
"end": .string("2026-01-01T11:00:00Z"),
|
||
"calendar": .string("Home")
|
||
])
|
||
let req = Request(id: "c6", operation: "calendar.event.create", arguments: args)
|
||
let calendars = [
|
||
CalendarListItem(id: "id1", title: "Home", source: "iCloud", type: "caldav"),
|
||
CalendarListItem(id: "id2", title: "Home", source: "Local", type: "local")
|
||
]
|
||
let provider = SelectingCreateProvider(availableCalendars: calendars, writableIds: ["id1","id2"], willReturn: sampleEvent())
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: calendars), eventsProvider: FakeEventsProvider(events: []), createProvider: provider, authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(resp.ok)
|
||
XCTAssertEqual(resp.error?.code, "invalid_request")
|
||
}
|
||
|
||
func testCreateReadOnlyCalendarFails() {
|
||
let args: JSONValue = .object([
|
||
"title": .string("Meeting"),
|
||
"start": .string("2026-01-01T10:00:00Z"),
|
||
"end": .string("2026-01-01T11:00:00Z"),
|
||
"calendar_id": .string("id1")
|
||
])
|
||
let req = Request(id: "c7", operation: "calendar.event.create", arguments: args)
|
||
let calendars = [CalendarListItem(id: "id1", title: "Birthdays", source: "iCloud", type: "birthday")]
|
||
let provider = SelectingCreateProvider(availableCalendars: calendars, writableIds: [], willReturn: sampleEvent())
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: calendars), eventsProvider: FakeEventsProvider(events: []), createProvider: provider, authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(resp.ok)
|
||
XCTAssertEqual(resp.error?.code, "invalid_request")
|
||
}
|
||
|
||
func testCreateSuccessReturnsMetadata() {
|
||
let args: JSONValue = .object([
|
||
"title": .string("Meeting"),
|
||
"start": .string("2026-01-01T10:00:00Z"),
|
||
"end": .string("2026-01-01T11:00:00Z"),
|
||
"calendar_id": .string("id1"),
|
||
"notes": .string("bring docs"),
|
||
"location": .string("Room 1")
|
||
])
|
||
let req = Request(id: "c8", operation: "calendar.event.create", arguments: args)
|
||
let created = CalendarEventItem(id: "new-id", title: "Meeting", start: "2026-01-01T10:00:00Z", end: "2026-01-01T11:00:00Z", all_day: false, calendar_id: "id1", calendar_title: "Home", notes: "bring docs", location: "Room 1")
|
||
let provider = FakeCreateProvider(willReturn: created)
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: []), eventsProvider: FakeEventsProvider(events: []), createProvider: provider, authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertTrue(resp.ok)
|
||
XCTAssertEqual(resp.result?.event?.id, "new-id")
|
||
XCTAssertEqual(resp.result?.event?.calendar_id, "id1")
|
||
XCTAssertEqual(resp.result?.operation, "calendar.event.create")
|
||
}
|
||
|
||
func testCreatePermissionRequired() {
|
||
let args: JSONValue = .object([
|
||
"title": .string("Meeting"),
|
||
"start": .string("2026-01-01T10:00:00Z"),
|
||
"end": .string("2026-01-01T11:00:00Z"),
|
||
"calendar_id": .string("id1")
|
||
])
|
||
let req = Request(id: "c9", operation: "calendar.event.create", arguments: args)
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: []), eventsProvider: FakeEventsProvider(events: []), createProvider: DeniedCreateProvider(), authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertFalse(resp.ok)
|
||
XCTAssertEqual(resp.error?.code, "permission_required")
|
||
}
|
||
|
||
func testListDoesNotTriggerCreate() {
|
||
let args: JSONValue = .object([
|
||
"start": .string("2026-01-01T00:00:00Z"),
|
||
"end": .string("2026-01-02T00:00:00Z")
|
||
])
|
||
let req = Request(id: "iso", operation: "calendar.events.list", arguments: args)
|
||
let counter = CountingCreate()
|
||
let resp = dispatch(request: req, calendarProvider: FakeCalendarListForSelection(calendars: []), eventsProvider: FakeEventsProvider(events: []), createProvider: counter, authProvider: MockAuthProvider(status: .authorized))
|
||
XCTAssertTrue(resp.ok)
|
||
XCTAssertEqual(counter.count, 0, "list must not trigger create")
|
||
}
|
||
}
|