Files
meeting-assistant/MeetingAssistant/Native/MacOsDesktopControls/SelfTests.swift
T
dh 728e66dd72
PR and Push Build/Test / windows-build-and-test (push) Failing after 38m25s
PR and Push Build/Test / portable-build-and-test (push) Successful in 8m9s
PR and Push Build/Test / macos-native-full (push) Skipped
ci: consolidate platform pipelines and test prerequisites on macOS support
2026-10-06 09:02:19 +02:00

68 lines
5.9 KiB
Swift

import Foundation
private enum SelfTestFailure: Error { case failed(String) }
private func expect(_ condition: @autoclosure () -> Bool, _ message: String) throws {
if !condition() { throw SelfTestFailure.failed(message) }
}
func runDesktopControlSelfTests() {
do {
let status = try JSONDecoder().decode(RecordingStatus.self, from: Data(#"{"isRecording":false,"state":2,"launchProfile":null}"#.utf8))
try expect(String(describing: status.state).lowercased() == "summarizing", "numeric status 2 must show summarizing")
print("PASS numeric recording status")
let requests = try decodeDesktopRequests(Data(requestFixture.utf8))
try expect(requests.count == 1 && requests[0].id == "approval-1", "pending request must decode")
try expect(requests[0].actions == [DesktopUserAction(id: "yes", label: "Yes"), DesktopUserAction(id: "no", label: "No")], "offered actions must be preserved")
try expect(requests[0].expiresAt.timeIntervalSince1970 == 1_791_022_260, "offset and fractional timestamps must decode")
print("PASS pending request wire payload")
var state = DesktopUserRequestState()
let now = Date(timeIntervalSince1970: 1_791_022_220)
_ = state.reconcile(requests, now: now)
try expect(state.notificationsToAnnounce().map(\.id) == ["approval-1"], "new pending request must be announced")
state.markAnnounced("approval-1")
_ = state.reconcile(requests, now: now)
try expect(state.notificationsToAnnounce().isEmpty && state.pending.count == 1, "repeated polling must not redeliver or hide menu requests")
try expect(state.reconcile([], now: now) == ["approval-1"] && state.pending.isEmpty, "withdrawn request must remove native presentation")
print("PASS notification reconciliation and menu fallback")
_ = state.reconcile(requests, now: now)
try expect(state.beginResponse(baseUrl: "http://127.0.0.1:5090", requestId: "approval-1", action: "not-offered", now: now) == nil, "invalid action must keep request pending")
guard let response = state.beginResponse(baseUrl: "http://127.0.0.1:5090/", requestId: "approval-1", action: "yes", now: now) else { throw SelfTestFailure.failed("offered action must produce a response") }
try expect(response.url?.absoluteString == "http://127.0.0.1:5090/desktop/requests/approval-1/respond" && response.httpMethod == "POST", "notification and menu response endpoint")
let body = try JSONDecoder().decode([String: String].self, from: response.httpBody ?? Data())
try expect(body == ["action": "yes"], "chosen action must use shared JSON contract")
try expect(state.beginResponse(baseUrl: "http://127.0.0.1:5090", requestId: "approval-1", action: "yes", now: now) == nil, "same pending action must not submit twice")
state.completeResponse("approval-1", accepted: false)
try expect(!state.isResponding("approval-1") && state.pending.count == 1, "failed transport must leave menu fallback actionable")
try expect(state.beginResponse(baseUrl: "http://127.0.0.1:5090", requestId: "approval-1", action: "yes", now: requests[0].expiresAt) == nil, "expired response must not submit")
try expect(state.reconcile(requests, now: requests[0].expiresAt) == ["approval-1"] && state.pending.isEmpty, "expired request must be removed locally")
try expect(state.beginResponse(baseUrl: "http://127.0.0.1:5090", requestId: "approval-1", action: "yes", now: now) == nil, "stale notification must not reconstruct request")
print("PASS allowed once-only response and stale action rejection")
_ = state.reconcile(requests, now: now)
state.completeResponse("approval-1", accepted: true)
_ = state.reconcile(requests, now: now)
try expect(state.pending.isEmpty && state.notificationsToAnnounce().isEmpty, "old HTTP snapshot must not resurrect accepted request")
print("PASS acknowledged response survives stale polling")
let category = requests[0].notificationCategory()
let notification = requests[0].notificationRequest()
try expect(category.actions.map(\.identifier) == ["yes", "no"], "native notification must offer same actions as menu")
try expect(category.actions.map(\.title) == ["Yes", "No"], "native action labels")
try expect(notification.content.title == "Associate project?" && notification.content.body == "Add project Delta to this meeting?", "native notification must show pending request text")
try expect(notification.content.categoryIdentifier == category.identifier && notification.content.userInfo["requestId"] as? String == "approval-1", "native actions must route original request id")
try expect(category.options.contains(.customDismissAction), "notification dismissal must leave menu decision pending")
print("PASS actionable native notification payload")
let windows = try JSONDecoder().decode([DesktopAgentWindow].self, from: Data(#"[{"id":"workflow-1","url":"http://127.0.0.1:5090/desktop/agent-sessions/workflow-1"},{"id":"workflow-2","url":"http://127.0.0.1:5090/desktop/agent-sessions/workflow-2"}]"#.utf8))
var windowState = DesktopAgentWindowState()
try expect(windowState.takeUnopened(windows).map(\.id) == ["workflow-1", "workflow-2"], "two approved workflows must request independent windows")
try expect(windowState.takeUnopened(windows).isEmpty, "polling must not reopen independent windows")
print("PASS independent agent window wire payload and opening")
} catch {
fputs("FAIL desktop controls self-test: \(error)\n", stderr)
exit(1)
}
}
private let requestFixture = #"""
[{"id":"approval-1","kind":"approval","title":"Associate project?","message":"Add project Delta to this meeting?","createdAt":"2026-10-03T12:10:00.1234567+02:00","expiresAt":"2026-10-03T12:11:00+02:00","actions":[{"id":"yes","label":"Yes"},{"id":"no","label":"No"}]}]
"""#