Files
claude-code-haha/native/cu-helper/Tests/CuHelperTests/ResolvedTargetAuthorizationTests.swift
T
程序员阿江(Relakkes) 8336cd8cc9 feat(computer-use): finalize native input and global app consent
Route coordinate clicks through the exact AX hit, reuse render-local
attributes and keep keyboard macros within validated native boundaries.
Preserve lossless capture evidence while returning bounded model images,
and propagate cancellation through daemon and lock acquisition.

Remove legacy per-app restrictions after feature-wide consent on both
platforms while retaining global disablement and target validation.
Include native helpers, regression tests and a stable receiver fixture.

Validation: 224 focused TypeScript tests, 516 XCTest cases and 15 Swift
Testing cases passed. Local sidecar and Electron builds, development
package creation and package smoke checks passed.
2026-09-10 20:58:39 +08:00

233 lines
8.6 KiB
Swift

import XCTest
@testable import cc_haha_computer_use
final class ResolvedTargetAuthorizationTests: XCTestCase {
private let terminalIdentity = AXTreeProcessIdentity(
bundleID: "com.apple.Terminal",
executablePath: "/System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal",
launchTime: 100
)
private let calculatorIdentity = AXTreeProcessIdentity(
bundleID: "com.apple.calculator",
executablePath: "/System/Applications/Calculator.app/Contents/MacOS/Calculator",
launchTime: 200
)
private let chromeIdentity = AXTreeProcessIdentity(
bundleID: "com.google.Chrome",
executablePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
launchTime: 300
)
func testChromePIDBundleNameAndPathSelectorsPreserveExactProvenIdentity() throws {
let chrome = AppTargetCandidate(
pid: 43,
bundleIdentifier: "com.google.Chrome",
bundleURL: URL(fileURLWithPath: "/Applications/Google Chrome.app"),
localizedName: "Google Chrome",
executableName: "Google Chrome"
)
let selectors: [AppTargetSelector] = [
.pid(43),
.bundleIdentifier("com.google.Chrome"),
.app("com.google.Chrome"),
.app("Google Chrome"),
.app("Google Chrome.app"),
.app("/Applications/Google Chrome.app"),
]
for selector in selectors {
let resolved = try XCTUnwrap(
AppTargetResolver.resolve(selector: selector, candidates: [chrome])
)
let target = try ResolvedTargetAuthorization.authorize(
resolved: resolved,
currentIdentity: chromeIdentity
)
XCTAssertEqual(target.pid, chrome.pid)
XCTAssertEqual(target.identity, chromeIdentity)
}
}
func testAllowingChromeStillRejectsUnprovenOrMismatchedIdentity() {
let unproven = AXTreeProcessIdentity(
bundleID: chromeIdentity.bundleID,
executablePath: chromeIdentity.executablePath,
launchTime: nil
)
XCTAssertThrowsError(try ResolvedTargetAuthorization.authorize(
pid: 43, identity: unproven, expectedBundleID: "com.google.Chrome"
)) {
XCTAssertEqual(($0 as? CUError)?.code, "app_denied")
}
XCTAssertThrowsError(try ResolvedTargetAuthorization.authorize(
pid: 43, identity: terminalIdentity, expectedBundleID: "com.google.Chrome"
)) {
XCTAssertEqual(($0 as? CUError)?.code, "stale_process")
}
}
func testNumericPIDAllowsTerminalAfterGlobalEnablement() throws {
let target = try ResolvedTargetAuthorization.authorize(
pid: 41,
identity: terminalIdentity,
expectedBundleID: nil
)
XCTAssertEqual(target.pid, 41)
XCTAssertEqual(target.identity, terminalIdentity)
}
func testEveryAppCategoryAndHostAreAllowedWithProvenProcessIdentity() throws {
let bundleIDs = [
"com.google.Chrome",
"com.apple.Safari",
"com.apple.Terminal",
"com.microsoft.VSCode",
"com.apple.shortcuts",
"com.webull.desktop.v1",
"com.binance.BinanceDesktop",
"com.ledger.live",
"com.spotify.client",
"com.apple.Music",
"com.amazon.Kindle",
"com.claude-code-haha.desktop",
"dev.cchaha.cu-helper",
"com.example.custom-host",
"com.example.new-app",
]
for bundleID in bundleIDs {
let identity = AXTreeProcessIdentity(
bundleID: bundleID,
executablePath: "/Applications/Fixture.app/Contents/MacOS/Fixture",
launchTime: 100
)
let target = try ResolvedTargetAuthorization.authorize(
pid: 41,
identity: identity,
expectedBundleID: bundleID
)
XCTAssertEqual(target.pid, 41, bundleID)
XCTAssertEqual(target.identity, identity, bundleID)
}
}
func testPIDBundleAndLocalizedNameSelectorsAllAuthorizeActualResolvedBundle() throws {
let terminal = AppTargetCandidate(
pid: 41,
bundleIdentifier: "com.apple.Terminal",
bundleURL: URL(fileURLWithPath: "/System/Applications/Utilities/Terminal.app"),
localizedName: "终端",
executableName: "Terminal"
)
let selectors: [AppTargetSelector] = [
.pid(41),
.bundleIdentifier("com.apple.Terminal"),
.app("终端"),
]
for selector in selectors {
let resolved = try XCTUnwrap(
AppTargetResolver.resolve(selector: selector, candidates: [terminal])
)
let target = try ResolvedTargetAuthorization.authorize(
resolved: resolved,
currentIdentity: terminalIdentity
)
XCTAssertEqual(target.pid, 41)
XCTAssertEqual(target.identity, terminalIdentity)
}
}
func testWorktreeHostPathResolutionAuthorizesExactProcess() throws {
let installed = AppTargetCandidate(
pid: 100,
bundleIdentifier: "com.claude-code-haha.desktop",
bundleURL: URL(fileURLWithPath: "/Applications/Claude Code Haha.app"),
localizedName: "Claude Code Haha",
executableName: "Claude Code Haha"
)
let worktree = AppTargetCandidate(
pid: 200,
bundleIdentifier: installed.bundleIdentifier,
bundleURL: URL(fileURLWithPath: "/Users/test/worktree/desktop/build-artifacts/macos-arm64/Claude Code Haha.app"),
localizedName: installed.localizedName,
executableName: installed.executableName
)
let resolved = try AppTargetResolver.match(
identifier: worktree.bundleURL!.path,
candidates: [installed, worktree]
)
let identity = AXTreeProcessIdentity(
bundleID: worktree.bundleIdentifier,
executablePath: worktree.bundleURL!.appendingPathComponent("Contents/MacOS/Claude Code Haha").path,
launchTime: 300
)
XCTAssertEqual(resolved.pid, worktree.pid)
let target = try ResolvedTargetAuthorization.authorize(
resolved: resolved,
currentIdentity: identity
)
XCTAssertEqual(target.pid, worktree.pid)
XCTAssertEqual(target.identity, identity)
}
func testOmittedFrontmostAndLaunchedTargetsUseSameActualBundlePolicy() throws {
// Both paths ultimately produce this same resolved target shape. The
// authorizer intentionally has no selector-specific bypass.
let resolved = ResolvedAppTarget(
pid: 41,
bundleIdentifier: "com.apple.Terminal",
bundleURL: URL(fileURLWithPath: "/System/Applications/Utilities/Terminal.app")
)
for _ in ["omitted-frontmost", "launched-get-app-state"] {
let target = try ResolvedTargetAuthorization.authorize(
resolved: resolved,
currentIdentity: terminalIdentity
)
XCTAssertEqual(target.pid, 41)
XCTAssertEqual(target.identity, terminalIdentity)
}
}
func testAllowedResolvedTargetReturnsExactProvenProcessLifetime() throws {
let target = try ResolvedTargetAuthorization.authorize(
pid: 42,
identity: calculatorIdentity,
expectedBundleID: "com.apple.calculator"
)
XCTAssertEqual(target.pid, 42)
XCTAssertEqual(target.identity, calculatorIdentity)
}
func testMissingBundleAndResolverIdentityMismatchFailClosed() {
let missingBundle = AXTreeProcessIdentity(
bundleID: nil,
executablePath: "/Applications/Unknown.app/Contents/MacOS/Unknown",
launchTime: 100
)
XCTAssertThrowsError(
try ResolvedTargetAuthorization.authorize(
pid: 42,
identity: missingBundle,
expectedBundleID: nil
)
) {
XCTAssertEqual(($0 as? CUError)?.code, "app_denied")
}
XCTAssertThrowsError(
try ResolvedTargetAuthorization.authorize(
pid: 42,
identity: calculatorIdentity,
expectedBundleID: "com.example.reused"
)
) {
XCTAssertEqual(($0 as? CUError)?.code, "stale_process")
}
}
}