feat(privacy-host): add signed native calendar contacts and reminders host
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.
This commit is contained in:
@@ -0,0 +1,419 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user