Add notification surface regression test

This commit is contained in:
Lawrence Chen 2026-03-19 01:17:18 -07:00
parent 0010e10bf5
commit 9841cb50bf
No known key found for this signature in database

View file

@ -148,6 +148,70 @@ final class TerminalControllerSocketSecurityTests: XCTestCase {
XCTAssertEqual(payload["has_ssh_options"] as? Bool, true)
}
func testNotificationCreateUsesExplicitSurfaceIDWhenProvided() async throws {
let socketPath = makeSocketPath("notify-surface")
let manager = TabManager()
let store = TerminalNotificationStore.shared
let appDelegate = AppDelegate.shared ?? AppDelegate()
let originalTabManager = appDelegate.tabManager
let originalNotificationStore = appDelegate.notificationStore
store.replaceNotificationsForTesting([])
store.configureNotificationDeliveryHandlerForTesting { _, _ in }
appDelegate.tabManager = manager
appDelegate.notificationStore = store
defer {
store.replaceNotificationsForTesting([])
store.resetNotificationDeliveryHandlerForTesting()
appDelegate.tabManager = originalTabManager
appDelegate.notificationStore = originalNotificationStore
}
guard let workspace = manager.selectedWorkspace,
let focusedPanelId = workspace.focusedPanelId else {
XCTFail("Expected selected workspace with a focused panel")
return
}
guard let targetPanel = workspace.newTerminalSplit(from: focusedPanelId, orientation: .horizontal) else {
XCTFail("Expected split panel to be created")
return
}
workspace.focusPanel(focusedPanelId)
TerminalController.shared.start(
tabManager: manager,
socketPath: socketPath,
accessMode: .allowAll
)
try waitForSocket(at: socketPath)
let response = try await withCheckedThrowingContinuation { continuation in
DispatchQueue.global(qos: .userInitiated).async {
do {
let response = try self.sendV2Request(
method: "notification.create",
params: [
"workspace_id": workspace.id.uuidString,
"surface_id": targetPanel.id.uuidString,
"title": "Targeted"
],
to: socketPath
)
continuation.resume(returning: response)
} catch {
continuation.resume(throwing: error)
}
}
}
let result = try XCTUnwrap(response["result"] as? [String: Any])
XCTAssertEqual(result["surface_id"] as? String, targetPanel.id.uuidString)
XCTAssertTrue(store.hasUnreadNotification(forTabId: workspace.id, surfaceId: targetPanel.id))
XCTAssertFalse(store.hasUnreadNotification(forTabId: workspace.id, surfaceId: focusedPanelId))
}
private func waitForSocket(at path: String, timeout: TimeInterval = 2.0) throws {
let expectation = XCTNSPredicateExpectation(
predicate: NSPredicate { _, _ in
@ -212,7 +276,75 @@ final class TerminalControllerSocketSecurityTests: XCTestCase {
return responses
}
private func writeLine(_ command: String, to fd: Int32) throws {
private nonisolated func sendV2Request(
method: String,
params: [String: Any],
to socketPath: String
) throws -> [String: Any] {
let fd = try connect(to: socketPath)
defer { Darwin.close(fd) }
let payload: [String: Any] = [
"jsonrpc": "2.0",
"id": 1,
"method": method,
"params": params
]
let data = try JSONSerialization.data(withJSONObject: payload)
guard let line = String(data: data, encoding: .utf8) else {
throw NSError(domain: NSCocoaErrorDomain, code: 0, userInfo: [
NSLocalizedDescriptionKey: "Failed to encode JSON-RPC request"
])
}
try writeLine(line, to: fd)
let responseLine = try readLine(from: fd)
let responseData = Data(responseLine.utf8)
return try XCTUnwrap(
try JSONSerialization.jsonObject(with: responseData) as? [String: Any],
"Expected JSON-RPC response object"
)
}
private nonisolated func connect(to socketPath: String) throws -> Int32 {
let fd = Darwin.socket(AF_UNIX, SOCK_STREAM, 0)
guard fd >= 0 else {
throw posixError("socket(AF_UNIX)")
}
var addr = sockaddr_un()
addr.sun_family = sa_family_t(AF_UNIX)
let bytes = Array(socketPath.utf8)
let maxPathLen = MemoryLayout.size(ofValue: addr.sun_path)
guard bytes.count < maxPathLen else {
Darwin.close(fd)
throw NSError(domain: NSPOSIXErrorDomain, code: Int(ENAMETOOLONG))
}
withUnsafeMutablePointer(to: &addr.sun_path) { pathPtr in
let cPath = UnsafeMutableRawPointer(pathPtr).assumingMemoryBound(to: CChar.self)
cPath.initialize(repeating: 0, count: maxPathLen)
for (index, byte) in bytes.enumerated() {
cPath[index] = CChar(bitPattern: byte)
}
}
let addrLen = socklen_t(MemoryLayout<sa_family_t>.size + bytes.count + 1)
let connectResult = withUnsafePointer(to: &addr) { ptr -> Int32 in
ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in
Darwin.connect(fd, sockaddrPtr, addrLen)
}
}
guard connectResult == 0 else {
let error = posixError("connect(\(socketPath))")
Darwin.close(fd)
throw error
}
return fd
}
private nonisolated func writeLine(_ command: String, to fd: Int32) throws {
let payload = Array((command + "\n").utf8)
var offset = 0
while offset < payload.count {
@ -226,7 +358,7 @@ final class TerminalControllerSocketSecurityTests: XCTestCase {
}
}
private func readLine(from fd: Int32) throws -> String {
private nonisolated func readLine(from fd: Int32) throws -> String {
var buffer = [UInt8](repeating: 0, count: 1)
var data = Data()
@ -248,7 +380,7 @@ final class TerminalControllerSocketSecurityTests: XCTestCase {
return line
}
private func posixError(_ operation: String) -> NSError {
private nonisolated func posixError(_ operation: String) -> NSError {
NSError(
domain: NSPOSIXErrorDomain,
code: Int(errno),