Files
openclaw/apps/macos/Tests/OpenClawIPCTests/ExecApprovalsGatewayPrompterTests.swift
Steven 25f458a907 macOS: respect exec-approvals.json settings in gateway prompter (#13707)
Fix macOS gateway exec approvals to respect exec-approvals.json.

This updates the macOS gateway prompter to resolve per-agent exec approval policy before deciding whether to show UI, use agentId for policy lookup, honor askFallback when prompts cannot be presented, and resolve no-prompt decisions from the configured security policy instead of hardcoded allow-once behavior. It also adds regression coverage for ask-policy and allowlist-fallback behavior, plus a changelog entry for the fix.

Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
2026-03-14 12:00:15 +11:00

103 lines
3.7 KiB
Swift

import Testing
@testable import OpenClaw
@MainActor
struct ExecApprovalsGatewayPrompterTests {
@Test func `session match prefers active session`() {
let matches = ExecApprovalsGatewayPrompter._testShouldPresent(
mode: .remote,
activeSession: " main ",
requestSession: "main",
lastInputSeconds: nil)
#expect(matches)
let mismatched = ExecApprovalsGatewayPrompter._testShouldPresent(
mode: .remote,
activeSession: "other",
requestSession: "main",
lastInputSeconds: 0)
#expect(!mismatched)
}
@Test func `session fallback uses recent activity`() {
let recent = ExecApprovalsGatewayPrompter._testShouldPresent(
mode: .remote,
activeSession: nil,
requestSession: "main",
lastInputSeconds: 10,
thresholdSeconds: 120)
#expect(recent)
let stale = ExecApprovalsGatewayPrompter._testShouldPresent(
mode: .remote,
activeSession: nil,
requestSession: "main",
lastInputSeconds: 200,
thresholdSeconds: 120)
#expect(!stale)
}
@Test func `default behavior matches mode`() {
let local = ExecApprovalsGatewayPrompter._testShouldPresent(
mode: .local,
activeSession: nil,
requestSession: nil,
lastInputSeconds: 400)
#expect(local)
let remote = ExecApprovalsGatewayPrompter._testShouldPresent(
mode: .remote,
activeSession: nil,
requestSession: nil,
lastInputSeconds: 400)
#expect(!remote)
}
// MARK: - shouldAsk
@Test func askAlwaysPromptsRegardlessOfSecurity() {
#expect(ExecApprovalsGatewayPrompter._testShouldAsk(security: .deny, ask: .always))
#expect(ExecApprovalsGatewayPrompter._testShouldAsk(security: .allowlist, ask: .always))
#expect(ExecApprovalsGatewayPrompter._testShouldAsk(security: .full, ask: .always))
}
@Test func askOnMissPromptsOnlyForAllowlist() {
#expect(ExecApprovalsGatewayPrompter._testShouldAsk(security: .allowlist, ask: .onMiss))
#expect(!ExecApprovalsGatewayPrompter._testShouldAsk(security: .deny, ask: .onMiss))
#expect(!ExecApprovalsGatewayPrompter._testShouldAsk(security: .full, ask: .onMiss))
}
@Test func askOffNeverPrompts() {
#expect(!ExecApprovalsGatewayPrompter._testShouldAsk(security: .deny, ask: .off))
#expect(!ExecApprovalsGatewayPrompter._testShouldAsk(security: .allowlist, ask: .off))
#expect(!ExecApprovalsGatewayPrompter._testShouldAsk(security: .full, ask: .off))
}
@Test func fallbackAllowlistAllowsMatchingResolvedPath() {
let decision = ExecApprovalsGatewayPrompter._testFallbackDecision(
command: "git status",
resolvedPath: "/usr/bin/git",
askFallback: .allowlist,
allowlistPatterns: ["/usr/bin/git"])
#expect(decision == .allowOnce)
}
@Test func fallbackAllowlistDeniesAllowlistMiss() {
let decision = ExecApprovalsGatewayPrompter._testFallbackDecision(
command: "git status",
resolvedPath: "/usr/bin/git",
askFallback: .allowlist,
allowlistPatterns: ["/usr/bin/rg"])
#expect(decision == .deny)
}
@Test func fallbackFullAllowsWhenPromptCannotBeShown() {
let decision = ExecApprovalsGatewayPrompter._testFallbackDecision(
command: "git status",
resolvedPath: "/usr/bin/git",
askFallback: .full,
allowlistPatterns: [])
#expect(decision == .allowOnce)
}
}