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:
Adolfo Reyna
2026-08-03 20:27:54 -04:00
parent 6e2117188e
commit 9fd04b0ce4
56 changed files with 14239 additions and 50 deletions
@@ -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")
}
}