mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix(macos): clean swiftformat pass and sendable warning
This commit is contained in:
@@ -6,14 +6,14 @@ import OpenClawKit
|
|||||||
import OSLog
|
import OSLog
|
||||||
|
|
||||||
actor CameraCaptureService {
|
actor CameraCaptureService {
|
||||||
struct CameraDeviceInfo: Encodable, Sendable {
|
struct CameraDeviceInfo: Encodable {
|
||||||
let id: String
|
let id: String
|
||||||
let name: String
|
let name: String
|
||||||
let position: String
|
let position: String
|
||||||
let deviceType: String
|
let deviceType: String
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CameraError: LocalizedError, Sendable {
|
enum CameraError: LocalizedError {
|
||||||
case cameraUnavailable
|
case cameraUnavailable
|
||||||
case microphoneUnavailable
|
case microphoneUnavailable
|
||||||
case permissionDenied(kind: String)
|
case permissionDenied(kind: String)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Foundation
|
|||||||
import OpenClawProtocol
|
import OpenClawProtocol
|
||||||
|
|
||||||
enum ConfigStore {
|
enum ConfigStore {
|
||||||
struct Overrides: Sendable {
|
struct Overrides {
|
||||||
var isRemoteMode: (@Sendable () async -> Bool)?
|
var isRemoteMode: (@Sendable () async -> Bool)?
|
||||||
var loadLocal: (@MainActor @Sendable () -> [String: Any])?
|
var loadLocal: (@MainActor @Sendable () -> [String: Any])?
|
||||||
var saveLocal: (@MainActor @Sendable ([String: Any]) -> Void)?
|
var saveLocal: (@MainActor @Sendable ([String: Any]) -> Void)?
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum EffectiveConnectionModeSource: Sendable, Equatable {
|
enum EffectiveConnectionModeSource: Equatable {
|
||||||
case configMode
|
case configMode
|
||||||
case configRemoteURL
|
case configRemoteURL
|
||||||
case userDefaults
|
case userDefaults
|
||||||
case onboarding
|
case onboarding
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EffectiveConnectionMode: Sendable, Equatable {
|
struct EffectiveConnectionMode: Equatable {
|
||||||
let mode: AppState.ConnectionMode
|
let mode: AppState.ConnectionMode
|
||||||
let source: EffectiveConnectionModeSource
|
let source: EffectiveConnectionModeSource
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ struct ControlHeartbeatEvent: Codable {
|
|||||||
let reason: String?
|
let reason: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ControlAgentEvent: Codable, Sendable, Identifiable {
|
struct ControlAgentEvent: Codable, Identifiable {
|
||||||
var id: String {
|
var id: String {
|
||||||
"\(self.runId)-\(self.seq)"
|
"\(self.runId)-\(self.seq)"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ struct CronJob: Identifiable, Codable, Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CronEvent: Codable, Sendable {
|
struct CronEvent: Codable {
|
||||||
let jobId: String
|
let jobId: String
|
||||||
let action: String
|
let action: String
|
||||||
let runAtMs: Int?
|
let runAtMs: Int?
|
||||||
@@ -237,7 +237,7 @@ struct CronEvent: Codable, Sendable {
|
|||||||
let nextRunAtMs: Int?
|
let nextRunAtMs: Int?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CronRunLogEntry: Codable, Identifiable, Sendable {
|
struct CronRunLogEntry: Codable, Identifiable {
|
||||||
var id: String {
|
var id: String {
|
||||||
"\(self.jobId)-\(self.ts)"
|
"\(self.jobId)-\(self.ts)"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct DevicePresentation: Sendable {
|
struct DevicePresentation {
|
||||||
let title: String
|
let title: String
|
||||||
let symbol: String?
|
let symbol: String?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ actor DiagnosticsFileLog {
|
|||||||
private let maxBytes: Int64 = 5 * 1024 * 1024
|
private let maxBytes: Int64 = 5 * 1024 * 1024
|
||||||
private let maxBackups = 5
|
private let maxBackups = 5
|
||||||
|
|
||||||
struct Record: Codable, Sendable {
|
struct Record: Codable {
|
||||||
let ts: String
|
let ts: String
|
||||||
let pid: Int32
|
let pid: Int32
|
||||||
let category: String
|
let category: String
|
||||||
|
|||||||
@@ -84,13 +84,13 @@ enum ExecAsk: String, CaseIterable, Codable, Identifiable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ExecApprovalDecision: String, Codable, Sendable {
|
enum ExecApprovalDecision: String, Codable {
|
||||||
case allowOnce = "allow-once"
|
case allowOnce = "allow-once"
|
||||||
case allowAlways = "allow-always"
|
case allowAlways = "allow-always"
|
||||||
case deny
|
case deny
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ExecAllowlistPatternValidationReason: String, Codable, Sendable, Equatable {
|
enum ExecAllowlistPatternValidationReason: String, Codable, Equatable {
|
||||||
case empty
|
case empty
|
||||||
case missingPathComponent
|
case missingPathComponent
|
||||||
|
|
||||||
@@ -104,12 +104,12 @@ enum ExecAllowlistPatternValidationReason: String, Codable, Sendable, Equatable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ExecAllowlistPatternValidation: Sendable, Equatable {
|
enum ExecAllowlistPatternValidation: Equatable {
|
||||||
case valid(String)
|
case valid(String)
|
||||||
case invalid(ExecAllowlistPatternValidationReason)
|
case invalid(ExecAllowlistPatternValidationReason)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ExecAllowlistRejectedEntry: Sendable, Equatable {
|
struct ExecAllowlistRejectedEntry: Equatable {
|
||||||
let id: UUID
|
let id: UUID
|
||||||
let pattern: String
|
let pattern: String
|
||||||
let reason: ExecAllowlistPatternValidationReason
|
let reason: ExecAllowlistPatternValidationReason
|
||||||
@@ -753,7 +753,7 @@ enum ExecApprovalHelpers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ExecEventPayload: Codable, Sendable {
|
struct ExecEventPayload: Codable {
|
||||||
var sessionKey: String
|
var sessionKey: String
|
||||||
var runId: String
|
var runId: String
|
||||||
var host: String
|
var host: String
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ final class ExecApprovalsGatewayPrompter {
|
|||||||
private let logger = Logger(subsystem: "ai.openclaw", category: "exec-approvals.gateway")
|
private let logger = Logger(subsystem: "ai.openclaw", category: "exec-approvals.gateway")
|
||||||
private var task: Task<Void, Never>?
|
private var task: Task<Void, Never>?
|
||||||
|
|
||||||
struct GatewayApprovalRequest: Codable, Sendable {
|
struct GatewayApprovalRequest: Codable {
|
||||||
var id: String
|
var id: String
|
||||||
var request: ExecApprovalPromptRequest
|
var request: ExecApprovalPromptRequest
|
||||||
var createdAtMs: Int
|
var createdAtMs: Int
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Foundation
|
|||||||
import OpenClawKit
|
import OpenClawKit
|
||||||
import OSLog
|
import OSLog
|
||||||
|
|
||||||
struct ExecApprovalPromptRequest: Codable, Sendable {
|
struct ExecApprovalPromptRequest: Codable {
|
||||||
var command: String
|
var command: String
|
||||||
var cwd: String?
|
var cwd: String?
|
||||||
var host: String?
|
var host: String?
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct ExecCommandResolution: Sendable {
|
struct ExecCommandResolution {
|
||||||
let rawExecutable: String
|
let rawExecutable: String
|
||||||
let resolvedPath: String?
|
let resolvedPath: String?
|
||||||
let executableName: String
|
let executableName: String
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import OSLog
|
|||||||
|
|
||||||
private let gatewayConnectionLogger = Logger(subsystem: "ai.openclaw", category: "gateway.connection")
|
private let gatewayConnectionLogger = Logger(subsystem: "ai.openclaw", category: "gateway.connection")
|
||||||
|
|
||||||
enum GatewayAgentChannel: String, Codable, CaseIterable, Sendable {
|
enum GatewayAgentChannel: String, Codable, CaseIterable {
|
||||||
case last
|
case last
|
||||||
case whatsapp
|
case whatsapp
|
||||||
case telegram
|
case telegram
|
||||||
@@ -33,7 +33,7 @@ enum GatewayAgentChannel: String, Codable, CaseIterable, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GatewayAgentInvocation: Sendable {
|
struct GatewayAgentInvocation {
|
||||||
var message: String
|
var message: String
|
||||||
var sessionKey: String = "main"
|
var sessionKey: String = "main"
|
||||||
var thinking: String?
|
var thinking: String?
|
||||||
@@ -53,7 +53,7 @@ actor GatewayConnection {
|
|||||||
|
|
||||||
typealias Config = (url: URL, token: String?, password: String?)
|
typealias Config = (url: URL, token: String?, password: String?)
|
||||||
|
|
||||||
enum Method: String, Sendable {
|
enum Method: String {
|
||||||
case agent
|
case agent
|
||||||
case status
|
case status
|
||||||
case setHeartbeats = "set-heartbeats"
|
case setHeartbeats = "set-heartbeats"
|
||||||
@@ -428,9 +428,9 @@ actor GatewayConnection {
|
|||||||
// MARK: - Typed gateway API
|
// MARK: - Typed gateway API
|
||||||
|
|
||||||
extension GatewayConnection {
|
extension GatewayConnection {
|
||||||
struct ConfigGetSnapshot: Decodable, Sendable {
|
struct ConfigGetSnapshot: Decodable {
|
||||||
struct SnapshotConfig: Decodable, Sendable {
|
struct SnapshotConfig: Decodable {
|
||||||
struct Session: Decodable, Sendable {
|
struct Session: Decodable {
|
||||||
let mainKey: String?
|
let mainKey: String?
|
||||||
let scope: String?
|
let scope: String?
|
||||||
}
|
}
|
||||||
@@ -729,7 +729,7 @@ extension GatewayConnection {
|
|||||||
|
|
||||||
// MARK: - Cron
|
// MARK: - Cron
|
||||||
|
|
||||||
struct CronSchedulerStatus: Decodable, Sendable {
|
struct CronSchedulerStatus: Decodable {
|
||||||
let enabled: Bool
|
let enabled: Bool
|
||||||
let storePath: String
|
let storePath: String
|
||||||
let jobs: Int
|
let jobs: Int
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import ConcurrencyExtras
|
|||||||
import Foundation
|
import Foundation
|
||||||
import OSLog
|
import OSLog
|
||||||
|
|
||||||
enum GatewayEndpointState: Sendable, Equatable {
|
enum GatewayEndpointState: Equatable {
|
||||||
case ready(mode: AppState.ConnectionMode, url: URL, token: String?, password: String?)
|
case ready(mode: AppState.ConnectionMode, url: URL, token: String?, password: String?)
|
||||||
case connecting(mode: AppState.ConnectionMode, detail: String)
|
case connecting(mode: AppState.ConnectionMode, detail: String)
|
||||||
case unavailable(mode: AppState.ConnectionMode, reason: String)
|
case unavailable(mode: AppState.ConnectionMode, reason: String)
|
||||||
@@ -24,14 +24,14 @@ actor GatewayEndpointStore {
|
|||||||
]
|
]
|
||||||
private static let remoteConnectingDetail = "Connecting to remote gateway…"
|
private static let remoteConnectingDetail = "Connecting to remote gateway…"
|
||||||
private static let staticLogger = Logger(subsystem: "ai.openclaw", category: "gateway-endpoint")
|
private static let staticLogger = Logger(subsystem: "ai.openclaw", category: "gateway-endpoint")
|
||||||
private enum EnvOverrideWarningKind: Sendable {
|
private enum EnvOverrideWarningKind {
|
||||||
case token
|
case token
|
||||||
case password
|
case password
|
||||||
}
|
}
|
||||||
|
|
||||||
private static let envOverrideWarnings = LockIsolated((token: false, password: false))
|
private static let envOverrideWarnings = LockIsolated((token: false, password: false))
|
||||||
|
|
||||||
struct Deps: Sendable {
|
struct Deps {
|
||||||
let mode: @Sendable () async -> AppState.ConnectionMode
|
let mode: @Sendable () async -> AppState.ConnectionMode
|
||||||
let token: @Sendable () -> String?
|
let token: @Sendable () -> String?
|
||||||
let password: @Sendable () -> String?
|
let password: @Sendable () -> String?
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import OpenClawIPC
|
|||||||
import OSLog
|
import OSLog
|
||||||
|
|
||||||
/// Lightweight SemVer helper (major.minor.patch only) for gateway compatibility checks.
|
/// Lightweight SemVer helper (major.minor.patch only) for gateway compatibility checks.
|
||||||
struct Semver: Comparable, CustomStringConvertible, Sendable {
|
struct Semver: Comparable, CustomStringConvertible {
|
||||||
let major: Int
|
let major: Int
|
||||||
let minor: Int
|
let minor: Int
|
||||||
let patch: Int
|
let patch: Int
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import Network
|
|||||||
import Observation
|
import Observation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct HealthSnapshot: Codable, Sendable {
|
struct HealthSnapshot: Codable {
|
||||||
struct ChannelSummary: Codable, Sendable {
|
struct ChannelSummary: Codable {
|
||||||
struct Probe: Codable, Sendable {
|
struct Probe: Codable {
|
||||||
struct Bot: Codable, Sendable {
|
struct Bot: Codable {
|
||||||
let username: String?
|
let username: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Webhook: Codable, Sendable {
|
struct Webhook: Codable {
|
||||||
let url: String?
|
let url: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,13 +29,13 @@ struct HealthSnapshot: Codable, Sendable {
|
|||||||
let lastProbeAt: Double?
|
let lastProbeAt: Double?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SessionInfo: Codable, Sendable {
|
struct SessionInfo: Codable {
|
||||||
let key: String
|
let key: String
|
||||||
let updatedAt: Double?
|
let updatedAt: Double?
|
||||||
let age: Double?
|
let age: Double?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Sessions: Codable, Sendable {
|
struct Sessions: Codable {
|
||||||
let path: String
|
let path: String
|
||||||
let count: Int
|
let count: Int
|
||||||
let recent: [SessionInfo]
|
let recent: [SessionInfo]
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ enum HostEnvSecurityPolicy {
|
|||||||
"PS4",
|
"PS4",
|
||||||
"GCONV_PATH",
|
"GCONV_PATH",
|
||||||
"IFS",
|
"IFS",
|
||||||
"SSLKEYLOGFILE"
|
"SSLKEYLOGFILE",
|
||||||
]
|
]
|
||||||
|
|
||||||
static let blockedOverrideKeys: Set<String> = [
|
static let blockedOverrideKeys: Set<String> = [
|
||||||
@@ -50,17 +50,17 @@ enum HostEnvSecurityPolicy {
|
|||||||
"OPENSSL_ENGINES",
|
"OPENSSL_ENGINES",
|
||||||
"PYTHONSTARTUP",
|
"PYTHONSTARTUP",
|
||||||
"WGETRC",
|
"WGETRC",
|
||||||
"CURL_HOME"
|
"CURL_HOME",
|
||||||
]
|
]
|
||||||
|
|
||||||
static let blockedOverridePrefixes: [String] = [
|
static let blockedOverridePrefixes: [String] = [
|
||||||
"GIT_CONFIG_",
|
"GIT_CONFIG_",
|
||||||
"NPM_CONFIG_"
|
"NPM_CONFIG_",
|
||||||
]
|
]
|
||||||
|
|
||||||
static let blockedPrefixes: [String] = [
|
static let blockedPrefixes: [String] = [
|
||||||
"DYLD_",
|
"DYLD_",
|
||||||
"LD_",
|
"LD_",
|
||||||
"BASH_FUNC_"
|
"BASH_FUNC_",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum Launchctl {
|
enum Launchctl {
|
||||||
struct Result: Sendable {
|
struct Result {
|
||||||
let status: Int32
|
let status: Int32
|
||||||
let output: String
|
let output: String
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ enum Launchctl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LaunchAgentPlistSnapshot: Equatable, Sendable {
|
struct LaunchAgentPlistSnapshot: Equatable {
|
||||||
let programArguments: [String]
|
let programArguments: [String]
|
||||||
let environment: [String: String]
|
let environment: [String: String]
|
||||||
let stdoutPath: String?
|
let stdoutPath: String?
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ actor MacNodeBrowserProxy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let profile = params.profile?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
let profile = params.profile?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||||
if !profile.isEmpty && !queryItems.contains(where: { $0.name == "profile" }) {
|
if !profile.isEmpty, !queryItems.contains(where: { $0.name == "profile" }) {
|
||||||
queryItems.append(URLQueryItem(name: "profile", value: profile))
|
queryItems.append(URLQueryItem(name: "profile", value: profile))
|
||||||
}
|
}
|
||||||
if !queryItems.isEmpty {
|
if !queryItems.isEmpty {
|
||||||
@@ -172,7 +172,7 @@ actor MacNodeBrowserProxy {
|
|||||||
}
|
}
|
||||||
if let text = String(data: data, encoding: .utf8)?
|
if let text = String(data: data, encoding: .utf8)?
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines),
|
.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||||
!text.isEmpty
|
!text.isEmpty
|
||||||
{
|
{
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum MacNodeScreenCommand: String, Codable, Sendable {
|
enum MacNodeScreenCommand: String, Codable {
|
||||||
case record = "screen.record"
|
case record = "screen.record"
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MacNodeScreenRecordParams: Codable, Sendable, Equatable {
|
struct MacNodeScreenRecordParams: Codable, Equatable {
|
||||||
var screenIndex: Int?
|
var screenIndex: Int?
|
||||||
var durationMs: Int?
|
var durationMs: Int?
|
||||||
var fps: Double?
|
var fps: Double?
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ enum OverlayPanelFactory {
|
|||||||
offsetX: CGFloat = 6,
|
offsetX: CGFloat = 6,
|
||||||
offsetY: CGFloat = 6,
|
offsetY: CGFloat = 6,
|
||||||
duration: TimeInterval = 0.16,
|
duration: TimeInterval = 0.16,
|
||||||
completion: @escaping () -> Void)
|
completion: @escaping @MainActor @Sendable () -> Void)
|
||||||
{
|
{
|
||||||
let target = window.frame.offsetBy(dx: offsetX, dy: offsetY)
|
let target = window.frame.offsetBy(dx: offsetX, dy: offsetY)
|
||||||
NSAnimationContext.runAnimationGroup { context in
|
NSAnimationContext.runAnimationGroup { context in
|
||||||
@@ -96,7 +96,7 @@ enum OverlayPanelFactory {
|
|||||||
window.animator().setFrame(target, display: true)
|
window.animator().setFrame(target, display: true)
|
||||||
window.animator().alphaValue = 0
|
window.animator().alphaValue = 0
|
||||||
} completionHandler: {
|
} completionHandler: {
|
||||||
completion()
|
Task { @MainActor in completion() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,10 +109,8 @@ enum OverlayPanelFactory {
|
|||||||
onHidden: @escaping @MainActor () -> Void)
|
onHidden: @escaping @MainActor () -> Void)
|
||||||
{
|
{
|
||||||
self.animateDismiss(window: window, offsetX: offsetX, offsetY: offsetY, duration: duration) {
|
self.animateDismiss(window: window, offsetX: offsetX, offsetY: offsetY, duration: duration) {
|
||||||
Task { @MainActor in
|
window.orderOut(nil)
|
||||||
window.orderOut(nil)
|
onHidden()
|
||||||
onHidden()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ final class PeekabooBridgeHostCoordinator {
|
|||||||
private func startIfNeeded() async {
|
private func startIfNeeded() async {
|
||||||
guard self.host == nil else { return }
|
guard self.host == nil else { return }
|
||||||
|
|
||||||
var allowlistedTeamIDs: Set<String> = ["Y5PE65HELJ"]
|
var allowlistedTeamIDs: Set = ["Y5PE65HELJ"]
|
||||||
if let teamID = Self.currentTeamID() {
|
if let teamID = Self.currentTeamID() {
|
||||||
allowlistedTeamIDs.insert(teamID)
|
allowlistedTeamIDs.insert(teamID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ actor PortGuardian {
|
|||||||
let timestamp: TimeInterval
|
let timestamp: TimeInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Descriptor: Sendable {
|
struct Descriptor {
|
||||||
let pid: Int32
|
let pid: Int32
|
||||||
let command: String
|
let command: String
|
||||||
let executablePath: String?
|
let executablePath: String?
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import OpenClawProtocol
|
|||||||
import OSLog
|
import OSLog
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SessionPreviewItem: Identifiable, Sendable {
|
struct SessionPreviewItem: Identifiable {
|
||||||
let id: String
|
let id: String
|
||||||
let role: PreviewRole
|
let role: PreviewRole
|
||||||
let text: String
|
let text: String
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PreviewRole: String, Sendable {
|
enum PreviewRole: String {
|
||||||
case user
|
case user
|
||||||
case assistant
|
case assistant
|
||||||
case tool
|
case tool
|
||||||
@@ -114,7 +114,7 @@ extension SessionPreviewCache {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct SessionMenuPreviewSnapshot: Sendable {
|
struct SessionMenuPreviewSnapshot {
|
||||||
let items: [SessionPreviewItem]
|
let items: [SessionPreviewItem]
|
||||||
let status: SessionMenuPreviewView.LoadStatus
|
let status: SessionMenuPreviewView.LoadStatus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ final class TalkAudioPlayer: NSObject, @preconcurrency AVAudioPlayerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TalkPlaybackResult: Sendable {
|
struct TalkPlaybackResult {
|
||||||
let finished: Bool
|
let finished: Bool
|
||||||
let interruptedAt: Double?
|
let interruptedAt: Double?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import AppKit
|
|||||||
import Foundation
|
import Foundation
|
||||||
import OSLog
|
import OSLog
|
||||||
|
|
||||||
enum VoiceWakeChime: Codable, Equatable, Sendable {
|
enum VoiceWakeChime: Codable, Equatable {
|
||||||
case none
|
case none
|
||||||
case system(name: String)
|
case system(name: String)
|
||||||
case custom(displayName: String, bookmark: Data)
|
case custom(displayName: String, bookmark: Data)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ enum VoiceWakeForwarder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ForwardOptions: Sendable {
|
struct ForwardOptions {
|
||||||
var sessionKey: String = "main"
|
var sessionKey: String = "main"
|
||||||
var thinking: String = "low"
|
var thinking: String = "low"
|
||||||
var deliver: Bool = true
|
var deliver: Bool = true
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ private enum WebChatSwiftUILayout {
|
|||||||
static let anchorPadding: CGFloat = 8
|
static let anchorPadding: CGFloat = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MacGatewayChatTransport: OpenClawChatTransport, Sendable {
|
struct MacGatewayChatTransport: OpenClawChatTransport {
|
||||||
func requestHistory(sessionKey: String) async throws -> OpenClawChatHistoryPayload {
|
func requestHistory(sessionKey: String) async throws -> OpenClawChatHistoryPayload {
|
||||||
try await GatewayConnection.shared.chatHistory(sessionKey: sessionKey)
|
try await GatewayConnection.shared.chatHistory(sessionKey: sessionKey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -374,9 +374,9 @@ public final class GatewayDiscoveryModel {
|
|||||||
if let host = gateway.serviceHost?
|
if let host = gateway.serviceHost?
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
.lowercased(),
|
.lowercased(),
|
||||||
!host.isEmpty,
|
!host.isEmpty,
|
||||||
let port = gateway.servicePort,
|
let port = gateway.servicePort,
|
||||||
port > 0
|
port > 0
|
||||||
{
|
{
|
||||||
return "endpoint|\(host):\(port)"
|
return "endpoint|\(host):\(port)"
|
||||||
}
|
}
|
||||||
@@ -674,7 +674,7 @@ public final class GatewayDiscoveryModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ResolvedGatewayService: Equatable, Sendable {
|
struct ResolvedGatewayService: Equatable {
|
||||||
var txt: [String: String]
|
var txt: [String: String]
|
||||||
var host: String?
|
var host: String?
|
||||||
var port: Int?
|
var port: Int?
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import OpenClawKit
|
import OpenClawKit
|
||||||
|
|
||||||
struct TailscaleServeGatewayBeacon: Sendable, Equatable {
|
struct TailscaleServeGatewayBeacon: Equatable {
|
||||||
var displayName: String
|
var displayName: String
|
||||||
var tailnetDns: String
|
var tailnetDns: String
|
||||||
var host: String
|
var host: String
|
||||||
@@ -13,7 +13,7 @@ enum TailscaleServeGatewayDiscovery {
|
|||||||
private static let probeConcurrency = 6
|
private static let probeConcurrency = 6
|
||||||
private static let defaultProbeTimeoutSeconds: TimeInterval = 1.6
|
private static let defaultProbeTimeoutSeconds: TimeInterval = 1.6
|
||||||
|
|
||||||
struct DiscoveryContext: Sendable {
|
struct DiscoveryContext {
|
||||||
var tailscaleStatus: @Sendable () async -> String?
|
var tailscaleStatus: @Sendable () async -> String?
|
||||||
var probeHost: @Sendable (_ host: String, _ timeout: TimeInterval) async -> Bool
|
var probeHost: @Sendable (_ host: String, _ timeout: TimeInterval) async -> Bool
|
||||||
|
|
||||||
@@ -85,13 +85,13 @@ enum TailscaleServeGatewayDiscovery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct Candidate: Sendable {
|
private struct Candidate {
|
||||||
var dnsName: String
|
var dnsName: String
|
||||||
var displayName: String
|
var displayName: String
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func collectCandidates(status: TailscaleStatus) -> [Candidate] {
|
private static func collectCandidates(status: TailscaleStatus) -> [Candidate] {
|
||||||
let selfDns = normalizeDnsName(status.selfNode?.dnsName)
|
let selfDns = self.normalizeDnsName(status.selfNode?.dnsName)
|
||||||
var out: [Candidate] = []
|
var out: [Candidate] = []
|
||||||
var seen = Set<String>()
|
var seen = Set<String>()
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ enum TailscaleServeGatewayDiscovery {
|
|||||||
|
|
||||||
out.append(Candidate(
|
out.append(Candidate(
|
||||||
dnsName: dnsName,
|
dnsName: dnsName,
|
||||||
displayName: displayName(hostName: node.hostName, dnsName: dnsName)))
|
displayName: self.displayName(hostName: node.hostName, dnsName: dnsName)))
|
||||||
|
|
||||||
if out.count >= self.maxCandidates {
|
if out.count >= self.maxCandidates {
|
||||||
break
|
break
|
||||||
@@ -257,7 +257,7 @@ enum TailscaleServeGatewayDiscovery {
|
|||||||
operation: {
|
operation: {
|
||||||
while true {
|
while true {
|
||||||
let message = try await task.receive()
|
let message = try await task.receive()
|
||||||
if isConnectChallenge(message: message) {
|
if self.isConnectChallenge(message: message) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import OpenClawKit
|
import OpenClawKit
|
||||||
|
|
||||||
struct WideAreaGatewayBeacon: Sendable, Equatable {
|
struct WideAreaGatewayBeacon: Equatable {
|
||||||
var instanceName: String
|
var instanceName: String
|
||||||
var displayName: String
|
var displayName: String
|
||||||
var host: String
|
var host: String
|
||||||
@@ -19,7 +19,7 @@ enum WideAreaGatewayDiscovery {
|
|||||||
private static let defaultTimeoutSeconds: TimeInterval = 0.2
|
private static let defaultTimeoutSeconds: TimeInterval = 0.2
|
||||||
private static let nameserverProbeConcurrency = 6
|
private static let nameserverProbeConcurrency = 6
|
||||||
|
|
||||||
struct DiscoveryContext: Sendable {
|
struct DiscoveryContext {
|
||||||
var tailscaleStatus: @Sendable () -> String?
|
var tailscaleStatus: @Sendable () -> String?
|
||||||
var dig: @Sendable (_ args: [String], _ timeout: TimeInterval) -> String?
|
var dig: @Sendable (_ args: [String], _ timeout: TimeInterval) -> String?
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ import OpenClawProtocol
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct AgentEventStoreTests {
|
struct AgentEventStoreTests {
|
||||||
@Test
|
@Test
|
||||||
func appendAndClear() {
|
func `append and clear`() {
|
||||||
let store = AgentEventStore()
|
let store = AgentEventStore()
|
||||||
#expect(store.events.isEmpty)
|
#expect(store.events.isEmpty)
|
||||||
|
|
||||||
@@ -25,7 +24,7 @@ struct AgentEventStoreTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func trimsToMaxEvents() {
|
func `trims to max events`() {
|
||||||
let store = AgentEventStore()
|
let store = AgentEventStore()
|
||||||
for i in 1...401 {
|
for i in 1...401 {
|
||||||
store.append(ControlAgentEvent(
|
store.append(ControlAgentEvent(
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite
|
|
||||||
struct AgentWorkspaceTests {
|
struct AgentWorkspaceTests {
|
||||||
@Test
|
@Test
|
||||||
func displayPathUsesTildeForHome() {
|
func `display path uses tilde for home`() {
|
||||||
let home = FileManager().homeDirectoryForCurrentUser
|
let home = FileManager().homeDirectoryForCurrentUser
|
||||||
#expect(AgentWorkspace.displayPath(for: home) == "~")
|
#expect(AgentWorkspace.displayPath(for: home) == "~")
|
||||||
|
|
||||||
@@ -14,20 +13,20 @@ struct AgentWorkspaceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func resolveWorkspaceURLExpandsTilde() {
|
func `resolve workspace URL expands tilde`() {
|
||||||
let url = AgentWorkspace.resolveWorkspaceURL(from: "~/tmp")
|
let url = AgentWorkspace.resolveWorkspaceURL(from: "~/tmp")
|
||||||
#expect(url.path.hasSuffix("/tmp"))
|
#expect(url.path.hasSuffix("/tmp"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func agentsURLAppendsFilename() {
|
func `agents URL appends filename`() {
|
||||||
let root = URL(fileURLWithPath: "/tmp/ws", isDirectory: true)
|
let root = URL(fileURLWithPath: "/tmp/ws", isDirectory: true)
|
||||||
let url = AgentWorkspace.agentsURL(workspaceURL: root)
|
let url = AgentWorkspace.agentsURL(workspaceURL: root)
|
||||||
#expect(url.lastPathComponent == AgentWorkspace.agentsFilename)
|
#expect(url.lastPathComponent == AgentWorkspace.agentsFilename)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func bootstrapCreatesAgentsFileWhenMissing() throws {
|
func `bootstrap creates agents file when missing`() throws {
|
||||||
let tmp = FileManager().temporaryDirectory
|
let tmp = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("openclaw-ws-\(UUID().uuidString)", isDirectory: true)
|
.appendingPathComponent("openclaw-ws-\(UUID().uuidString)", isDirectory: true)
|
||||||
defer { try? FileManager().removeItem(at: tmp) }
|
defer { try? FileManager().removeItem(at: tmp) }
|
||||||
@@ -50,7 +49,7 @@ struct AgentWorkspaceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func bootstrapSafetyRejectsNonEmptyFolderWithoutAgents() throws {
|
func `bootstrap safety rejects non empty folder without agents`() throws {
|
||||||
let tmp = FileManager().temporaryDirectory
|
let tmp = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("openclaw-ws-\(UUID().uuidString)", isDirectory: true)
|
.appendingPathComponent("openclaw-ws-\(UUID().uuidString)", isDirectory: true)
|
||||||
defer { try? FileManager().removeItem(at: tmp) }
|
defer { try? FileManager().removeItem(at: tmp) }
|
||||||
@@ -63,7 +62,7 @@ struct AgentWorkspaceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func bootstrapSafetyAllowsExistingAgentsFile() throws {
|
func `bootstrap safety allows existing agents file`() throws {
|
||||||
let tmp = FileManager().temporaryDirectory
|
let tmp = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("openclaw-ws-\(UUID().uuidString)", isDirectory: true)
|
.appendingPathComponent("openclaw-ws-\(UUID().uuidString)", isDirectory: true)
|
||||||
defer { try? FileManager().removeItem(at: tmp) }
|
defer { try? FileManager().removeItem(at: tmp) }
|
||||||
@@ -76,7 +75,7 @@ struct AgentWorkspaceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func bootstrapSkipsBootstrapFileWhenWorkspaceHasContent() throws {
|
func `bootstrap skips bootstrap file when workspace has content`() throws {
|
||||||
let tmp = FileManager().temporaryDirectory
|
let tmp = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("openclaw-ws-\(UUID().uuidString)", isDirectory: true)
|
.appendingPathComponent("openclaw-ws-\(UUID().uuidString)", isDirectory: true)
|
||||||
defer { try? FileManager().removeItem(at: tmp) }
|
defer { try? FileManager().removeItem(at: tmp) }
|
||||||
@@ -91,7 +90,7 @@ struct AgentWorkspaceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func needsBootstrapFalseWhenIdentityAlreadySet() throws {
|
func `needs bootstrap false when identity already set`() throws {
|
||||||
let tmp = FileManager().temporaryDirectory
|
let tmp = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("openclaw-ws-\(UUID().uuidString)", isDirectory: true)
|
.appendingPathComponent("openclaw-ws-\(UUID().uuidString)", isDirectory: true)
|
||||||
defer { try? FileManager().removeItem(at: tmp) }
|
defer { try? FileManager().removeItem(at: tmp) }
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import OpenClawProtocol
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct AnyCodableEncodingTests {
|
struct AnyCodableEncodingTests {
|
||||||
@Test func encodesSwiftArrayAndDictionaryValues() throws {
|
@Test func `encodes swift array and dictionary values`() throws {
|
||||||
let payload: [String: Any] = [
|
let payload: [String: Any] = [
|
||||||
"tags": ["node", "ios"],
|
"tags": ["node", "ios"],
|
||||||
"meta": ["count": 2],
|
"meta": ["count": 2],
|
||||||
@@ -19,7 +19,7 @@ import Testing
|
|||||||
#expect(obj["null"] is NSNull)
|
#expect(obj["null"] is NSNull)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func protocolAnyCodableEncodesPrimitiveArrays() throws {
|
@Test func `protocol any codable encodes primitive arrays`() throws {
|
||||||
let payload: [String: Any] = [
|
let payload: [String: Any] = [
|
||||||
"items": [1, "two", NSNull(), ["ok": true]],
|
"items": [1, "two", NSNull(), ["ok": true]],
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct AudioInputDeviceObserverTests {
|
struct AudioInputDeviceObserverTests {
|
||||||
@Test func hasUsableDefaultInputDeviceReturnsBool() {
|
@Test func `has usable default input device returns bool`() {
|
||||||
// Smoke test: verifies the composition logic runs without crashing.
|
// Smoke test: verifies the composition logic runs without crashing.
|
||||||
// Actual result depends on whether the host has an audio input device.
|
// Actual result depends on whether the host has an audio input device.
|
||||||
let result = AudioInputDeviceObserver.hasUsableDefaultInputDevice()
|
let result = AudioInputDeviceObserver.hasUsableDefaultInputDevice()
|
||||||
_ = result // suppress unused-variable warning; the assertion is "no crash"
|
_ = result // suppress unused-variable warning; the assertion is "no crash"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func hasUsableDefaultInputDeviceConsistentWithComponents() {
|
@Test func `has usable default input device consistent with components`() {
|
||||||
// When no default UID exists, the method must return false.
|
// When no default UID exists, the method must return false.
|
||||||
// When a default UID exists, the result must match alive-set membership.
|
// When a default UID exists, the result must match alive-set membership.
|
||||||
let uid = AudioInputDeviceObserver.defaultInputDeviceUID()
|
let uid = AudioInputDeviceObserver.defaultInputDeviceUID()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Testing
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct CLIInstallerTests {
|
struct CLIInstallerTests {
|
||||||
@Test func installedLocationFindsExecutable() throws {
|
@Test func `installed location finds executable`() throws {
|
||||||
let fm = FileManager()
|
let fm = FileManager()
|
||||||
let root = fm.temporaryDirectory.appendingPathComponent(
|
let root = fm.temporaryDirectory.appendingPathComponent(
|
||||||
"openclaw-cli-installer-\(UUID().uuidString)")
|
"openclaw-cli-installer-\(UUID().uuidString)")
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct CameraCaptureServiceTests {
|
struct CameraCaptureServiceTests {
|
||||||
@Test func normalizeSnapDefaults() {
|
@Test func `normalize snap defaults`() {
|
||||||
let res = CameraCaptureService.normalizeSnap(maxWidth: nil, quality: nil)
|
let res = CameraCaptureService.normalizeSnap(maxWidth: nil, quality: nil)
|
||||||
#expect(res.maxWidth == 1600)
|
#expect(res.maxWidth == 1600)
|
||||||
#expect(res.quality == 0.9)
|
#expect(res.quality == 0.9)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func normalizeSnapClampsValues() {
|
@Test func `normalize snap clamps values`() {
|
||||||
let low = CameraCaptureService.normalizeSnap(maxWidth: -1, quality: -10)
|
let low = CameraCaptureService.normalizeSnap(maxWidth: -1, quality: -10)
|
||||||
#expect(low.maxWidth == 1600)
|
#expect(low.maxWidth == 1600)
|
||||||
#expect(low.quality == 0.05)
|
#expect(low.quality == 0.05)
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import Foundation
|
|||||||
import OpenClawIPC
|
import OpenClawIPC
|
||||||
import Testing
|
import Testing
|
||||||
|
|
||||||
@Suite struct CameraIPCTests {
|
struct CameraIPCTests {
|
||||||
@Test func cameraSnapCodableRoundtrip() throws {
|
@Test func `camera snap codable roundtrip`() throws {
|
||||||
let req: Request = .cameraSnap(
|
let req: Request = .cameraSnap(
|
||||||
facing: .front,
|
facing: .front,
|
||||||
maxWidth: 640,
|
maxWidth: 640,
|
||||||
@@ -24,7 +24,7 @@ import Testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func cameraClipCodableRoundtrip() throws {
|
@Test func `camera clip codable roundtrip`() throws {
|
||||||
let req: Request = .cameraClip(
|
let req: Request = .cameraClip(
|
||||||
facing: .back,
|
facing: .back,
|
||||||
durationMs: 3000,
|
durationMs: 3000,
|
||||||
@@ -45,7 +45,7 @@ import Testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func cameraClipDefaultsIncludeAudioToTrueWhenMissing() throws {
|
@Test func `camera clip defaults include audio to true when missing`() throws {
|
||||||
let json = """
|
let json = """
|
||||||
{"type":"cameraClip","durationMs":1234}
|
{"type":"cameraClip","durationMs":1234}
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import Testing
|
|||||||
return dir
|
return dir
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func detectsInPlaceFileWrites() async throws {
|
@Test func `detects in place file writes`() async throws {
|
||||||
let dir = try self.makeTempDir()
|
let dir = try self.makeTempDir()
|
||||||
defer { try? FileManager().removeItem(at: dir) }
|
defer { try? FileManager().removeItem(at: dir) }
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import Foundation
|
|||||||
import OpenClawIPC
|
import OpenClawIPC
|
||||||
import Testing
|
import Testing
|
||||||
|
|
||||||
@Suite struct CanvasIPCTests {
|
struct CanvasIPCTests {
|
||||||
@Test func canvasPresentCodableRoundtrip() throws {
|
@Test func `canvas present codable roundtrip`() throws {
|
||||||
let placement = CanvasPlacement(x: 10, y: 20, width: 640, height: 480)
|
let placement = CanvasPlacement(x: 10, y: 20, width: 640, height: 480)
|
||||||
let req: Request = .canvasPresent(session: "main", path: "/index.html", placement: placement)
|
let req: Request = .canvasPresent(session: "main", path: "/index.html", placement: placement)
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ import Testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func canvasPresentDecodesNilPlacementWhenMissing() throws {
|
@Test func `canvas present decodes nil placement when missing`() throws {
|
||||||
let json = """
|
let json = """
|
||||||
{"type":"canvasPresent","session":"s","path":"/"}
|
{"type":"canvasPresent","session":"s","path":"/"}
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Testing
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct CanvasWindowSmokeTests {
|
struct CanvasWindowSmokeTests {
|
||||||
@Test func panelControllerShowsAndHides() async throws {
|
@Test func `panel controller shows and hides`() async throws {
|
||||||
let root = FileManager().temporaryDirectory
|
let root = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("openclaw-canvas-test-\(UUID().uuidString)")
|
.appendingPathComponent("openclaw-canvas-test-\(UUID().uuidString)")
|
||||||
try FileManager().createDirectory(at: root, withIntermediateDirectories: true)
|
try FileManager().createDirectory(at: root, withIntermediateDirectories: true)
|
||||||
@@ -30,7 +30,7 @@ struct CanvasWindowSmokeTests {
|
|||||||
controller.close()
|
controller.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func windowControllerShowsAndCloses() throws {
|
@Test func `window controller shows and closes`() throws {
|
||||||
let root = FileManager().temporaryDirectory
|
let root = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("openclaw-canvas-test-\(UUID().uuidString)")
|
.appendingPathComponent("openclaw-canvas-test-\(UUID().uuidString)")
|
||||||
try FileManager().createDirectory(at: root, withIntermediateDirectories: true)
|
try FileManager().createDirectory(at: root, withIntermediateDirectories: true)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ private func makeChannelsStore(
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct ChannelsSettingsSmokeTests {
|
struct ChannelsSettingsSmokeTests {
|
||||||
@Test func channelsSettingsBuildsBodyWithSnapshot() {
|
@Test func `channels settings builds body with snapshot`() {
|
||||||
let store = makeChannelsStore(
|
let store = makeChannelsStore(
|
||||||
channels: [
|
channels: [
|
||||||
"whatsapp": SnapshotAnyCodable([
|
"whatsapp": SnapshotAnyCodable([
|
||||||
@@ -108,7 +108,7 @@ struct ChannelsSettingsSmokeTests {
|
|||||||
_ = view.body
|
_ = view.body
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func channelsSettingsBuildsBodyWithoutSnapshot() {
|
@Test func `channels settings builds body without snapshot`() {
|
||||||
let store = makeChannelsStore(
|
let store = makeChannelsStore(
|
||||||
channels: [
|
channels: [
|
||||||
"whatsapp": SnapshotAnyCodable([
|
"whatsapp": SnapshotAnyCodable([
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import Testing
|
|||||||
return (tmp, pnpmPath)
|
return (tmp, pnpmPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func prefersOpenClawBinary() throws {
|
@Test func `prefers open claw binary`() throws {
|
||||||
let defaults = self.makeLocalDefaults()
|
let defaults = self.makeLocalDefaults()
|
||||||
|
|
||||||
let tmp = try makeTempDirForTests()
|
let tmp = try makeTempDirForTests()
|
||||||
@@ -36,7 +36,7 @@ import Testing
|
|||||||
#expect(cmd.prefix(2).elementsEqual([openclawPath.path, "gateway"]))
|
#expect(cmd.prefix(2).elementsEqual([openclawPath.path, "gateway"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func fallsBackToNodeAndScript() throws {
|
@Test func `falls back to node and script`() throws {
|
||||||
let defaults = self.makeLocalDefaults()
|
let defaults = self.makeLocalDefaults()
|
||||||
|
|
||||||
let tmp = try makeTempDirForTests()
|
let tmp = try makeTempDirForTests()
|
||||||
@@ -63,7 +63,7 @@ import Testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func prefersOpenClawBinaryOverPnpm() throws {
|
@Test func `prefers open claw binary over pnpm`() throws {
|
||||||
let defaults = self.makeLocalDefaults()
|
let defaults = self.makeLocalDefaults()
|
||||||
|
|
||||||
let tmp = try makeTempDirForTests()
|
let tmp = try makeTempDirForTests()
|
||||||
@@ -84,7 +84,7 @@ import Testing
|
|||||||
#expect(cmd.prefix(2).elementsEqual([openclawPath.path, "rpc"]))
|
#expect(cmd.prefix(2).elementsEqual([openclawPath.path, "rpc"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func usesOpenClawBinaryWithoutNodeRuntime() throws {
|
@Test func `uses open claw binary without node runtime`() throws {
|
||||||
let defaults = self.makeLocalDefaults()
|
let defaults = self.makeLocalDefaults()
|
||||||
|
|
||||||
let tmp = try makeTempDirForTests()
|
let tmp = try makeTempDirForTests()
|
||||||
@@ -103,7 +103,7 @@ import Testing
|
|||||||
#expect(cmd.prefix(2).elementsEqual([openclawPath.path, "gateway"]))
|
#expect(cmd.prefix(2).elementsEqual([openclawPath.path, "gateway"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func fallsBackToPnpm() throws {
|
@Test func `falls back to pnpm`() throws {
|
||||||
let defaults = self.makeLocalDefaults()
|
let defaults = self.makeLocalDefaults()
|
||||||
let (tmp, pnpmPath) = try self.makeProjectRootWithPnpm()
|
let (tmp, pnpmPath) = try self.makeProjectRootWithPnpm()
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ import Testing
|
|||||||
#expect(cmd.prefix(4).elementsEqual([pnpmPath.path, "--silent", "openclaw", "rpc"]))
|
#expect(cmd.prefix(4).elementsEqual([pnpmPath.path, "--silent", "openclaw", "rpc"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func pnpmKeepsExtraArgsAfterSubcommand() throws {
|
@Test func `pnpm keeps extra args after subcommand`() throws {
|
||||||
let defaults = self.makeLocalDefaults()
|
let defaults = self.makeLocalDefaults()
|
||||||
let (tmp, pnpmPath) = try self.makeProjectRootWithPnpm()
|
let (tmp, pnpmPath) = try self.makeProjectRootWithPnpm()
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ import Testing
|
|||||||
#expect(cmd.suffix(2).elementsEqual(["--timeout", "5"]))
|
#expect(cmd.suffix(2).elementsEqual(["--timeout", "5"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func preferredPathsStartWithProjectNodeBins() throws {
|
@Test func `preferred paths start with project node bins`() throws {
|
||||||
let tmp = try makeTempDirForTests()
|
let tmp = try makeTempDirForTests()
|
||||||
CommandResolver.setProjectRoot(tmp.path)
|
CommandResolver.setProjectRoot(tmp.path)
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ import Testing
|
|||||||
#expect(first == tmp.appendingPathComponent("node_modules/.bin").path)
|
#expect(first == tmp.appendingPathComponent("node_modules/.bin").path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func buildsSSHCommandForRemoteMode() {
|
@Test func `builds SSH command for remote mode`() {
|
||||||
let defaults = self.makeDefaults()
|
let defaults = self.makeDefaults()
|
||||||
defaults.set(AppState.ConnectionMode.remote.rawValue, forKey: connectionModeKey)
|
defaults.set(AppState.ConnectionMode.remote.rawValue, forKey: connectionModeKey)
|
||||||
defaults.set("openclaw@example.com:2222", forKey: remoteTargetKey)
|
defaults.set("openclaw@example.com:2222", forKey: remoteTargetKey)
|
||||||
@@ -170,13 +170,13 @@ import Testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func rejectsUnsafeSSHTargets() {
|
@Test func `rejects unsafe SSH targets`() {
|
||||||
#expect(CommandResolver.parseSSHTarget("-oProxyCommand=calc") == nil)
|
#expect(CommandResolver.parseSSHTarget("-oProxyCommand=calc") == nil)
|
||||||
#expect(CommandResolver.parseSSHTarget("host:-oProxyCommand=calc") == nil)
|
#expect(CommandResolver.parseSSHTarget("host:-oProxyCommand=calc") == nil)
|
||||||
#expect(CommandResolver.parseSSHTarget("user@host:2222")?.port == 2222)
|
#expect(CommandResolver.parseSSHTarget("user@host:2222")?.port == 2222)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func configRootLocalOverridesRemoteDefaults() throws {
|
@Test func `config root local overrides remote defaults`() throws {
|
||||||
let defaults = self.makeDefaults()
|
let defaults = self.makeDefaults()
|
||||||
defaults.set(AppState.ConnectionMode.remote.rawValue, forKey: connectionModeKey)
|
defaults.set(AppState.ConnectionMode.remote.rawValue, forKey: connectionModeKey)
|
||||||
defaults.set("openclaw@example.com:2222", forKey: remoteTargetKey)
|
defaults.set("openclaw@example.com:2222", forKey: remoteTargetKey)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Testing
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct ConfigStoreTests {
|
struct ConfigStoreTests {
|
||||||
@Test func loadUsesRemoteInRemoteMode() async {
|
@Test func `load uses remote in remote mode`() async {
|
||||||
var localHit = false
|
var localHit = false
|
||||||
var remoteHit = false
|
var remoteHit = false
|
||||||
await ConfigStore._testSetOverrides(.init(
|
await ConfigStore._testSetOverrides(.init(
|
||||||
@@ -20,7 +20,7 @@ struct ConfigStoreTests {
|
|||||||
#expect(result["remote"] as? Bool == true)
|
#expect(result["remote"] as? Bool == true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func loadUsesLocalInLocalMode() async {
|
@Test func `load uses local in local mode`() async {
|
||||||
var localHit = false
|
var localHit = false
|
||||||
var remoteHit = false
|
var remoteHit = false
|
||||||
await ConfigStore._testSetOverrides(.init(
|
await ConfigStore._testSetOverrides(.init(
|
||||||
@@ -36,7 +36,7 @@ struct ConfigStoreTests {
|
|||||||
#expect(result["local"] as? Bool == true)
|
#expect(result["local"] as? Bool == true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func saveRoutesToRemoteInRemoteMode() async throws {
|
@Test func `save routes to remote in remote mode`() async throws {
|
||||||
var localHit = false
|
var localHit = false
|
||||||
var remoteHit = false
|
var remoteHit = false
|
||||||
await ConfigStore._testSetOverrides(.init(
|
await ConfigStore._testSetOverrides(.init(
|
||||||
@@ -51,7 +51,7 @@ struct ConfigStoreTests {
|
|||||||
#expect(!localHit)
|
#expect(!localHit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func saveRoutesToLocalInLocalMode() async throws {
|
@Test func `save routes to local in local mode`() async throws {
|
||||||
var localHit = false
|
var localHit = false
|
||||||
var remoteHit = false
|
var remoteHit = false
|
||||||
await ConfigStore._testSetOverrides(.init(
|
await ConfigStore._testSetOverrides(.init(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Testing
|
|||||||
|
|
||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
struct CoverageDumpTests {
|
struct CoverageDumpTests {
|
||||||
@Test func periodicallyFlushCoverage() async {
|
@Test func `periodically flush coverage`() async {
|
||||||
guard ProcessInfo.processInfo.environment["LLVM_PROFILE_FILE"] != nil else { return }
|
guard ProcessInfo.processInfo.environment["LLVM_PROFILE_FILE"] != nil else { return }
|
||||||
guard let writeProfile = resolveProfileWriteFile() else { return }
|
guard let writeProfile = resolveProfileWriteFile() else { return }
|
||||||
let deadline = Date().addingTimeInterval(4)
|
let deadline = Date().addingTimeInterval(4)
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import AppKit
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct CritterIconRendererTests {
|
struct CritterIconRendererTests {
|
||||||
@Test func makeIconRendersExpectedSize() {
|
@Test func `make icon renders expected size`() {
|
||||||
let image = CritterIconRenderer.makeIcon(
|
let image = CritterIconRenderer.makeIcon(
|
||||||
blink: 0.25,
|
blink: 0.25,
|
||||||
legWiggle: 0.5,
|
legWiggle: 0.5,
|
||||||
@@ -19,7 +18,7 @@ struct CritterIconRendererTests {
|
|||||||
#expect(image.tiffRepresentation != nil)
|
#expect(image.tiffRepresentation != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func makeIconRendersWithBadge() {
|
@Test func `make icon renders with badge`() {
|
||||||
let image = CritterIconRenderer.makeIcon(
|
let image = CritterIconRenderer.makeIcon(
|
||||||
blink: 0,
|
blink: 0,
|
||||||
legWiggle: 0,
|
legWiggle: 0,
|
||||||
@@ -31,7 +30,7 @@ struct CritterIconRendererTests {
|
|||||||
#expect(image.tiffRepresentation != nil)
|
#expect(image.tiffRepresentation != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func critterStatusLabelExercisesHelpers() async {
|
@Test func `critter status label exercises helpers`() async {
|
||||||
await CritterStatusLabel.exerciseForTesting()
|
await CritterStatusLabel.exerciseForTesting()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,17 +15,17 @@ struct CronJobEditorSmokeTests {
|
|||||||
onSave: { _ in })
|
onSave: { _ in })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func statusPillBuildsBody() {
|
@Test func `status pill builds body`() {
|
||||||
_ = StatusPill(text: "ok", tint: .green).body
|
_ = StatusPill(text: "ok", tint: .green).body
|
||||||
_ = StatusPill(text: "disabled", tint: .secondary).body
|
_ = StatusPill(text: "disabled", tint: .secondary).body
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func cronJobEditorBuildsBodyForNewJob() {
|
@Test func `cron job editor builds body for new job`() {
|
||||||
let view = self.makeEditor()
|
let view = self.makeEditor()
|
||||||
_ = view.body
|
_ = view.body
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func cronJobEditorBuildsBodyForExistingJob() {
|
@Test func `cron job editor builds body for existing job`() {
|
||||||
let channelsStore = ChannelsStore(isPreview: true)
|
let channelsStore = ChannelsStore(isPreview: true)
|
||||||
let job = CronJob(
|
let job = CronJob(
|
||||||
id: "job-1",
|
id: "job-1",
|
||||||
@@ -60,12 +60,12 @@ struct CronJobEditorSmokeTests {
|
|||||||
_ = view.body
|
_ = view.body
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func cronJobEditorExercisesBuilders() {
|
@Test func `cron job editor exercises builders`() {
|
||||||
var view = self.makeEditor()
|
var view = self.makeEditor()
|
||||||
view.exerciseForTesting()
|
view.exerciseForTesting()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func cronJobEditorIncludesDeleteAfterRunForAtSchedule() {
|
@Test func `cron job editor includes delete after run for at schedule`() {
|
||||||
let view = self.makeEditor()
|
let view = self.makeEditor()
|
||||||
|
|
||||||
var root: [String: Any] = [:]
|
var root: [String: Any] = [:]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite
|
|
||||||
struct CronModelsTests {
|
struct CronModelsTests {
|
||||||
private func makeCronJob(
|
private func makeCronJob(
|
||||||
name: String,
|
name: String,
|
||||||
@@ -26,14 +25,14 @@ struct CronModelsTests {
|
|||||||
state: state)
|
state: state)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func scheduleAtEncodesAndDecodes() throws {
|
@Test func `schedule at encodes and decodes`() throws {
|
||||||
let schedule = CronSchedule.at(at: "2026-02-03T18:00:00Z")
|
let schedule = CronSchedule.at(at: "2026-02-03T18:00:00Z")
|
||||||
let data = try JSONEncoder().encode(schedule)
|
let data = try JSONEncoder().encode(schedule)
|
||||||
let decoded = try JSONDecoder().decode(CronSchedule.self, from: data)
|
let decoded = try JSONDecoder().decode(CronSchedule.self, from: data)
|
||||||
#expect(decoded == schedule)
|
#expect(decoded == schedule)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func scheduleAtDecodesLegacyAtMs() throws {
|
@Test func `schedule at decodes legacy at ms`() throws {
|
||||||
let json = """
|
let json = """
|
||||||
{"kind":"at","atMs":1700000000000}
|
{"kind":"at","atMs":1700000000000}
|
||||||
"""
|
"""
|
||||||
@@ -45,21 +44,21 @@ struct CronModelsTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func scheduleEveryEncodesAndDecodesWithAnchor() throws {
|
@Test func `schedule every encodes and decodes with anchor`() throws {
|
||||||
let schedule = CronSchedule.every(everyMs: 5000, anchorMs: 10000)
|
let schedule = CronSchedule.every(everyMs: 5000, anchorMs: 10000)
|
||||||
let data = try JSONEncoder().encode(schedule)
|
let data = try JSONEncoder().encode(schedule)
|
||||||
let decoded = try JSONDecoder().decode(CronSchedule.self, from: data)
|
let decoded = try JSONDecoder().decode(CronSchedule.self, from: data)
|
||||||
#expect(decoded == schedule)
|
#expect(decoded == schedule)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func scheduleCronEncodesAndDecodesWithTimezone() throws {
|
@Test func `schedule cron encodes and decodes with timezone`() throws {
|
||||||
let schedule = CronSchedule.cron(expr: "*/5 * * * *", tz: "Europe/Vienna")
|
let schedule = CronSchedule.cron(expr: "*/5 * * * *", tz: "Europe/Vienna")
|
||||||
let data = try JSONEncoder().encode(schedule)
|
let data = try JSONEncoder().encode(schedule)
|
||||||
let decoded = try JSONDecoder().decode(CronSchedule.self, from: data)
|
let decoded = try JSONDecoder().decode(CronSchedule.self, from: data)
|
||||||
#expect(decoded == schedule)
|
#expect(decoded == schedule)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func payloadAgentTurnEncodesAndDecodes() throws {
|
@Test func `payload agent turn encodes and decodes`() throws {
|
||||||
let payload = CronPayload.agentTurn(
|
let payload = CronPayload.agentTurn(
|
||||||
message: "hello",
|
message: "hello",
|
||||||
thinking: "low",
|
thinking: "low",
|
||||||
@@ -73,7 +72,7 @@ struct CronModelsTests {
|
|||||||
#expect(decoded == payload)
|
#expect(decoded == payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func jobEncodesAndDecodesDeleteAfterRun() throws {
|
@Test func `job encodes and decodes delete after run`() throws {
|
||||||
let job = CronJob(
|
let job = CronJob(
|
||||||
id: "job-1",
|
id: "job-1",
|
||||||
agentId: nil,
|
agentId: nil,
|
||||||
@@ -94,7 +93,7 @@ struct CronModelsTests {
|
|||||||
#expect(decoded.deleteAfterRun == true)
|
#expect(decoded.deleteAfterRun == true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func scheduleDecodeRejectsUnknownKind() {
|
@Test func `schedule decode rejects unknown kind`() {
|
||||||
let json = """
|
let json = """
|
||||||
{"kind":"wat","at":"2026-02-03T18:00:00Z"}
|
{"kind":"wat","at":"2026-02-03T18:00:00Z"}
|
||||||
"""
|
"""
|
||||||
@@ -103,7 +102,7 @@ struct CronModelsTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func payloadDecodeRejectsUnknownKind() {
|
@Test func `payload decode rejects unknown kind`() {
|
||||||
let json = """
|
let json = """
|
||||||
{"kind":"wat","text":"hello"}
|
{"kind":"wat","text":"hello"}
|
||||||
"""
|
"""
|
||||||
@@ -112,8 +111,8 @@ struct CronModelsTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func displayNameTrimsWhitespaceAndFallsBack() {
|
@Test func `display name trims whitespace and falls back`() {
|
||||||
let base = makeCronJob(name: " hello ", payloadText: "hi")
|
let base = self.makeCronJob(name: " hello ", payloadText: "hi")
|
||||||
#expect(base.displayName == "hello")
|
#expect(base.displayName == "hello")
|
||||||
|
|
||||||
var unnamed = base
|
var unnamed = base
|
||||||
@@ -121,8 +120,8 @@ struct CronModelsTests {
|
|||||||
#expect(unnamed.displayName == "Untitled job")
|
#expect(unnamed.displayName == "Untitled job")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func nextRunDateAndLastRunDateDeriveFromState() {
|
@Test func `next run date and last run date derive from state`() {
|
||||||
let job = makeCronJob(
|
let job = self.makeCronJob(
|
||||||
name: "t",
|
name: "t",
|
||||||
payloadText: "hi",
|
payloadText: "hi",
|
||||||
state: CronJobState(
|
state: CronJobState(
|
||||||
@@ -136,7 +135,7 @@ struct CronModelsTests {
|
|||||||
#expect(job.lastRunDate == Date(timeIntervalSince1970: 1_700_000_050))
|
#expect(job.lastRunDate == Date(timeIntervalSince1970: 1_700_000_050))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func decodeCronListResponseSkipsMalformedJobs() throws {
|
@Test func `decode cron list response skips malformed jobs`() throws {
|
||||||
let json = """
|
let json = """
|
||||||
{
|
{
|
||||||
"jobs": [
|
"jobs": [
|
||||||
@@ -177,7 +176,7 @@ struct CronModelsTests {
|
|||||||
#expect(jobs.first?.id == "good")
|
#expect(jobs.first?.id == "good")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func decodeCronRunsResponseSkipsMalformedEntries() throws {
|
@Test func `decode cron runs response skips malformed entries`() throws {
|
||||||
let json = """
|
let json = """
|
||||||
{
|
{
|
||||||
"entries": [
|
"entries": [
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import OpenClawKit
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct DeepLinkAgentPolicyTests {
|
struct DeepLinkAgentPolicyTests {
|
||||||
@Test func validateMessageForHandleRejectsTooLongWhenUnkeyed() {
|
@Test func `validate message for handle rejects too long when unkeyed`() {
|
||||||
let msg = String(repeating: "a", count: DeepLinkAgentPolicy.maxUnkeyedConfirmChars + 1)
|
let msg = String(repeating: "a", count: DeepLinkAgentPolicy.maxUnkeyedConfirmChars + 1)
|
||||||
let res = DeepLinkAgentPolicy.validateMessageForHandle(message: msg, allowUnattended: false)
|
let res = DeepLinkAgentPolicy.validateMessageForHandle(message: msg, allowUnattended: false)
|
||||||
switch res {
|
switch res {
|
||||||
@@ -17,7 +17,7 @@ import Testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func validateMessageForHandleAllowsTooLongWhenKeyed() {
|
@Test func `validate message for handle allows too long when keyed`() {
|
||||||
let msg = String(repeating: "a", count: DeepLinkAgentPolicy.maxUnkeyedConfirmChars + 1)
|
let msg = String(repeating: "a", count: DeepLinkAgentPolicy.maxUnkeyedConfirmChars + 1)
|
||||||
let res = DeepLinkAgentPolicy.validateMessageForHandle(message: msg, allowUnattended: true)
|
let res = DeepLinkAgentPolicy.validateMessageForHandle(message: msg, allowUnattended: true)
|
||||||
switch res {
|
switch res {
|
||||||
@@ -28,7 +28,7 @@ import Testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func effectiveDeliveryIgnoresDeliveryFieldsWhenUnkeyed() {
|
@Test func `effective delivery ignores delivery fields when unkeyed`() {
|
||||||
let link = AgentDeepLink(
|
let link = AgentDeepLink(
|
||||||
message: "Hello",
|
message: "Hello",
|
||||||
sessionKey: "s",
|
sessionKey: "s",
|
||||||
@@ -44,7 +44,7 @@ import Testing
|
|||||||
#expect(res.channel == .last)
|
#expect(res.channel == .last)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func effectiveDeliveryHonorsDeliverForDeliverableChannelsWhenKeyed() {
|
@Test func `effective delivery honors deliver for deliverable channels when keyed`() {
|
||||||
let link = AgentDeepLink(
|
let link = AgentDeepLink(
|
||||||
message: "Hello",
|
message: "Hello",
|
||||||
sessionKey: "s",
|
sessionKey: "s",
|
||||||
@@ -60,7 +60,7 @@ import Testing
|
|||||||
#expect(res.channel == .whatsapp)
|
#expect(res.channel == .whatsapp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func effectiveDeliveryStillBlocksWebChatDeliveryWhenKeyed() {
|
@Test func `effective delivery still blocks web chat delivery when keyed`() {
|
||||||
let link = AgentDeepLink(
|
let link = AgentDeepLink(
|
||||||
message: "Hello",
|
message: "Hello",
|
||||||
sessionKey: "s",
|
sessionKey: "s",
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite
|
|
||||||
struct DeviceModelCatalogTests {
|
struct DeviceModelCatalogTests {
|
||||||
@Test
|
@Test
|
||||||
func symbolPrefersModelIdentifierPrefixes() {
|
func `symbol prefers model identifier prefixes`() {
|
||||||
#expect(DeviceModelCatalog
|
#expect(DeviceModelCatalog
|
||||||
.symbol(deviceFamily: "iPad", modelIdentifier: "iPad16,6", friendlyName: nil) == "ipad")
|
.symbol(deviceFamily: "iPad", modelIdentifier: "iPad16,6", friendlyName: nil) == "ipad")
|
||||||
#expect(DeviceModelCatalog
|
#expect(DeviceModelCatalog
|
||||||
@@ -12,7 +11,7 @@ struct DeviceModelCatalogTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func symbolUsesFriendlyNameForMacVariants() {
|
func `symbol uses friendly name for mac variants`() {
|
||||||
#expect(DeviceModelCatalog.symbol(
|
#expect(DeviceModelCatalog.symbol(
|
||||||
deviceFamily: "Mac",
|
deviceFamily: "Mac",
|
||||||
modelIdentifier: "Mac99,1",
|
modelIdentifier: "Mac99,1",
|
||||||
@@ -28,13 +27,13 @@ struct DeviceModelCatalogTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func symbolFallsBackToDeviceFamily() {
|
func `symbol falls back to device family`() {
|
||||||
#expect(DeviceModelCatalog.symbol(deviceFamily: "Android", modelIdentifier: "", friendlyName: nil) == "android")
|
#expect(DeviceModelCatalog.symbol(deviceFamily: "Android", modelIdentifier: "", friendlyName: nil) == "android")
|
||||||
#expect(DeviceModelCatalog.symbol(deviceFamily: "Linux", modelIdentifier: "", friendlyName: nil) == "cpu")
|
#expect(DeviceModelCatalog.symbol(deviceFamily: "Linux", modelIdentifier: "", friendlyName: nil) == "cpu")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func presentationUsesBundledModelMappings() {
|
func `presentation uses bundled model mappings`() {
|
||||||
let presentation = DeviceModelCatalog.presentation(deviceFamily: "iPhone", modelIdentifier: "iPhone1,1")
|
let presentation = DeviceModelCatalog.presentation(deviceFamily: "iPhone", modelIdentifier: "iPhone1,1")
|
||||||
#expect(presentation?.title == "iPhone")
|
#expect(presentation?.title == "iPhone")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,21 +59,21 @@ struct ExecAllowlistTests {
|
|||||||
cwd: nil)
|
cwd: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func matchUsesResolvedPath() {
|
@Test func `match uses resolved path`() {
|
||||||
let entry = ExecAllowlistEntry(pattern: "/opt/homebrew/bin/rg")
|
let entry = ExecAllowlistEntry(pattern: "/opt/homebrew/bin/rg")
|
||||||
let resolution = Self.homebrewRGResolution()
|
let resolution = Self.homebrewRGResolution()
|
||||||
let match = ExecAllowlistMatcher.match(entries: [entry], resolution: resolution)
|
let match = ExecAllowlistMatcher.match(entries: [entry], resolution: resolution)
|
||||||
#expect(match?.pattern == entry.pattern)
|
#expect(match?.pattern == entry.pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func matchIgnoresBasenamePattern() {
|
@Test func `match ignores basename pattern`() {
|
||||||
let entry = ExecAllowlistEntry(pattern: "rg")
|
let entry = ExecAllowlistEntry(pattern: "rg")
|
||||||
let resolution = Self.homebrewRGResolution()
|
let resolution = Self.homebrewRGResolution()
|
||||||
let match = ExecAllowlistMatcher.match(entries: [entry], resolution: resolution)
|
let match = ExecAllowlistMatcher.match(entries: [entry], resolution: resolution)
|
||||||
#expect(match == nil)
|
#expect(match == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func matchIgnoresBasenameForRelativeExecutable() {
|
@Test func `match ignores basename for relative executable`() {
|
||||||
let entry = ExecAllowlistEntry(pattern: "echo")
|
let entry = ExecAllowlistEntry(pattern: "echo")
|
||||||
let resolution = ExecCommandResolution(
|
let resolution = ExecCommandResolution(
|
||||||
rawExecutable: "./echo",
|
rawExecutable: "./echo",
|
||||||
@@ -84,21 +84,21 @@ struct ExecAllowlistTests {
|
|||||||
#expect(match == nil)
|
#expect(match == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func matchIsCaseInsensitive() {
|
@Test func `match is case insensitive`() {
|
||||||
let entry = ExecAllowlistEntry(pattern: "/OPT/HOMEBREW/BIN/RG")
|
let entry = ExecAllowlistEntry(pattern: "/OPT/HOMEBREW/BIN/RG")
|
||||||
let resolution = Self.homebrewRGResolution()
|
let resolution = Self.homebrewRGResolution()
|
||||||
let match = ExecAllowlistMatcher.match(entries: [entry], resolution: resolution)
|
let match = ExecAllowlistMatcher.match(entries: [entry], resolution: resolution)
|
||||||
#expect(match?.pattern == entry.pattern)
|
#expect(match?.pattern == entry.pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func matchSupportsGlobStar() {
|
@Test func `match supports glob star`() {
|
||||||
let entry = ExecAllowlistEntry(pattern: "/opt/**/rg")
|
let entry = ExecAllowlistEntry(pattern: "/opt/**/rg")
|
||||||
let resolution = Self.homebrewRGResolution()
|
let resolution = Self.homebrewRGResolution()
|
||||||
let match = ExecAllowlistMatcher.match(entries: [entry], resolution: resolution)
|
let match = ExecAllowlistMatcher.match(entries: [entry], resolution: resolution)
|
||||||
#expect(match?.pattern == entry.pattern)
|
#expect(match?.pattern == entry.pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveForAllowlistSplitsShellChains() {
|
@Test func `resolve for allowlist splits shell chains`() {
|
||||||
let command = ["/bin/sh", "-lc", "echo allowlisted && /usr/bin/touch /tmp/openclaw-allowlist-test"]
|
let command = ["/bin/sh", "-lc", "echo allowlisted && /usr/bin/touch /tmp/openclaw-allowlist-test"]
|
||||||
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
||||||
command: command,
|
command: command,
|
||||||
@@ -110,7 +110,7 @@ struct ExecAllowlistTests {
|
|||||||
#expect(resolutions[1].executableName == "touch")
|
#expect(resolutions[1].executableName == "touch")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveForAllowlistKeepsQuotedOperatorsInSingleSegment() {
|
@Test func `resolve for allowlist keeps quoted operators in single segment`() {
|
||||||
let command = ["/bin/sh", "-lc", "echo \"a && b\""]
|
let command = ["/bin/sh", "-lc", "echo \"a && b\""]
|
||||||
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
||||||
command: command,
|
command: command,
|
||||||
@@ -121,7 +121,7 @@ struct ExecAllowlistTests {
|
|||||||
#expect(resolutions[0].executableName == "echo")
|
#expect(resolutions[0].executableName == "echo")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveForAllowlistFailsClosedOnCommandSubstitution() {
|
@Test func `resolve for allowlist fails closed on command substitution`() {
|
||||||
let command = ["/bin/sh", "-lc", "echo $(/usr/bin/touch /tmp/openclaw-allowlist-test-subst)"]
|
let command = ["/bin/sh", "-lc", "echo $(/usr/bin/touch /tmp/openclaw-allowlist-test-subst)"]
|
||||||
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
||||||
command: command,
|
command: command,
|
||||||
@@ -131,7 +131,7 @@ struct ExecAllowlistTests {
|
|||||||
#expect(resolutions.isEmpty)
|
#expect(resolutions.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveForAllowlistFailsClosedOnQuotedCommandSubstitution() {
|
@Test func `resolve for allowlist fails closed on quoted command substitution`() {
|
||||||
let command = ["/bin/sh", "-lc", "echo \"ok $(/usr/bin/touch /tmp/openclaw-allowlist-test-quoted-subst)\""]
|
let command = ["/bin/sh", "-lc", "echo \"ok $(/usr/bin/touch /tmp/openclaw-allowlist-test-quoted-subst)\""]
|
||||||
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
||||||
command: command,
|
command: command,
|
||||||
@@ -141,7 +141,7 @@ struct ExecAllowlistTests {
|
|||||||
#expect(resolutions.isEmpty)
|
#expect(resolutions.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveForAllowlistFailsClosedOnQuotedBackticks() {
|
@Test func `resolve for allowlist fails closed on quoted backticks`() {
|
||||||
let command = ["/bin/sh", "-lc", "echo \"ok `/usr/bin/id`\""]
|
let command = ["/bin/sh", "-lc", "echo \"ok `/usr/bin/id`\""]
|
||||||
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
||||||
command: command,
|
command: command,
|
||||||
@@ -151,7 +151,7 @@ struct ExecAllowlistTests {
|
|||||||
#expect(resolutions.isEmpty)
|
#expect(resolutions.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveForAllowlistMatchesSharedShellParserFixture() throws {
|
@Test func `resolve for allowlist matches shared shell parser fixture`() throws {
|
||||||
let fixtures = try Self.loadShellParserParityCases()
|
let fixtures = try Self.loadShellParserParityCases()
|
||||||
for fixture in fixtures {
|
for fixture in fixtures {
|
||||||
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
||||||
@@ -169,7 +169,7 @@ struct ExecAllowlistTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveMatchesSharedWrapperResolutionFixture() throws {
|
@Test func `resolve matches shared wrapper resolution fixture`() throws {
|
||||||
let fixtures = try Self.loadWrapperResolutionParityCases()
|
let fixtures = try Self.loadWrapperResolutionParityCases()
|
||||||
for fixture in fixtures {
|
for fixture in fixtures {
|
||||||
let resolution = ExecCommandResolution.resolve(
|
let resolution = ExecCommandResolution.resolve(
|
||||||
@@ -180,7 +180,7 @@ struct ExecAllowlistTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveForAllowlistTreatsPlainShInvocationAsDirectExec() {
|
@Test func `resolve for allowlist treats plain sh invocation as direct exec`() {
|
||||||
let command = ["/bin/sh", "./script.sh"]
|
let command = ["/bin/sh", "./script.sh"]
|
||||||
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
||||||
command: command,
|
command: command,
|
||||||
@@ -191,7 +191,7 @@ struct ExecAllowlistTests {
|
|||||||
#expect(resolutions[0].executableName == "sh")
|
#expect(resolutions[0].executableName == "sh")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveForAllowlistUnwrapsEnvShellWrapperChains() {
|
@Test func `resolve for allowlist unwraps env shell wrapper chains`() {
|
||||||
let command = [
|
let command = [
|
||||||
"/usr/bin/env",
|
"/usr/bin/env",
|
||||||
"/bin/sh",
|
"/bin/sh",
|
||||||
@@ -208,7 +208,7 @@ struct ExecAllowlistTests {
|
|||||||
#expect(resolutions[1].executableName == "touch")
|
#expect(resolutions[1].executableName == "touch")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveForAllowlistUnwrapsEnvToEffectiveDirectExecutable() {
|
@Test func `resolve for allowlist unwraps env to effective direct executable`() {
|
||||||
let command = ["/usr/bin/env", "FOO=bar", "/usr/bin/printf", "ok"]
|
let command = ["/usr/bin/env", "FOO=bar", "/usr/bin/printf", "ok"]
|
||||||
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
let resolutions = ExecCommandResolution.resolveForAllowlist(
|
||||||
command: command,
|
command: command,
|
||||||
@@ -220,7 +220,7 @@ struct ExecAllowlistTests {
|
|||||||
#expect(resolutions[0].executableName == "printf")
|
#expect(resolutions[0].executableName == "printf")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func matchAllRequiresEverySegmentToMatch() {
|
@Test func `match all requires every segment to match`() {
|
||||||
let first = ExecCommandResolution(
|
let first = ExecCommandResolution(
|
||||||
rawExecutable: "echo",
|
rawExecutable: "echo",
|
||||||
resolvedPath: "/usr/bin/echo",
|
resolvedPath: "/usr/bin/echo",
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct ExecApprovalHelpersTests {
|
struct ExecApprovalHelpersTests {
|
||||||
@Test func parseDecisionTrimsAndRejectsInvalid() {
|
@Test func `parse decision trims and rejects invalid`() {
|
||||||
#expect(ExecApprovalHelpers.parseDecision("allow-once") == .allowOnce)
|
#expect(ExecApprovalHelpers.parseDecision("allow-once") == .allowOnce)
|
||||||
#expect(ExecApprovalHelpers.parseDecision(" allow-always ") == .allowAlways)
|
#expect(ExecApprovalHelpers.parseDecision(" allow-always ") == .allowAlways)
|
||||||
#expect(ExecApprovalHelpers.parseDecision("deny") == .deny)
|
#expect(ExecApprovalHelpers.parseDecision("deny") == .deny)
|
||||||
@@ -11,7 +11,7 @@ import Testing
|
|||||||
#expect(ExecApprovalHelpers.parseDecision("nope") == nil)
|
#expect(ExecApprovalHelpers.parseDecision("nope") == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func allowlistPatternPrefersResolution() {
|
@Test func `allowlist pattern prefers resolution`() {
|
||||||
let resolved = ExecCommandResolution(
|
let resolved = ExecCommandResolution(
|
||||||
rawExecutable: "rg",
|
rawExecutable: "rg",
|
||||||
resolvedPath: "/opt/homebrew/bin/rg",
|
resolvedPath: "/opt/homebrew/bin/rg",
|
||||||
@@ -29,7 +29,7 @@ import Testing
|
|||||||
#expect(ExecApprovalHelpers.allowlistPattern(command: [], resolution: nil) == nil)
|
#expect(ExecApprovalHelpers.allowlistPattern(command: [], resolution: nil) == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func validateAllowlistPatternReturnsReasons() {
|
@Test func `validate allowlist pattern returns reasons`() {
|
||||||
#expect(ExecApprovalHelpers.isPathPattern("/usr/bin/rg"))
|
#expect(ExecApprovalHelpers.isPathPattern("/usr/bin/rg"))
|
||||||
#expect(ExecApprovalHelpers.isPathPattern(" ~/bin/rg "))
|
#expect(ExecApprovalHelpers.isPathPattern(" ~/bin/rg "))
|
||||||
#expect(!ExecApprovalHelpers.isPathPattern("rg"))
|
#expect(!ExecApprovalHelpers.isPathPattern("rg"))
|
||||||
@@ -47,7 +47,7 @@ import Testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func requiresAskMatchesPolicy() {
|
@Test func `requires ask matches policy`() {
|
||||||
let entry = ExecAllowlistEntry(pattern: "/bin/ls", lastUsedAt: nil, lastUsedCommand: nil, lastResolvedPath: nil)
|
let entry = ExecAllowlistEntry(pattern: "/bin/ls", lastUsedAt: nil, lastUsedCommand: nil, lastResolvedPath: nil)
|
||||||
#expect(ExecApprovalHelpers.requiresAsk(
|
#expect(ExecApprovalHelpers.requiresAsk(
|
||||||
ask: .always,
|
ask: .always,
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct ExecApprovalsGatewayPrompterTests {
|
struct ExecApprovalsGatewayPrompterTests {
|
||||||
@Test func sessionMatchPrefersActiveSession() {
|
@Test func `session match prefers active session`() {
|
||||||
let matches = ExecApprovalsGatewayPrompter._testShouldPresent(
|
let matches = ExecApprovalsGatewayPrompter._testShouldPresent(
|
||||||
mode: .remote,
|
mode: .remote,
|
||||||
activeSession: " main ",
|
activeSession: " main ",
|
||||||
@@ -20,7 +19,7 @@ struct ExecApprovalsGatewayPrompterTests {
|
|||||||
#expect(!mismatched)
|
#expect(!mismatched)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func sessionFallbackUsesRecentActivity() {
|
@Test func `session fallback uses recent activity`() {
|
||||||
let recent = ExecApprovalsGatewayPrompter._testShouldPresent(
|
let recent = ExecApprovalsGatewayPrompter._testShouldPresent(
|
||||||
mode: .remote,
|
mode: .remote,
|
||||||
activeSession: nil,
|
activeSession: nil,
|
||||||
@@ -38,7 +37,7 @@ struct ExecApprovalsGatewayPrompterTests {
|
|||||||
#expect(!stale)
|
#expect(!stale)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func defaultBehaviorMatchesMode() {
|
@Test func `default behavior matches mode`() {
|
||||||
let local = ExecApprovalsGatewayPrompter._testShouldPresent(
|
let local = ExecApprovalsGatewayPrompter._testShouldPresent(
|
||||||
mode: .local,
|
mode: .local,
|
||||||
activeSession: nil,
|
activeSession: nil,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Testing
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
struct ExecApprovalsSocketPathGuardTests {
|
struct ExecApprovalsSocketPathGuardTests {
|
||||||
@Test
|
@Test
|
||||||
func hardenParentDirectoryCreatesDirectoryWith0700Permissions() throws {
|
func `harden parent directory creates directory with0700 permissions`() throws {
|
||||||
let root = FileManager().temporaryDirectory
|
let root = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("openclaw-socket-guard-\(UUID().uuidString)", isDirectory: true)
|
.appendingPathComponent("openclaw-socket-guard-\(UUID().uuidString)", isDirectory: true)
|
||||||
defer { try? FileManager().removeItem(at: root) }
|
defer { try? FileManager().removeItem(at: root) }
|
||||||
@@ -24,7 +24,7 @@ struct ExecApprovalsSocketPathGuardTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func removeExistingSocketRejectsSymlinkPath() throws {
|
func `remove existing socket rejects symlink path`() throws {
|
||||||
let root = FileManager().temporaryDirectory
|
let root = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("openclaw-socket-guard-\(UUID().uuidString)", isDirectory: true)
|
.appendingPathComponent("openclaw-socket-guard-\(UUID().uuidString)", isDirectory: true)
|
||||||
defer { try? FileManager().removeItem(at: root) }
|
defer { try? FileManager().removeItem(at: root) }
|
||||||
@@ -50,7 +50,7 @@ struct ExecApprovalsSocketPathGuardTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func removeExistingSocketRejectsRegularFilePath() throws {
|
func `remove existing socket rejects regular file path`() throws {
|
||||||
let root = FileManager().temporaryDirectory
|
let root = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("openclaw-socket-guard-\(UUID().uuidString)", isDirectory: true)
|
.appendingPathComponent("openclaw-socket-guard-\(UUID().uuidString)", isDirectory: true)
|
||||||
defer { try? FileManager().removeItem(at: root) }
|
defer { try? FileManager().removeItem(at: root) }
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ struct ExecApprovalsStoreRefactorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func ensureFileSkipsRewriteWhenUnchanged() async throws {
|
func `ensure file skips rewrite when unchanged`() async throws {
|
||||||
try await self.withTempStateDir { stateDir in
|
try await self.withTempStateDir { _ in
|
||||||
_ = ExecApprovalsStore.ensureFile()
|
_ = ExecApprovalsStore.ensureFile()
|
||||||
let url = ExecApprovalsStore.fileURL()
|
let url = ExecApprovalsStore.fileURL()
|
||||||
let firstWriteDate = try Self.modificationDate(at: url)
|
let firstWriteDate = try Self.modificationDate(at: url)
|
||||||
@@ -32,7 +32,7 @@ struct ExecApprovalsStoreRefactorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func updateAllowlistReportsRejectedBasenamePattern() async throws {
|
func `update allowlist reports rejected basename pattern`() async throws {
|
||||||
try await self.withTempStateDir { _ in
|
try await self.withTempStateDir { _ in
|
||||||
let rejected = ExecApprovalsStore.updateAllowlist(
|
let rejected = ExecApprovalsStore.updateAllowlist(
|
||||||
agentId: "main",
|
agentId: "main",
|
||||||
@@ -50,7 +50,7 @@ struct ExecApprovalsStoreRefactorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func updateAllowlistMigratesLegacyPatternFromResolvedPath() async throws {
|
func `update allowlist migrates legacy pattern from resolved path`() async throws {
|
||||||
try await self.withTempStateDir { _ in
|
try await self.withTempStateDir { _ in
|
||||||
let rejected = ExecApprovalsStore.updateAllowlist(
|
let rejected = ExecApprovalsStore.updateAllowlist(
|
||||||
agentId: "main",
|
agentId: "main",
|
||||||
@@ -69,7 +69,7 @@ struct ExecApprovalsStoreRefactorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func ensureFileHardensStateDirectoryPermissions() async throws {
|
func `ensure file hardens state directory permissions`() async throws {
|
||||||
try await self.withTempStateDir { stateDir in
|
try await self.withTempStateDir { stateDir in
|
||||||
try FileManager().createDirectory(at: stateDir, withIntermediateDirectories: true)
|
try FileManager().createDirectory(at: stateDir, withIntermediateDirectories: true)
|
||||||
try FileManager().setAttributes([.posixPermissions: 0o755], ofItemAtPath: stateDir.path)
|
try FileManager().setAttributes([.posixPermissions: 0o755], ofItemAtPath: stateDir.path)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Testing
|
|||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
struct ExecHostRequestEvaluatorTests {
|
struct ExecHostRequestEvaluatorTests {
|
||||||
@Test func validateRequestRejectsEmptyCommand() {
|
@Test func `validate request rejects empty command`() {
|
||||||
let request = ExecHostRequest(
|
let request = ExecHostRequest(
|
||||||
command: [],
|
command: [],
|
||||||
rawCommand: nil,
|
rawCommand: nil,
|
||||||
@@ -23,7 +23,7 @@ struct ExecHostRequestEvaluatorTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func evaluateRequiresPromptOnAllowlistMissWithoutDecision() {
|
@Test func `evaluate requires prompt on allowlist miss without decision`() {
|
||||||
let context = Self.makeContext(security: .allowlist, ask: .onMiss, allowlistSatisfied: false, skillAllow: false)
|
let context = Self.makeContext(security: .allowlist, ask: .onMiss, allowlistSatisfied: false, skillAllow: false)
|
||||||
let decision = ExecHostRequestEvaluator.evaluate(context: context, approvalDecision: nil)
|
let decision = ExecHostRequestEvaluator.evaluate(context: context, approvalDecision: nil)
|
||||||
switch decision {
|
switch decision {
|
||||||
@@ -36,7 +36,7 @@ struct ExecHostRequestEvaluatorTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func evaluateAllowsAllowOnceDecisionOnAllowlistMiss() {
|
@Test func `evaluate allows allow once decision on allowlist miss`() {
|
||||||
let context = Self.makeContext(security: .allowlist, ask: .onMiss, allowlistSatisfied: false, skillAllow: false)
|
let context = Self.makeContext(security: .allowlist, ask: .onMiss, allowlistSatisfied: false, skillAllow: false)
|
||||||
let decision = ExecHostRequestEvaluator.evaluate(context: context, approvalDecision: .allowOnce)
|
let decision = ExecHostRequestEvaluator.evaluate(context: context, approvalDecision: .allowOnce)
|
||||||
switch decision {
|
switch decision {
|
||||||
@@ -49,7 +49,7 @@ struct ExecHostRequestEvaluatorTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func evaluateDeniesOnExplicitDenyDecision() {
|
@Test func `evaluate denies on explicit deny decision`() {
|
||||||
let context = Self.makeContext(security: .full, ask: .off, allowlistSatisfied: true, skillAllow: false)
|
let context = Self.makeContext(security: .full, ask: .off, allowlistSatisfied: true, skillAllow: false)
|
||||||
let decision = ExecHostRequestEvaluator.evaluate(context: context, approvalDecision: .deny)
|
let decision = ExecHostRequestEvaluator.evaluate(context: context, approvalDecision: .deny)
|
||||||
switch decision {
|
switch decision {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ private struct SystemRunCommandContractExpected: Decodable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ExecSystemRunCommandValidatorTests {
|
struct ExecSystemRunCommandValidatorTests {
|
||||||
@Test func matchesSharedSystemRunCommandContractFixture() throws {
|
@Test func `matches shared system run command contract fixture`() throws {
|
||||||
for entry in try Self.loadContractCases() {
|
for entry in try Self.loadContractCases() {
|
||||||
let result = ExecSystemRunCommandValidator.resolve(command: entry.command, rawCommand: entry.rawCommand)
|
let result = ExecSystemRunCommandValidator.resolve(command: entry.command, rawCommand: entry.rawCommand)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Testing
|
import Testing
|
||||||
|
|
||||||
@Suite struct FileHandleLegacyAPIGuardTests {
|
struct FileHandleLegacyAPIGuardTests {
|
||||||
@Test func sourcesAvoidLegacyNonThrowingFileHandleReadAPIs() throws {
|
@Test func `sources avoid legacy non throwing file handle read AP is`() throws {
|
||||||
let testFile = URL(fileURLWithPath: #filePath)
|
let testFile = URL(fileURLWithPath: #filePath)
|
||||||
let packageRoot = testFile
|
let packageRoot = testFile
|
||||||
.deletingLastPathComponent() // OpenClawIPCTests
|
.deletingLastPathComponent() // OpenClawIPCTests
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct FileHandleSafeReadTests {
|
struct FileHandleSafeReadTests {
|
||||||
@Test func readToEndSafelyReturnsEmptyForClosedHandle() {
|
@Test func `read to end safely returns empty for closed handle`() {
|
||||||
let pipe = Pipe()
|
let pipe = Pipe()
|
||||||
let handle = pipe.fileHandleForReading
|
let handle = pipe.fileHandleForReading
|
||||||
try? handle.close()
|
try? handle.close()
|
||||||
@@ -12,7 +12,7 @@ import Testing
|
|||||||
#expect(data.isEmpty)
|
#expect(data.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func readSafelyUpToCountReturnsEmptyForClosedHandle() {
|
@Test func `read safely up to count returns empty for closed handle`() {
|
||||||
let pipe = Pipe()
|
let pipe = Pipe()
|
||||||
let handle = pipe.fileHandleForReading
|
let handle = pipe.fileHandleForReading
|
||||||
try? handle.close()
|
try? handle.close()
|
||||||
@@ -21,7 +21,7 @@ import Testing
|
|||||||
#expect(data.isEmpty)
|
#expect(data.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func readToEndSafelyReadsPipeContents() {
|
@Test func `read to end safely reads pipe contents`() {
|
||||||
let pipe = Pipe()
|
let pipe = Pipe()
|
||||||
let writeHandle = pipe.fileHandleForWriting
|
let writeHandle = pipe.fileHandleForWriting
|
||||||
writeHandle.write(Data("hello".utf8))
|
writeHandle.write(Data("hello".utf8))
|
||||||
@@ -31,7 +31,7 @@ import Testing
|
|||||||
#expect(String(data: data, encoding: .utf8) == "hello")
|
#expect(String(data: data, encoding: .utf8) == "hello")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func readSafelyUpToCountReadsIncrementally() {
|
@Test func `read safely up to count reads incrementally`() {
|
||||||
let pipe = Pipe()
|
let pipe = Pipe()
|
||||||
let writeHandle = pipe.fileHandleForWriting
|
let writeHandle = pipe.fileHandleForWriting
|
||||||
writeHandle.write(Data("hello world".utf8))
|
writeHandle.write(Data("hello world".utf8))
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct GatewayAgentChannelTests {
|
struct GatewayAgentChannelTests {
|
||||||
@Test func shouldDeliverBlocksWebChat() {
|
@Test func `should deliver blocks web chat`() {
|
||||||
#expect(GatewayAgentChannel.webchat.shouldDeliver(true) == false)
|
#expect(GatewayAgentChannel.webchat.shouldDeliver(true) == false)
|
||||||
#expect(GatewayAgentChannel.webchat.shouldDeliver(false) == false)
|
#expect(GatewayAgentChannel.webchat.shouldDeliver(false) == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func shouldDeliverAllowsLastAndProviderChannels() {
|
@Test func `should deliver allows last and provider channels`() {
|
||||||
#expect(GatewayAgentChannel.last.shouldDeliver(true) == true)
|
#expect(GatewayAgentChannel.last.shouldDeliver(true) == true)
|
||||||
#expect(GatewayAgentChannel.whatsapp.shouldDeliver(true) == true)
|
#expect(GatewayAgentChannel.whatsapp.shouldDeliver(true) == true)
|
||||||
#expect(GatewayAgentChannel.telegram.shouldDeliver(true) == true)
|
#expect(GatewayAgentChannel.telegram.shouldDeliver(true) == true)
|
||||||
@@ -16,7 +16,7 @@ import Testing
|
|||||||
#expect(GatewayAgentChannel.last.shouldDeliver(false) == false)
|
#expect(GatewayAgentChannel.last.shouldDeliver(false) == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func initRawNormalizesAndFallsBackToLast() {
|
@Test func `init raw normalizes and falls back to last`() {
|
||||||
#expect(GatewayAgentChannel(raw: nil) == .last)
|
#expect(GatewayAgentChannel(raw: nil) == .last)
|
||||||
#expect(GatewayAgentChannel(raw: " ") == .last)
|
#expect(GatewayAgentChannel(raw: " ") == .last)
|
||||||
#expect(GatewayAgentChannel(raw: "WEBCHAT") == .webchat)
|
#expect(GatewayAgentChannel(raw: "WEBCHAT") == .webchat)
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import Testing
|
|||||||
|
|
||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
struct GatewayAutostartPolicyTests {
|
struct GatewayAutostartPolicyTests {
|
||||||
@Test func startsGatewayOnlyWhenLocalAndNotPaused() {
|
@Test func `starts gateway only when local and not paused`() {
|
||||||
#expect(GatewayAutostartPolicy.shouldStartGateway(mode: .local, paused: false))
|
#expect(GatewayAutostartPolicy.shouldStartGateway(mode: .local, paused: false))
|
||||||
#expect(!GatewayAutostartPolicy.shouldStartGateway(mode: .local, paused: true))
|
#expect(!GatewayAutostartPolicy.shouldStartGateway(mode: .local, paused: true))
|
||||||
#expect(!GatewayAutostartPolicy.shouldStartGateway(mode: .remote, paused: false))
|
#expect(!GatewayAutostartPolicy.shouldStartGateway(mode: .remote, paused: false))
|
||||||
#expect(!GatewayAutostartPolicy.shouldStartGateway(mode: .unconfigured, paused: false))
|
#expect(!GatewayAutostartPolicy.shouldStartGateway(mode: .unconfigured, paused: false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func ensuresLaunchAgentWhenLocalAndNotAttachOnly() {
|
@Test func `ensures launch agent when local and not attach only`() {
|
||||||
#expect(GatewayAutostartPolicy.shouldEnsureLaunchAgent(
|
#expect(GatewayAutostartPolicy.shouldEnsureLaunchAgent(
|
||||||
mode: .local,
|
mode: .local,
|
||||||
paused: false))
|
paused: false))
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import os
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct GatewayConnectionTests {
|
struct GatewayConnectionTests {
|
||||||
private func makeConnection(
|
private func makeConnection(
|
||||||
session: GatewayTestWebSocketSession,
|
session: GatewayTestWebSocketSession,
|
||||||
token: String? = nil) throws -> (GatewayConnection, ConfigSource)
|
token: String? = nil) throws -> (GatewayConnection, ConfigSource)
|
||||||
@@ -56,7 +56,7 @@ import Testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func requestReusesSingleWebSocketForSameConfig() async throws {
|
@Test func `request reuses single web socket for same config`() async throws {
|
||||||
let session = self.makeSession()
|
let session = self.makeSession()
|
||||||
let (conn, _) = try self.makeConnection(session: session)
|
let (conn, _) = try self.makeConnection(session: session)
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ import Testing
|
|||||||
#expect(session.snapshotCancelCount() == 0)
|
#expect(session.snapshotCancelCount() == 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func requestReconfiguresAndCancelsOnTokenChange() async throws {
|
@Test func `request reconfigures and cancels on token change`() async throws {
|
||||||
let session = self.makeSession()
|
let session = self.makeSession()
|
||||||
let (conn, cfg) = try self.makeConnection(session: session, token: "a")
|
let (conn, cfg) = try self.makeConnection(session: session, token: "a")
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ import Testing
|
|||||||
#expect(session.snapshotCancelCount() == 1)
|
#expect(session.snapshotCancelCount() == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func concurrentRequestsStillUseSingleWebSocket() async throws {
|
@Test func `concurrent requests still use single web socket`() async throws {
|
||||||
let session = self.makeSession(helloDelayMs: 150)
|
let session = self.makeSession(helloDelayMs: 150)
|
||||||
let (conn, _) = try self.makeConnection(session: session)
|
let (conn, _) = try self.makeConnection(session: session)
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ import Testing
|
|||||||
#expect(session.snapshotMakeCount() == 1)
|
#expect(session.snapshotMakeCount() == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func subscribeReplaysLatestSnapshot() async throws {
|
@Test func `subscribe replays latest snapshot`() async throws {
|
||||||
let session = self.makeSession()
|
let session = self.makeSession()
|
||||||
let (conn, _) = try self.makeConnection(session: session)
|
let (conn, _) = try self.makeConnection(session: session)
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ import Testing
|
|||||||
#expect(snap.type == "hello-ok")
|
#expect(snap.type == "hello-ok")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func subscribeEmitsSeqGapBeforeEvent() async throws {
|
@Test func `subscribe emits seq gap before event`() async throws {
|
||||||
let session = self.makeSession()
|
let session = self.makeSession()
|
||||||
let (conn, _) = try self.makeConnection(session: session)
|
let (conn, _) = try self.makeConnection(session: session)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import OpenClawKit
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct GatewayChannelConnectTests {
|
struct GatewayChannelConnectTests {
|
||||||
private enum FakeResponse {
|
private enum FakeResponse {
|
||||||
case helloOk(delayMs: Int)
|
case helloOk(delayMs: Int)
|
||||||
case invalid(delayMs: Int)
|
case invalid(delayMs: Int)
|
||||||
@@ -34,7 +34,7 @@ import Testing
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func concurrentConnectIsSingleFlightOnSuccess() async throws {
|
@Test func `concurrent connect is single flight on success`() async throws {
|
||||||
let session = self.makeSession(response: .helloOk(delayMs: 200))
|
let session = self.makeSession(response: .helloOk(delayMs: 200))
|
||||||
let channel = try GatewayChannelActor(
|
let channel = try GatewayChannelActor(
|
||||||
url: #require(URL(string: "ws://example.invalid")),
|
url: #require(URL(string: "ws://example.invalid")),
|
||||||
@@ -50,7 +50,7 @@ import Testing
|
|||||||
#expect(session.snapshotMakeCount() == 1)
|
#expect(session.snapshotMakeCount() == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func concurrentConnectSharesFailure() async throws {
|
@Test func `concurrent connect shares failure`() async throws {
|
||||||
let session = self.makeSession(response: .invalid(delayMs: 200))
|
let session = self.makeSession(response: .invalid(delayMs: 200))
|
||||||
let channel = try GatewayChannelActor(
|
let channel = try GatewayChannelActor(
|
||||||
url: #require(URL(string: "ws://example.invalid")),
|
url: #require(URL(string: "ws://example.invalid")),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import OpenClawKit
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct GatewayChannelRequestTests {
|
struct GatewayChannelRequestTests {
|
||||||
private func makeSession(requestSendDelayMs: Int) -> GatewayTestWebSocketSession {
|
private func makeSession(requestSendDelayMs: Int) -> GatewayTestWebSocketSession {
|
||||||
GatewayTestWebSocketSession(
|
GatewayTestWebSocketSession(
|
||||||
taskFactory: {
|
taskFactory: {
|
||||||
@@ -16,7 +16,7 @@ import Testing
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func requestTimeoutThenSendFailureDoesNotDoubleResume() async throws {
|
@Test func `request timeout then send failure does not double resume`() async throws {
|
||||||
let session = self.makeSession(requestSendDelayMs: 100)
|
let session = self.makeSession(requestSendDelayMs: 100)
|
||||||
let channel = try GatewayChannelActor(
|
let channel = try GatewayChannelActor(
|
||||||
url: #require(URL(string: "ws://example.invalid")),
|
url: #require(URL(string: "ws://example.invalid")),
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import OpenClawKit
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct GatewayChannelShutdownTests {
|
struct GatewayChannelShutdownTests {
|
||||||
@Test func shutdownPreventsReconnectLoopFromReceiveFailure() async throws {
|
@Test func `shutdown prevents reconnect loop from receive failure`() async throws {
|
||||||
let session = GatewayTestWebSocketSession()
|
let session = GatewayTestWebSocketSession()
|
||||||
let channel = try GatewayChannelActor(
|
let channel = try GatewayChannelActor(
|
||||||
url: #require(URL(string: "ws://example.invalid")),
|
url: #require(URL(string: "ws://example.invalid")),
|
||||||
|
|||||||
@@ -39,14 +39,14 @@ private func makeTestGatewayConnection() -> GatewayConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suite(.serialized) struct GatewayConnectionControlTests {
|
@Suite(.serialized) struct GatewayConnectionControlTests {
|
||||||
@Test func statusFailsWhenProcessMissing() async {
|
@Test func `status fails when process missing`() async {
|
||||||
let connection = makeTestGatewayConnection()
|
let connection = makeTestGatewayConnection()
|
||||||
let result = await connection.status()
|
let result = await connection.status()
|
||||||
#expect(result.ok == false)
|
#expect(result.ok == false)
|
||||||
#expect(result.error != nil)
|
#expect(result.error != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func rejectEmptyMessage() async {
|
@Test func `reject empty message`() async {
|
||||||
let connection = makeTestGatewayConnection()
|
let connection = makeTestGatewayConnection()
|
||||||
let result = await connection.sendAgent(
|
let result = await connection.sendAgent(
|
||||||
message: "",
|
message: "",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import OpenClawDiscovery
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite
|
|
||||||
struct GatewayDiscoveryHelpersTests {
|
struct GatewayDiscoveryHelpersTests {
|
||||||
private func makeGateway(
|
private func makeGateway(
|
||||||
serviceHost: String?,
|
serviceHost: String?,
|
||||||
@@ -41,23 +40,23 @@ struct GatewayDiscoveryHelpersTests {
|
|||||||
#expect(parsed?.port == port)
|
#expect(parsed?.port == port)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func sshTargetUsesResolvedServiceHostOnly() {
|
@Test func `ssh target uses resolved service host only`() {
|
||||||
let gateway = self.makeGateway(
|
let gateway = self.makeGateway(
|
||||||
serviceHost: "resolved.example.ts.net",
|
serviceHost: "resolved.example.ts.net",
|
||||||
servicePort: 18789,
|
servicePort: 18789,
|
||||||
sshPort: 2201)
|
sshPort: 2201)
|
||||||
assertSSHTarget(for: gateway, host: "resolved.example.ts.net", port: 2201)
|
self.assertSSHTarget(for: gateway, host: "resolved.example.ts.net", port: 2201)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func sshTargetAllowsMissingResolvedServicePort() {
|
@Test func `ssh target allows missing resolved service port`() {
|
||||||
let gateway = self.makeGateway(
|
let gateway = self.makeGateway(
|
||||||
serviceHost: "resolved.example.ts.net",
|
serviceHost: "resolved.example.ts.net",
|
||||||
servicePort: nil,
|
servicePort: nil,
|
||||||
sshPort: 2201)
|
sshPort: 2201)
|
||||||
assertSSHTarget(for: gateway, host: "resolved.example.ts.net", port: 2201)
|
self.assertSSHTarget(for: gateway, host: "resolved.example.ts.net", port: 2201)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func sshTargetRejectsTxtOnlyGateways() {
|
@Test func `ssh target rejects txt only gateways`() {
|
||||||
let gateway = self.makeGateway(
|
let gateway = self.makeGateway(
|
||||||
serviceHost: nil,
|
serviceHost: nil,
|
||||||
servicePort: nil,
|
servicePort: nil,
|
||||||
@@ -68,7 +67,7 @@ struct GatewayDiscoveryHelpersTests {
|
|||||||
#expect(GatewayDiscoveryHelpers.sshTarget(for: gateway) == nil)
|
#expect(GatewayDiscoveryHelpers.sshTarget(for: gateway) == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func directUrlUsesResolvedServiceEndpointOnly() {
|
@Test func `direct url uses resolved service endpoint only`() {
|
||||||
let tlsGateway = self.makeGateway(
|
let tlsGateway = self.makeGateway(
|
||||||
serviceHost: "resolved.example.ts.net",
|
serviceHost: "resolved.example.ts.net",
|
||||||
servicePort: 443)
|
servicePort: 443)
|
||||||
@@ -85,7 +84,7 @@ struct GatewayDiscoveryHelpersTests {
|
|||||||
#expect(GatewayDiscoveryHelpers.directUrl(for: localGateway) == "ws://127.0.0.1:18789")
|
#expect(GatewayDiscoveryHelpers.directUrl(for: localGateway) == "ws://127.0.0.1:18789")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func directUrlRejectsTxtOnlyFallback() {
|
@Test func `direct url rejects txt only fallback`() {
|
||||||
let gateway = self.makeGateway(
|
let gateway = self.makeGateway(
|
||||||
serviceHost: nil,
|
serviceHost: nil,
|
||||||
servicePort: nil,
|
servicePort: nil,
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
@testable import OpenClawDiscovery
|
|
||||||
import Testing
|
import Testing
|
||||||
|
@testable import OpenClawDiscovery
|
||||||
|
|
||||||
@Suite
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct GatewayDiscoveryModelTests {
|
struct GatewayDiscoveryModelTests {
|
||||||
@Test func localGatewayMatchesLanHost() {
|
@Test func `local gateway matches lan host`() {
|
||||||
let local = GatewayDiscoveryModel.LocalIdentity(
|
let local = GatewayDiscoveryModel.LocalIdentity(
|
||||||
hostTokens: ["studio"],
|
hostTokens: ["studio"],
|
||||||
displayTokens: [])
|
displayTokens: [])
|
||||||
@@ -16,7 +15,7 @@ struct GatewayDiscoveryModelTests {
|
|||||||
local: local))
|
local: local))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func localGatewayMatchesTailnetDns() {
|
@Test func `local gateway matches tailnet dns`() {
|
||||||
let local = GatewayDiscoveryModel.LocalIdentity(
|
let local = GatewayDiscoveryModel.LocalIdentity(
|
||||||
hostTokens: ["studio"],
|
hostTokens: ["studio"],
|
||||||
displayTokens: [])
|
displayTokens: [])
|
||||||
@@ -28,7 +27,7 @@ struct GatewayDiscoveryModelTests {
|
|||||||
local: local))
|
local: local))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func localGatewayMatchesDisplayName() {
|
@Test func `local gateway matches display name`() {
|
||||||
let local = GatewayDiscoveryModel.LocalIdentity(
|
let local = GatewayDiscoveryModel.LocalIdentity(
|
||||||
hostTokens: [],
|
hostTokens: [],
|
||||||
displayTokens: ["peter's mac studio"])
|
displayTokens: ["peter's mac studio"])
|
||||||
@@ -40,7 +39,7 @@ struct GatewayDiscoveryModelTests {
|
|||||||
local: local))
|
local: local))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func remoteGatewayDoesNotMatch() {
|
@Test func `remote gateway does not match`() {
|
||||||
let local = GatewayDiscoveryModel.LocalIdentity(
|
let local = GatewayDiscoveryModel.LocalIdentity(
|
||||||
hostTokens: ["studio"],
|
hostTokens: ["studio"],
|
||||||
displayTokens: ["peter's mac studio"])
|
displayTokens: ["peter's mac studio"])
|
||||||
@@ -52,7 +51,7 @@ struct GatewayDiscoveryModelTests {
|
|||||||
local: local))
|
local: local))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func localGatewayMatchesServiceName() {
|
@Test func `local gateway matches service name`() {
|
||||||
let local = GatewayDiscoveryModel.LocalIdentity(
|
let local = GatewayDiscoveryModel.LocalIdentity(
|
||||||
hostTokens: ["studio"],
|
hostTokens: ["studio"],
|
||||||
displayTokens: [])
|
displayTokens: [])
|
||||||
@@ -64,7 +63,7 @@ struct GatewayDiscoveryModelTests {
|
|||||||
local: local))
|
local: local))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func serviceNameDoesNotFalsePositiveOnSubstringHostToken() {
|
@Test func `service name does not false positive on substring host token`() {
|
||||||
let local = GatewayDiscoveryModel.LocalIdentity(
|
let local = GatewayDiscoveryModel.LocalIdentity(
|
||||||
hostTokens: ["steipete"],
|
hostTokens: ["steipete"],
|
||||||
displayTokens: [])
|
displayTokens: [])
|
||||||
@@ -82,7 +81,7 @@ struct GatewayDiscoveryModelTests {
|
|||||||
local: local))
|
local: local))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func parsesGatewayTXTFields() {
|
@Test func `parses gateway TXT fields`() {
|
||||||
let parsed = GatewayDiscoveryModel.parseGatewayTXT([
|
let parsed = GatewayDiscoveryModel.parseGatewayTXT([
|
||||||
"lanHost": " studio.local ",
|
"lanHost": " studio.local ",
|
||||||
"tailnetDns": " peters-mac-studio-1.ts.net ",
|
"tailnetDns": " peters-mac-studio-1.ts.net ",
|
||||||
@@ -97,7 +96,7 @@ struct GatewayDiscoveryModelTests {
|
|||||||
#expect(parsed.cliPath == "/opt/openclaw")
|
#expect(parsed.cliPath == "/opt/openclaw")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func parsesGatewayTXTDefaults() {
|
@Test func `parses gateway TXT defaults`() {
|
||||||
let parsed = GatewayDiscoveryModel.parseGatewayTXT([
|
let parsed = GatewayDiscoveryModel.parseGatewayTXT([
|
||||||
"lanHost": " ",
|
"lanHost": " ",
|
||||||
"tailnetDns": "\n",
|
"tailnetDns": "\n",
|
||||||
@@ -111,7 +110,7 @@ struct GatewayDiscoveryModelTests {
|
|||||||
#expect(parsed.cliPath == nil)
|
#expect(parsed.cliPath == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func buildsSSHTarget() {
|
@Test func `builds SSH target`() {
|
||||||
#expect(GatewayDiscoveryModel.buildSSHTarget(
|
#expect(GatewayDiscoveryModel.buildSSHTarget(
|
||||||
user: "peter",
|
user: "peter",
|
||||||
host: "studio.local",
|
host: "studio.local",
|
||||||
@@ -122,7 +121,7 @@ struct GatewayDiscoveryModelTests {
|
|||||||
port: 2201) == "peter@studio.local:2201")
|
port: 2201) == "peter@studio.local:2201")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func dedupeKeyPrefersResolvedEndpointAcrossSources() {
|
@Test func `dedupe key prefers resolved endpoint across sources`() {
|
||||||
let wideArea = GatewayDiscoveryModel.DiscoveredGateway(
|
let wideArea = GatewayDiscoveryModel.DiscoveredGateway(
|
||||||
displayName: "Gateway",
|
displayName: "Gateway",
|
||||||
serviceHost: "gateway-host.tailnet-example.ts.net",
|
serviceHost: "gateway-host.tailnet-example.ts.net",
|
||||||
@@ -151,7 +150,7 @@ struct GatewayDiscoveryModelTests {
|
|||||||
#expect(GatewayDiscoveryModel.dedupeKey(for: wideArea) == GatewayDiscoveryModel.dedupeKey(for: serve))
|
#expect(GatewayDiscoveryModel.dedupeKey(for: wideArea) == GatewayDiscoveryModel.dedupeKey(for: serve))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func dedupeKeyFallsBackToStableIDWithoutEndpoint() {
|
@Test func `dedupe key falls back to stable ID without endpoint`() {
|
||||||
let unresolved = GatewayDiscoveryModel.DiscoveredGateway(
|
let unresolved = GatewayDiscoveryModel.DiscoveredGateway(
|
||||||
displayName: "Gateway",
|
displayName: "Gateway",
|
||||||
serviceHost: nil,
|
serviceHost: nil,
|
||||||
@@ -165,6 +164,7 @@ struct GatewayDiscoveryModelTests {
|
|||||||
debugID: "serve",
|
debugID: "serve",
|
||||||
isLocal: false)
|
isLocal: false)
|
||||||
|
|
||||||
#expect(GatewayDiscoveryModel.dedupeKey(for: unresolved) == "stable|tailscale-serve|gateway-host.tailnet-example.ts.net")
|
#expect(GatewayDiscoveryModel
|
||||||
|
.dedupeKey(for: unresolved) == "stable|tailscale-serve|gateway-host.tailnet-example.ts.net")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct GatewayEndpointStoreTests {
|
struct GatewayEndpointStoreTests {
|
||||||
private func makeLaunchAgentSnapshot(
|
private func makeLaunchAgentSnapshot(
|
||||||
env: [String: String],
|
env: [String: String],
|
||||||
token: String?,
|
token: String?,
|
||||||
@@ -26,7 +26,7 @@ import Testing
|
|||||||
return defaults
|
return defaults
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveGatewayTokenPrefersEnvAndFallsBackToLaunchd() {
|
@Test func `resolve gateway token prefers env and falls back to launchd`() {
|
||||||
let snapshot = self.makeLaunchAgentSnapshot(
|
let snapshot = self.makeLaunchAgentSnapshot(
|
||||||
env: ["OPENCLAW_GATEWAY_TOKEN": "launchd-token"],
|
env: ["OPENCLAW_GATEWAY_TOKEN": "launchd-token"],
|
||||||
token: "launchd-token",
|
token: "launchd-token",
|
||||||
@@ -47,7 +47,7 @@ import Testing
|
|||||||
#expect(fallbackToken == "launchd-token")
|
#expect(fallbackToken == "launchd-token")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveGatewayTokenIgnoresLaunchdInRemoteMode() {
|
@Test func `resolve gateway token ignores launchd in remote mode`() {
|
||||||
let snapshot = self.makeLaunchAgentSnapshot(
|
let snapshot = self.makeLaunchAgentSnapshot(
|
||||||
env: ["OPENCLAW_GATEWAY_TOKEN": "launchd-token"],
|
env: ["OPENCLAW_GATEWAY_TOKEN": "launchd-token"],
|
||||||
token: "launchd-token",
|
token: "launchd-token",
|
||||||
@@ -61,7 +61,7 @@ import Testing
|
|||||||
#expect(token == nil)
|
#expect(token == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveGatewayPasswordFallsBackToLaunchd() {
|
@Test func `resolve gateway password falls back to launchd`() {
|
||||||
let snapshot = self.makeLaunchAgentSnapshot(
|
let snapshot = self.makeLaunchAgentSnapshot(
|
||||||
env: ["OPENCLAW_GATEWAY_PASSWORD": "launchd-pass"],
|
env: ["OPENCLAW_GATEWAY_PASSWORD": "launchd-pass"],
|
||||||
token: nil,
|
token: nil,
|
||||||
@@ -75,7 +75,7 @@ import Testing
|
|||||||
#expect(password == "launchd-pass")
|
#expect(password == "launchd-pass")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func connectionModeResolverPrefersConfigModeOverDefaults() {
|
@Test func `connection mode resolver prefers config mode over defaults`() {
|
||||||
let defaults = self.makeDefaults()
|
let defaults = self.makeDefaults()
|
||||||
defaults.set("remote", forKey: connectionModeKey)
|
defaults.set("remote", forKey: connectionModeKey)
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ import Testing
|
|||||||
#expect(resolved.mode == .local)
|
#expect(resolved.mode == .local)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func connectionModeResolverTrimsConfigMode() {
|
@Test func `connection mode resolver trims config mode`() {
|
||||||
let defaults = self.makeDefaults()
|
let defaults = self.makeDefaults()
|
||||||
defaults.set("local", forKey: connectionModeKey)
|
defaults.set("local", forKey: connectionModeKey)
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ import Testing
|
|||||||
#expect(resolved.mode == .remote)
|
#expect(resolved.mode == .remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func connectionModeResolverFallsBackToDefaultsWhenMissingConfig() {
|
@Test func `connection mode resolver falls back to defaults when missing config`() {
|
||||||
let defaults = self.makeDefaults()
|
let defaults = self.makeDefaults()
|
||||||
defaults.set("remote", forKey: connectionModeKey)
|
defaults.set("remote", forKey: connectionModeKey)
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ import Testing
|
|||||||
#expect(resolved.mode == .remote)
|
#expect(resolved.mode == .remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func connectionModeResolverFallsBackToDefaultsOnUnknownConfig() {
|
@Test func `connection mode resolver falls back to defaults on unknown config`() {
|
||||||
let defaults = self.makeDefaults()
|
let defaults = self.makeDefaults()
|
||||||
defaults.set("local", forKey: connectionModeKey)
|
defaults.set("local", forKey: connectionModeKey)
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ import Testing
|
|||||||
#expect(resolved.mode == .local)
|
#expect(resolved.mode == .local)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func connectionModeResolverPrefersRemoteURLWhenModeMissing() {
|
@Test func `connection mode resolver prefers remote URL when mode missing`() {
|
||||||
let defaults = self.makeDefaults()
|
let defaults = self.makeDefaults()
|
||||||
defaults.set("local", forKey: connectionModeKey)
|
defaults.set("local", forKey: connectionModeKey)
|
||||||
|
|
||||||
@@ -141,35 +141,35 @@ import Testing
|
|||||||
#expect(resolved.mode == .remote)
|
#expect(resolved.mode == .remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveLocalGatewayHostUsesLoopbackForAutoEvenWithTailnet() {
|
@Test func `resolve local gateway host uses loopback for auto even with tailnet`() {
|
||||||
let host = GatewayEndpointStore._testResolveLocalGatewayHost(
|
let host = GatewayEndpointStore._testResolveLocalGatewayHost(
|
||||||
bindMode: "auto",
|
bindMode: "auto",
|
||||||
tailscaleIP: "100.64.1.2")
|
tailscaleIP: "100.64.1.2")
|
||||||
#expect(host == "127.0.0.1")
|
#expect(host == "127.0.0.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveLocalGatewayHostUsesLoopbackForAutoWithoutTailnet() {
|
@Test func `resolve local gateway host uses loopback for auto without tailnet`() {
|
||||||
let host = GatewayEndpointStore._testResolveLocalGatewayHost(
|
let host = GatewayEndpointStore._testResolveLocalGatewayHost(
|
||||||
bindMode: "auto",
|
bindMode: "auto",
|
||||||
tailscaleIP: nil)
|
tailscaleIP: nil)
|
||||||
#expect(host == "127.0.0.1")
|
#expect(host == "127.0.0.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveLocalGatewayHostPrefersTailnetForTailnetMode() {
|
@Test func `resolve local gateway host prefers tailnet for tailnet mode`() {
|
||||||
let host = GatewayEndpointStore._testResolveLocalGatewayHost(
|
let host = GatewayEndpointStore._testResolveLocalGatewayHost(
|
||||||
bindMode: "tailnet",
|
bindMode: "tailnet",
|
||||||
tailscaleIP: "100.64.1.5")
|
tailscaleIP: "100.64.1.5")
|
||||||
#expect(host == "100.64.1.5")
|
#expect(host == "100.64.1.5")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveLocalGatewayHostFallsBackToLoopbackForTailnetMode() {
|
@Test func `resolve local gateway host falls back to loopback for tailnet mode`() {
|
||||||
let host = GatewayEndpointStore._testResolveLocalGatewayHost(
|
let host = GatewayEndpointStore._testResolveLocalGatewayHost(
|
||||||
bindMode: "tailnet",
|
bindMode: "tailnet",
|
||||||
tailscaleIP: nil)
|
tailscaleIP: nil)
|
||||||
#expect(host == "127.0.0.1")
|
#expect(host == "127.0.0.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func resolveLocalGatewayHostUsesCustomBindHost() {
|
@Test func `resolve local gateway host uses custom bind host`() {
|
||||||
let host = GatewayEndpointStore._testResolveLocalGatewayHost(
|
let host = GatewayEndpointStore._testResolveLocalGatewayHost(
|
||||||
bindMode: "custom",
|
bindMode: "custom",
|
||||||
tailscaleIP: "100.64.1.9",
|
tailscaleIP: "100.64.1.9",
|
||||||
@@ -177,7 +177,7 @@ import Testing
|
|||||||
#expect(host == "192.168.1.10")
|
#expect(host == "192.168.1.10")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func localConfigUsesLocalGatewayAuthAndHostResolution() throws {
|
@Test func `local config uses local gateway auth and host resolution`() {
|
||||||
let snapshot = self.makeLaunchAgentSnapshot(
|
let snapshot = self.makeLaunchAgentSnapshot(
|
||||||
env: [:],
|
env: [:],
|
||||||
token: "launchd-token",
|
token: "launchd-token",
|
||||||
@@ -204,7 +204,7 @@ import Testing
|
|||||||
#expect(config.password == "launchd-pass")
|
#expect(config.password == "launchd-pass")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func dashboardURLUsesLocalBasePathInLocalMode() throws {
|
@Test func `dashboard URL uses local base path in local mode`() throws {
|
||||||
let config: GatewayConnection.Config = try (
|
let config: GatewayConnection.Config = try (
|
||||||
url: #require(URL(string: "ws://127.0.0.1:18789")),
|
url: #require(URL(string: "ws://127.0.0.1:18789")),
|
||||||
token: nil,
|
token: nil,
|
||||||
@@ -217,7 +217,7 @@ import Testing
|
|||||||
#expect(url.absoluteString == "http://127.0.0.1:18789/control/")
|
#expect(url.absoluteString == "http://127.0.0.1:18789/control/")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func dashboardURLSkipsLocalBasePathInRemoteMode() throws {
|
@Test func `dashboard URL skips local base path in remote mode`() throws {
|
||||||
let config: GatewayConnection.Config = try (
|
let config: GatewayConnection.Config = try (
|
||||||
url: #require(URL(string: "ws://gateway.example:18789")),
|
url: #require(URL(string: "ws://gateway.example:18789")),
|
||||||
token: nil,
|
token: nil,
|
||||||
@@ -230,7 +230,7 @@ import Testing
|
|||||||
#expect(url.absoluteString == "http://gateway.example:18789/")
|
#expect(url.absoluteString == "http://gateway.example:18789/")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func dashboardURLPrefersPathFromConfigURL() throws {
|
@Test func `dashboard URL prefers path from config URL`() throws {
|
||||||
let config: GatewayConnection.Config = try (
|
let config: GatewayConnection.Config = try (
|
||||||
url: #require(URL(string: "wss://gateway.example:443/remote-ui")),
|
url: #require(URL(string: "wss://gateway.example:443/remote-ui")),
|
||||||
token: nil,
|
token: nil,
|
||||||
@@ -243,7 +243,7 @@ import Testing
|
|||||||
#expect(url.absoluteString == "https://gateway.example:443/remote-ui/")
|
#expect(url.absoluteString == "https://gateway.example:443/remote-ui/")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func dashboardURLUsesFragmentTokenAndOmitsPassword() throws {
|
@Test func `dashboard URL uses fragment token and omits password`() throws {
|
||||||
let config: GatewayConnection.Config = try (
|
let config: GatewayConnection.Config = try (
|
||||||
url: #require(URL(string: "ws://127.0.0.1:18789")),
|
url: #require(URL(string: "ws://127.0.0.1:18789")),
|
||||||
token: "abc123",
|
token: "abc123",
|
||||||
@@ -257,18 +257,18 @@ import Testing
|
|||||||
#expect(url.query == nil)
|
#expect(url.query == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func normalizeGatewayUrlAddsDefaultPortForLoopbackWs() {
|
@Test func `normalize gateway url adds default port for loopback ws`() {
|
||||||
let url = GatewayRemoteConfig.normalizeGatewayUrl("ws://127.0.0.1")
|
let url = GatewayRemoteConfig.normalizeGatewayUrl("ws://127.0.0.1")
|
||||||
#expect(url?.port == 18789)
|
#expect(url?.port == 18789)
|
||||||
#expect(url?.absoluteString == "ws://127.0.0.1:18789")
|
#expect(url?.absoluteString == "ws://127.0.0.1:18789")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func normalizeGatewayUrlRejectsNonLoopbackWs() {
|
@Test func `normalize gateway url rejects non loopback ws`() {
|
||||||
let url = GatewayRemoteConfig.normalizeGatewayUrl("ws://gateway.example:18789")
|
let url = GatewayRemoteConfig.normalizeGatewayUrl("ws://gateway.example:18789")
|
||||||
#expect(url == nil)
|
#expect(url == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func normalizeGatewayUrlRejectsPrefixBypassLoopbackHost() {
|
@Test func `normalize gateway url rejects prefix bypass loopback host`() {
|
||||||
let url = GatewayRemoteConfig.normalizeGatewayUrl("ws://127.attacker.example")
|
let url = GatewayRemoteConfig.normalizeGatewayUrl("ws://127.attacker.example")
|
||||||
#expect(url == nil)
|
#expect(url == nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct GatewayEnvironmentTests {
|
struct GatewayEnvironmentTests {
|
||||||
@Test func semverParsesCommonForms() {
|
@Test func `semver parses common forms`() {
|
||||||
#expect(Semver.parse("1.2.3") == Semver(major: 1, minor: 2, patch: 3))
|
#expect(Semver.parse("1.2.3") == Semver(major: 1, minor: 2, patch: 3))
|
||||||
#expect(Semver.parse(" v1.2.3 \n") == Semver(major: 1, minor: 2, patch: 3))
|
#expect(Semver.parse(" v1.2.3 \n") == Semver(major: 1, minor: 2, patch: 3))
|
||||||
#expect(Semver.parse("v2.0.0") == Semver(major: 2, minor: 0, patch: 0))
|
#expect(Semver.parse("v2.0.0") == Semver(major: 2, minor: 0, patch: 0))
|
||||||
@@ -21,7 +21,7 @@ import Testing
|
|||||||
#expect(Semver.parse("1.2.x") == nil)
|
#expect(Semver.parse("1.2.x") == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func semverCompatibilityRequiresSameMajorAndNotOlder() {
|
@Test func `semver compatibility requires same major and not older`() {
|
||||||
let required = Semver(major: 2, minor: 1, patch: 0)
|
let required = Semver(major: 2, minor: 1, patch: 0)
|
||||||
#expect(Semver(major: 2, minor: 1, patch: 0).compatible(with: required))
|
#expect(Semver(major: 2, minor: 1, patch: 0).compatible(with: required))
|
||||||
#expect(Semver(major: 2, minor: 2, patch: 0).compatible(with: required))
|
#expect(Semver(major: 2, minor: 2, patch: 0).compatible(with: required))
|
||||||
@@ -31,7 +31,7 @@ import Testing
|
|||||||
#expect(Semver(major: 1, minor: 9, patch: 9).compatible(with: required) == false)
|
#expect(Semver(major: 1, minor: 9, patch: 9).compatible(with: required) == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func gatewayPortDefaultsAndRespectsOverride() async {
|
@Test func `gateway port defaults and respects override`() async {
|
||||||
let configPath = TestIsolation.tempConfigPath()
|
let configPath = TestIsolation.tempConfigPath()
|
||||||
await TestIsolation.withIsolatedState(
|
await TestIsolation.withIsolatedState(
|
||||||
env: ["OPENCLAW_CONFIG_PATH": configPath],
|
env: ["OPENCLAW_CONFIG_PATH": configPath],
|
||||||
@@ -46,7 +46,7 @@ import Testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func expectedGatewayVersionFromStringUsesParser() {
|
@Test func `expected gateway version from string uses parser`() {
|
||||||
#expect(GatewayEnvironment.expectedGatewayVersion(from: "v9.1.2") == Semver(major: 9, minor: 1, patch: 2))
|
#expect(GatewayEnvironment.expectedGatewayVersion(from: "v9.1.2") == Semver(major: 9, minor: 1, patch: 2))
|
||||||
#expect(GatewayEnvironment.expectedGatewayVersion(from: "2026.1.11-4") == Semver(
|
#expect(GatewayEnvironment.expectedGatewayVersion(from: "2026.1.11-4") == Semver(
|
||||||
major: 2026,
|
major: 2026,
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import Foundation
|
|||||||
import OpenClawProtocol
|
import OpenClawProtocol
|
||||||
import Testing
|
import Testing
|
||||||
|
|
||||||
@Suite struct GatewayFrameDecodeTests {
|
struct GatewayFrameDecodeTests {
|
||||||
@Test func decodesEventFrameWithAnyCodablePayload() throws {
|
@Test func `decodes event frame with any codable payload`() throws {
|
||||||
let json = """
|
let json = """
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
@@ -29,7 +29,7 @@ import Testing
|
|||||||
#expect(evt.seq == 7)
|
#expect(evt.seq == 7)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func decodesRequestFrameWithNestedParams() throws {
|
@Test func `decodes request frame with nested params`() throws {
|
||||||
let json = """
|
let json = """
|
||||||
{
|
{
|
||||||
"type": "req",
|
"type": "req",
|
||||||
@@ -68,7 +68,7 @@ import Testing
|
|||||||
#expect(meta?["count"]?.value as? Int == 2)
|
#expect(meta?["count"]?.value as? Int == 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func decodesUnknownFrameAndPreservesRaw() throws {
|
@Test func `decodes unknown frame and preserves raw`() throws {
|
||||||
let json = """
|
let json = """
|
||||||
{
|
{
|
||||||
"type": "made-up",
|
"type": "made-up",
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct GatewayLaunchAgentManagerTests {
|
struct GatewayLaunchAgentManagerTests {
|
||||||
@Test func launchAgentPlistSnapshotParsesArgsAndEnv() throws {
|
@Test func `launch agent plist snapshot parses args and env`() throws {
|
||||||
let url = FileManager().temporaryDirectory
|
let url = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("openclaw-launchd-\(UUID().uuidString).plist")
|
.appendingPathComponent("openclaw-launchd-\(UUID().uuidString).plist")
|
||||||
let plist: [String: Any] = [
|
let plist: [String: Any] = [
|
||||||
@@ -24,7 +24,7 @@ import Testing
|
|||||||
#expect(snapshot.password == "pw")
|
#expect(snapshot.password == "pw")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func launchAgentPlistSnapshotAllowsMissingBind() throws {
|
@Test func `launch agent plist snapshot allows missing bind`() throws {
|
||||||
let url = FileManager().temporaryDirectory
|
let url = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("openclaw-launchd-\(UUID().uuidString).plist")
|
.appendingPathComponent("openclaw-launchd-\(UUID().uuidString).plist")
|
||||||
let plist: [String: Any] = [
|
let plist: [String: Any] = [
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Testing
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct GatewayProcessManagerTests {
|
struct GatewayProcessManagerTests {
|
||||||
@Test func clearsLastFailureWhenHealthSucceeds() async throws {
|
@Test func `clears last failure when health succeeds`() async throws {
|
||||||
let session = GatewayTestWebSocketSession(
|
let session = GatewayTestWebSocketSession(
|
||||||
taskFactory: {
|
taskFactory: {
|
||||||
GatewayTestWebSocketTask(
|
GatewayTestWebSocketTask(
|
||||||
|
|||||||
@@ -83,9 +83,9 @@ enum GatewayWebSocketTestSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension NSLock {
|
extension NSLock {
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
func withLock<T>(_ body: () throws -> T) rethrows -> T {
|
fileprivate func withLock<T>(_ body: () throws -> T) rethrows -> T {
|
||||||
self.lock(); defer { self.unlock() }
|
self.lock(); defer { self.unlock() }
|
||||||
return try body()
|
return try body()
|
||||||
}
|
}
|
||||||
@@ -129,7 +129,10 @@ final class GatewayTestWebSocketTask: WebSocketTasking, @unchecked Sendable {
|
|||||||
|
|
||||||
func cancel(with closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
|
func cancel(with closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
|
||||||
_ = (closeCode, reason)
|
_ = (closeCode, reason)
|
||||||
let handler = self.lock.withLock { () -> (@Sendable (Result<URLSessionWebSocketTask.Message, Error>) -> Void)? in
|
let handler = self.lock.withLock { () -> (@Sendable (Result<
|
||||||
|
URLSessionWebSocketTask.Message,
|
||||||
|
Error,
|
||||||
|
>) -> Void)? in
|
||||||
self._state = .canceling
|
self._state = .canceling
|
||||||
self.cancelCount += 1
|
self.cancelCount += 1
|
||||||
defer { self.pendingReceiveHandler = nil }
|
defer { self.pendingReceiveHandler = nil }
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct HealthDecodeTests {
|
struct HealthDecodeTests {
|
||||||
private let sampleJSON: String = // minimal but complete payload
|
private let sampleJSON: String = // minimal but complete payload
|
||||||
"""
|
"""
|
||||||
{"ts":1733622000,"durationMs":420,"channels":{"whatsapp":{"linked":true,"authAgeMs":120000},"telegram":{"configured":true,"probe":{"ok":true,"elapsedMs":800}}},"channelOrder":["whatsapp","telegram"],"heartbeatSeconds":60,"sessions":{"path":"/tmp/sessions.json","count":1,"recent":[{"key":"abc","updatedAt":1733621900,"age":120000}]}}
|
{"ts":1733622000,"durationMs":420,"channels":{"whatsapp":{"linked":true,"authAgeMs":120000},"telegram":{"configured":true,"probe":{"ok":true,"elapsedMs":800}}},"channelOrder":["whatsapp","telegram"],"heartbeatSeconds":60,"sessions":{"path":"/tmp/sessions.json","count":1,"recent":[{"key":"abc","updatedAt":1733621900,"age":120000}]}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@Test func decodesCleanJSON() {
|
@Test func `decodes clean JSON`() {
|
||||||
let data = Data(sampleJSON.utf8)
|
let data = Data(sampleJSON.utf8)
|
||||||
let snap = decodeHealthSnapshot(from: data)
|
let snap = decodeHealthSnapshot(from: data)
|
||||||
|
|
||||||
@@ -16,14 +16,14 @@ import Testing
|
|||||||
#expect(snap?.sessions.count == 1)
|
#expect(snap?.sessions.count == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func decodesWithLeadingNoise() {
|
@Test func `decodes with leading noise`() {
|
||||||
let noisy = "debug: something logged\n" + self.sampleJSON + "\ntrailer"
|
let noisy = "debug: something logged\n" + self.sampleJSON + "\ntrailer"
|
||||||
let snap = decodeHealthSnapshot(from: Data(noisy.utf8))
|
let snap = decodeHealthSnapshot(from: Data(noisy.utf8))
|
||||||
|
|
||||||
#expect(snap?.channels["telegram"]?.probe?.elapsedMs == 800)
|
#expect(snap?.channels["telegram"]?.probe?.elapsedMs == 800)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func failsWithoutBraces() {
|
@Test func `fails without braces`() {
|
||||||
let data = Data("no json here".utf8)
|
let data = Data("no json here".utf8)
|
||||||
let snap = decodeHealthSnapshot(from: data)
|
let snap = decodeHealthSnapshot(from: data)
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct HealthStoreStateTests {
|
struct HealthStoreStateTests {
|
||||||
@Test @MainActor func linkedChannelProbeFailureDegradesState() {
|
@Test @MainActor func `linked channel probe failure degrades state`() {
|
||||||
let snap = HealthSnapshot(
|
let snap = HealthSnapshot(
|
||||||
ok: true,
|
ok: true,
|
||||||
ts: 0,
|
ts: 0,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Testing
|
|||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
struct HostEnvSanitizerTests {
|
struct HostEnvSanitizerTests {
|
||||||
@Test func sanitizeBlocksShellTraceVariables() {
|
@Test func `sanitize blocks shell trace variables`() {
|
||||||
let env = HostEnvSanitizer.sanitize(overrides: [
|
let env = HostEnvSanitizer.sanitize(overrides: [
|
||||||
"SHELLOPTS": "xtrace",
|
"SHELLOPTS": "xtrace",
|
||||||
"PS4": "$(touch /tmp/pwned)",
|
"PS4": "$(touch /tmp/pwned)",
|
||||||
@@ -13,7 +13,7 @@ struct HostEnvSanitizerTests {
|
|||||||
#expect(env["OPENCLAW_TEST"] == "1")
|
#expect(env["OPENCLAW_TEST"] == "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func sanitizeShellWrapperAllowsOnlyExplicitOverrideKeys() {
|
@Test func `sanitize shell wrapper allows only explicit override keys`() {
|
||||||
let env = HostEnvSanitizer.sanitize(
|
let env = HostEnvSanitizer.sanitize(
|
||||||
overrides: [
|
overrides: [
|
||||||
"LANG": "C",
|
"LANG": "C",
|
||||||
@@ -29,7 +29,7 @@ struct HostEnvSanitizerTests {
|
|||||||
#expect(env["PS4"] == nil)
|
#expect(env["PS4"] == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func sanitizeNonShellWrapperKeepsRegularOverrides() {
|
@Test func `sanitize non shell wrapper keeps regular overrides`() {
|
||||||
let env = HostEnvSanitizer.sanitize(overrides: ["OPENCLAW_TOKEN": "secret"])
|
let env = HostEnvSanitizer.sanitize(overrides: ["OPENCLAW_TOKEN": "secret"])
|
||||||
#expect(env["OPENCLAW_TOKEN"] == "secret")
|
#expect(env["OPENCLAW_TOKEN"] == "secret")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Testing
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct HoverHUDControllerTests {
|
struct HoverHUDControllerTests {
|
||||||
@Test func hoverHUDControllerPresentsAndDismisses() async {
|
@Test func `hover HUD controller presents and dismisses`() async {
|
||||||
let controller = HoverHUDController()
|
let controller = HoverHUDController()
|
||||||
controller.setSuppressed(false)
|
controller.setSuppressed(false)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Testing
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct InstancesSettingsSmokeTests {
|
struct InstancesSettingsSmokeTests {
|
||||||
@Test func instancesSettingsBuildsBodyWithMultipleInstances() {
|
@Test func `instances settings builds body with multiple instances`() {
|
||||||
let store = InstancesStore(isPreview: true)
|
let store = InstancesStore(isPreview: true)
|
||||||
store.statusMessage = "Loaded"
|
store.statusMessage = "Loaded"
|
||||||
store.instances = [
|
store.instances = [
|
||||||
@@ -53,7 +53,7 @@ struct InstancesSettingsSmokeTests {
|
|||||||
_ = view.body
|
_ = view.body
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func instancesSettingsExercisesHelpers() {
|
@Test func `instances settings exercises helpers`() {
|
||||||
InstancesSettings.exerciseForTesting()
|
InstancesSettings.exerciseForTesting()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import OpenClawProtocol
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct InstancesStoreTests {
|
struct InstancesStoreTests {
|
||||||
@Test
|
@Test
|
||||||
@MainActor
|
@MainActor
|
||||||
func presenceEventPayloadDecodesViaJSONEncoder() {
|
func `presence event payload decodes via JSON encoder`() {
|
||||||
// Build a payload that mirrors the gateway's presence event shape:
|
// Build a payload that mirrors the gateway's presence event shape:
|
||||||
// { "presence": [ PresenceEntry ] }
|
// { "presence": [ PresenceEntry ] }
|
||||||
let entry: [String: OpenClawProtocol.AnyCodable] = [
|
let entry: [String: OpenClawProtocol.AnyCodable] = [
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct LogLocatorTests {
|
struct LogLocatorTests {
|
||||||
@Test func launchdGatewayLogPathEnsuresTmpDirExists() {
|
@Test func `launchd gateway log path ensures tmp dir exists`() {
|
||||||
let fm = FileManager()
|
let fm = FileManager()
|
||||||
let baseDir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
let baseDir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
||||||
let logDir = baseDir.appendingPathComponent("openclaw-tests-\(UUID().uuidString)")
|
let logDir = baseDir.appendingPathComponent("openclaw-tests-\(UUID().uuidString)")
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import Testing
|
|||||||
struct LowCoverageHelperTests {
|
struct LowCoverageHelperTests {
|
||||||
private typealias ProtoAnyCodable = OpenClawProtocol.AnyCodable
|
private typealias ProtoAnyCodable = OpenClawProtocol.AnyCodable
|
||||||
|
|
||||||
@Test func anyCodableHelperAccessors() throws {
|
@Test func `any codable helper accessors`() throws {
|
||||||
let payload: [String: ProtoAnyCodable] = [
|
let payload: [String: ProtoAnyCodable] = [
|
||||||
"title": ProtoAnyCodable("Hello"),
|
"title": ProtoAnyCodable("Hello"),
|
||||||
"flag": ProtoAnyCodable(true),
|
"flag": ProtoAnyCodable(true),
|
||||||
@@ -28,7 +28,7 @@ struct LowCoverageHelperTests {
|
|||||||
#expect((foundation?["title"] as? String) == "Hello")
|
#expect((foundation?["title"] as? String) == "Hello")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func attributedStringStripsForegroundColor() {
|
@Test func `attributed string strips foreground color`() {
|
||||||
let text = NSMutableAttributedString(string: "Test")
|
let text = NSMutableAttributedString(string: "Test")
|
||||||
text.addAttribute(.foregroundColor, value: NSColor.red, range: NSRange(location: 0, length: 4))
|
text.addAttribute(.foregroundColor, value: NSColor.red, range: NSRange(location: 0, length: 4))
|
||||||
let stripped = text.strippingForegroundColor()
|
let stripped = text.strippingForegroundColor()
|
||||||
@@ -36,29 +36,29 @@ struct LowCoverageHelperTests {
|
|||||||
#expect(color == nil)
|
#expect(color == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func viewMetricsReduceWidth() {
|
@Test func `view metrics reduce width`() {
|
||||||
let value = ViewMetricsTesting.reduceWidth(current: 120, next: 180)
|
let value = ViewMetricsTesting.reduceWidth(current: 120, next: 180)
|
||||||
#expect(value == 180)
|
#expect(value == 180)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func shellExecutorHandlesEmptyCommand() async {
|
@Test func `shell executor handles empty command`() async {
|
||||||
let result = await ShellExecutor.runDetailed(command: [], cwd: nil, env: nil, timeout: nil)
|
let result = await ShellExecutor.runDetailed(command: [], cwd: nil, env: nil, timeout: nil)
|
||||||
#expect(result.success == false)
|
#expect(result.success == false)
|
||||||
#expect(result.errorMessage != nil)
|
#expect(result.errorMessage != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func shellExecutorRunsCommand() async {
|
@Test func `shell executor runs command`() async {
|
||||||
let result = await ShellExecutor.runDetailed(command: ["/bin/echo", "ok"], cwd: nil, env: nil, timeout: 2)
|
let result = await ShellExecutor.runDetailed(command: ["/bin/echo", "ok"], cwd: nil, env: nil, timeout: 2)
|
||||||
#expect(result.success == true)
|
#expect(result.success == true)
|
||||||
#expect(result.stdout.contains("ok") || result.stderr.contains("ok"))
|
#expect(result.stdout.contains("ok") || result.stderr.contains("ok"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func shellExecutorTimesOut() async {
|
@Test func `shell executor times out`() async {
|
||||||
let result = await ShellExecutor.runDetailed(command: ["/bin/sleep", "1"], cwd: nil, env: nil, timeout: 0.05)
|
let result = await ShellExecutor.runDetailed(command: ["/bin/sleep", "1"], cwd: nil, env: nil, timeout: 0.05)
|
||||||
#expect(result.timedOut == true)
|
#expect(result.timedOut == true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func shellExecutorDrainsStdoutAndStderr() async {
|
@Test func `shell executor drains stdout and stderr`() async {
|
||||||
let script = """
|
let script = """
|
||||||
i=0
|
i=0
|
||||||
while [ $i -lt 2000 ]; do
|
while [ $i -lt 2000 ]; do
|
||||||
@@ -77,7 +77,7 @@ struct LowCoverageHelperTests {
|
|||||||
#expect(result.stderr.contains("stderr-1999"))
|
#expect(result.stderr.contains("stderr-1999"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func nodeInfoCodableRoundTrip() throws {
|
@Test func `node info codable round trip`() throws {
|
||||||
let info = NodeInfo(
|
let info = NodeInfo(
|
||||||
nodeId: "node-1",
|
nodeId: "node-1",
|
||||||
displayName: "Node One",
|
displayName: "Node One",
|
||||||
@@ -100,7 +100,7 @@ struct LowCoverageHelperTests {
|
|||||||
#expect(decoded.isConnected == false)
|
#expect(decoded.isConnected == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test @MainActor func presenceReporterHelpers() {
|
@Test @MainActor func `presence reporter helpers`() {
|
||||||
let summary = PresenceReporter._testComposePresenceSummary(mode: "local", reason: "test")
|
let summary = PresenceReporter._testComposePresenceSummary(mode: "local", reason: "test")
|
||||||
#expect(summary.contains("mode local"))
|
#expect(summary.contains("mode local"))
|
||||||
#expect(!PresenceReporter._testAppVersionString().isEmpty)
|
#expect(!PresenceReporter._testAppVersionString().isEmpty)
|
||||||
@@ -109,7 +109,7 @@ struct LowCoverageHelperTests {
|
|||||||
_ = PresenceReporter._testPrimaryIPv4Address()
|
_ = PresenceReporter._testPrimaryIPv4Address()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func portGuardianParsesListenersAndBuildsReports() {
|
@Test func `port guardian parses listeners and builds reports`() {
|
||||||
let output = """
|
let output = """
|
||||||
p123
|
p123
|
||||||
cnode
|
cnode
|
||||||
@@ -139,7 +139,7 @@ struct LowCoverageHelperTests {
|
|||||||
#expect(emptyReport.summary.contains("Nothing is listening"))
|
#expect(emptyReport.summary.contains("Nothing is listening"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test @MainActor func canvasSchemeHandlerResolvesFilesAndErrors() throws {
|
@Test @MainActor func `canvas scheme handler resolves files and errors`() throws {
|
||||||
let root = FileManager().temporaryDirectory
|
let root = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("canvas-\(UUID().uuidString)", isDirectory: true)
|
.appendingPathComponent("canvas-\(UUID().uuidString)", isDirectory: true)
|
||||||
defer { try? FileManager().removeItem(at: root) }
|
defer { try? FileManager().removeItem(at: root) }
|
||||||
@@ -168,7 +168,7 @@ struct LowCoverageHelperTests {
|
|||||||
#expect(handler._testTextEncodingName(for: "application/octet-stream") == nil)
|
#expect(handler._testTextEncodingName(for: "application/octet-stream") == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test @MainActor func menuContextCardInjectorInsertsAndFindsIndex() {
|
@Test @MainActor func `menu context card injector inserts and finds index`() {
|
||||||
let injector = MenuContextCardInjector()
|
let injector = MenuContextCardInjector()
|
||||||
let menu = NSMenu()
|
let menu = NSMenu()
|
||||||
menu.minimumWidth = 280
|
menu.minimumWidth = 280
|
||||||
@@ -190,7 +190,7 @@ struct LowCoverageHelperTests {
|
|||||||
#expect(injector._testFindInsertIndex(in: fallbackMenu) == 1)
|
#expect(injector._testFindInsertIndex(in: fallbackMenu) == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test @MainActor func canvasWindowHelperFunctions() throws {
|
@Test @MainActor func `canvas window helper functions`() throws {
|
||||||
#expect(CanvasWindowController._testSanitizeSessionKey(" main ") == "main")
|
#expect(CanvasWindowController._testSanitizeSessionKey(" main ") == "main")
|
||||||
#expect(CanvasWindowController._testSanitizeSessionKey("bad/..") == "bad___")
|
#expect(CanvasWindowController._testSanitizeSessionKey("bad/..") == "bad___")
|
||||||
#expect(CanvasWindowController._testJSOptionalStringLiteral(nil) == "null")
|
#expect(CanvasWindowController._testJSOptionalStringLiteral(nil) == "null")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Testing
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct LowCoverageViewSmokeTests {
|
struct LowCoverageViewSmokeTests {
|
||||||
@Test func contextMenuCardBuildsBody() {
|
@Test func `context menu card builds body`() {
|
||||||
let loading = ContextMenuCardView(rows: [], statusText: "Loading…", isLoading: true)
|
let loading = ContextMenuCardView(rows: [], statusText: "Loading…", isLoading: true)
|
||||||
_ = loading.body
|
_ = loading.body
|
||||||
|
|
||||||
@@ -18,14 +18,14 @@ struct LowCoverageViewSmokeTests {
|
|||||||
_ = withRows.body
|
_ = withRows.body
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func settingsToggleRowBuildsBody() {
|
@Test func `settings toggle row builds body`() {
|
||||||
var flag = false
|
var flag = false
|
||||||
let binding = Binding(get: { flag }, set: { flag = $0 })
|
let binding = Binding(get: { flag }, set: { flag = $0 })
|
||||||
let view = SettingsToggleRow(title: "Enable", subtitle: "Detail", binding: binding)
|
let view = SettingsToggleRow(title: "Enable", subtitle: "Detail", binding: binding)
|
||||||
_ = view.body
|
_ = view.body
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func voiceWakeTestCardBuildsBodyAcrossStates() {
|
@Test func `voice wake test card builds body across states`() {
|
||||||
var state = VoiceWakeTestState.idle
|
var state = VoiceWakeTestState.idle
|
||||||
var isTesting = false
|
var isTesting = false
|
||||||
let stateBinding = Binding(get: { state }, set: { state = $0 })
|
let stateBinding = Binding(get: { state }, set: { state = $0 })
|
||||||
@@ -44,7 +44,7 @@ struct LowCoverageViewSmokeTests {
|
|||||||
_ = VoiceWakeTestCard(testState: stateBinding, isTesting: testingBinding, onToggle: {}).body
|
_ = VoiceWakeTestCard(testState: stateBinding, isTesting: testingBinding, onToggle: {}).body
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func agentEventsWindowBuildsBodyWithEvent() {
|
@Test func `agent events window builds body with event`() {
|
||||||
AgentEventStore.shared.clear()
|
AgentEventStore.shared.clear()
|
||||||
let sample = ControlAgentEvent(
|
let sample = ControlAgentEvent(
|
||||||
runId: "run-1",
|
runId: "run-1",
|
||||||
@@ -58,7 +58,7 @@ struct LowCoverageViewSmokeTests {
|
|||||||
AgentEventStore.shared.clear()
|
AgentEventStore.shared.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func notifyOverlayPresentsAndDismisses() async {
|
@Test func `notify overlay presents and dismisses`() async {
|
||||||
let controller = NotifyOverlayController()
|
let controller = NotifyOverlayController()
|
||||||
controller.present(title: "Hello", body: "World", autoDismissAfter: 0)
|
controller.present(title: "Hello", body: "World", autoDismissAfter: 0)
|
||||||
controller.present(title: "Updated", body: "Again", autoDismissAfter: 0)
|
controller.present(title: "Updated", body: "Again", autoDismissAfter: 0)
|
||||||
@@ -66,14 +66,14 @@ struct LowCoverageViewSmokeTests {
|
|||||||
try? await Task.sleep(nanoseconds: 250_000_000)
|
try? await Task.sleep(nanoseconds: 250_000_000)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func visualEffectViewHostsInNSHostingView() {
|
@Test func `visual effect view hosts in NS hosting view`() {
|
||||||
let hosting = NSHostingView(rootView: VisualEffectView(material: .sidebar))
|
let hosting = NSHostingView(rootView: VisualEffectView(material: .sidebar))
|
||||||
_ = hosting.fittingSize
|
_ = hosting.fittingSize
|
||||||
hosting.rootView = VisualEffectView(material: .popover, emphasized: true)
|
hosting.rootView = VisualEffectView(material: .popover, emphasized: true)
|
||||||
_ = hosting.fittingSize
|
_ = hosting.fittingSize
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func menuHostedItemHostsContent() {
|
@Test func `menu hosted item hosts content`() {
|
||||||
let view = MenuHostedItem(width: 240, rootView: AnyView(Text("Menu")))
|
let view = MenuHostedItem(width: 240, rootView: AnyView(Text("Menu")))
|
||||||
let hosting = NSHostingView(rootView: view)
|
let hosting = NSHostingView(rootView: view)
|
||||||
_ = hosting.fittingSize
|
_ = hosting.fittingSize
|
||||||
@@ -81,18 +81,18 @@ struct LowCoverageViewSmokeTests {
|
|||||||
_ = hosting.fittingSize
|
_ = hosting.fittingSize
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func dockIconManagerUpdatesVisibility() {
|
@Test func `dock icon manager updates visibility`() {
|
||||||
_ = NSApplication.shared
|
_ = NSApplication.shared
|
||||||
UserDefaults.standard.set(false, forKey: showDockIconKey)
|
UserDefaults.standard.set(false, forKey: showDockIconKey)
|
||||||
DockIconManager.shared.updateDockVisibility()
|
DockIconManager.shared.updateDockVisibility()
|
||||||
DockIconManager.shared.temporarilyShowDock()
|
DockIconManager.shared.temporarilyShowDock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func voiceWakeSettingsExercisesHelpers() {
|
@Test func `voice wake settings exercises helpers`() {
|
||||||
VoiceWakeSettings.exerciseForTesting()
|
VoiceWakeSettings.exerciseForTesting()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func debugSettingsExercisesHelpers() async {
|
@Test func `debug settings exercises helpers`() async {
|
||||||
await DebugSettings.exerciseForTesting()
|
await DebugSettings.exerciseForTesting()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import OpenClawProtocol
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct MacGatewayChatTransportMappingTests {
|
struct MacGatewayChatTransportMappingTests {
|
||||||
@Test func snapshotMapsToHealth() {
|
@Test func `snapshot maps to health`() {
|
||||||
let snapshot = Snapshot(
|
let snapshot = Snapshot(
|
||||||
presence: [],
|
presence: [],
|
||||||
health: OpenClawProtocol.AnyCodable(["ok": OpenClawProtocol.AnyCodable(false)]),
|
health: OpenClawProtocol.AnyCodable(["ok": OpenClawProtocol.AnyCodable(false)]),
|
||||||
@@ -35,7 +35,7 @@ import Testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func healthEventMapsToHealth() {
|
@Test func `health event maps to health`() {
|
||||||
let frame = EventFrame(
|
let frame = EventFrame(
|
||||||
type: "event",
|
type: "event",
|
||||||
event: "health",
|
event: "health",
|
||||||
@@ -52,7 +52,7 @@ import Testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func tickEventMapsToTick() {
|
@Test func `tick event maps to tick`() {
|
||||||
let frame = EventFrame(type: "event", event: "tick", payload: nil, seq: 1, stateversion: nil)
|
let frame = EventFrame(type: "event", event: "tick", payload: nil, seq: 1, stateversion: nil)
|
||||||
let mapped = MacGatewayChatTransport.mapPushToTransportEvent(.event(frame))
|
let mapped = MacGatewayChatTransport.mapPushToTransportEvent(.event(frame))
|
||||||
#expect({
|
#expect({
|
||||||
@@ -61,7 +61,7 @@ import Testing
|
|||||||
}())
|
}())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func chatEventMapsToChat() {
|
@Test func `chat event maps to chat`() {
|
||||||
let payload = OpenClawProtocol.AnyCodable([
|
let payload = OpenClawProtocol.AnyCodable([
|
||||||
"runId": OpenClawProtocol.AnyCodable("run-1"),
|
"runId": OpenClawProtocol.AnyCodable("run-1"),
|
||||||
"sessionKey": OpenClawProtocol.AnyCodable("main"),
|
"sessionKey": OpenClawProtocol.AnyCodable("main"),
|
||||||
@@ -80,7 +80,7 @@ import Testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func unknownEventMapsToNil() {
|
@Test func `unknown event maps to nil`() {
|
||||||
let frame = EventFrame(
|
let frame = EventFrame(
|
||||||
type: "event",
|
type: "event",
|
||||||
event: "unknown",
|
event: "unknown",
|
||||||
@@ -91,7 +91,7 @@ import Testing
|
|||||||
#expect(mapped == nil)
|
#expect(mapped == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func seqGapMapsToSeqGap() {
|
@Test func `seq gap maps to seq gap`() {
|
||||||
let mapped = MacGatewayChatTransport.mapPushToTransportEvent(.seqGap(expected: 1, received: 9))
|
let mapped = MacGatewayChatTransport.mapPushToTransportEvent(.seqGap(expected: 1, received: 9))
|
||||||
#expect({
|
#expect({
|
||||||
if case .seqGap = mapped { return true }
|
if case .seqGap = mapped { return true }
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Testing
|
|||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
struct MacNodeBrowserProxyTests {
|
struct MacNodeBrowserProxyTests {
|
||||||
@Test func requestUsesBrowserControlEndpointAndWrapsResult() async throws {
|
@Test func `request uses browser control endpoint and wraps result`() async throws {
|
||||||
let proxy = MacNodeBrowserProxy(
|
let proxy = MacNodeBrowserProxy(
|
||||||
endpointProvider: {
|
endpointProvider: {
|
||||||
MacNodeBrowserProxy.Endpoint(
|
MacNodeBrowserProxy.Endpoint(
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import Testing
|
|||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
struct MacNodeRuntimeTests {
|
struct MacNodeRuntimeTests {
|
||||||
@Test func handleInvokeRejectsUnknownCommand() async {
|
@Test func `handle invoke rejects unknown command`() async {
|
||||||
let runtime = MacNodeRuntime()
|
let runtime = MacNodeRuntime()
|
||||||
let response = await runtime.handleInvoke(
|
let response = await runtime.handleInvoke(
|
||||||
BridgeInvokeRequest(id: "req-1", command: "unknown.command"))
|
BridgeInvokeRequest(id: "req-1", command: "unknown.command"))
|
||||||
#expect(response.ok == false)
|
#expect(response.ok == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func handleInvokeRejectsEmptySystemRun() async throws {
|
@Test func `handle invoke rejects empty system run`() async throws {
|
||||||
let runtime = MacNodeRuntime()
|
let runtime = MacNodeRuntime()
|
||||||
let params = OpenClawSystemRunParams(command: [])
|
let params = OpenClawSystemRunParams(command: [])
|
||||||
let json = try String(data: JSONEncoder().encode(params), encoding: .utf8)
|
let json = try String(data: JSONEncoder().encode(params), encoding: .utf8)
|
||||||
@@ -21,7 +21,7 @@ struct MacNodeRuntimeTests {
|
|||||||
#expect(response.ok == false)
|
#expect(response.ok == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func handleInvokeRejectsEmptySystemWhich() async throws {
|
@Test func `handle invoke rejects empty system which`() async throws {
|
||||||
let runtime = MacNodeRuntime()
|
let runtime = MacNodeRuntime()
|
||||||
let params = OpenClawSystemWhichParams(bins: [])
|
let params = OpenClawSystemWhichParams(bins: [])
|
||||||
let json = try String(data: JSONEncoder().encode(params), encoding: .utf8)
|
let json = try String(data: JSONEncoder().encode(params), encoding: .utf8)
|
||||||
@@ -30,7 +30,7 @@ struct MacNodeRuntimeTests {
|
|||||||
#expect(response.ok == false)
|
#expect(response.ok == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func handleInvokeRejectsEmptyNotification() async throws {
|
@Test func `handle invoke rejects empty notification`() async throws {
|
||||||
let runtime = MacNodeRuntime()
|
let runtime = MacNodeRuntime()
|
||||||
let params = OpenClawSystemNotifyParams(title: "", body: "")
|
let params = OpenClawSystemNotifyParams(title: "", body: "")
|
||||||
let json = try String(data: JSONEncoder().encode(params), encoding: .utf8)
|
let json = try String(data: JSONEncoder().encode(params), encoding: .utf8)
|
||||||
@@ -39,7 +39,7 @@ struct MacNodeRuntimeTests {
|
|||||||
#expect(response.ok == false)
|
#expect(response.ok == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func handleInvokeCameraListRequiresEnabledCamera() async {
|
@Test func `handle invoke camera list requires enabled camera`() async {
|
||||||
await TestIsolation.withUserDefaultsValues([cameraEnabledKey: false]) {
|
await TestIsolation.withUserDefaultsValues([cameraEnabledKey: false]) {
|
||||||
let runtime = MacNodeRuntime()
|
let runtime = MacNodeRuntime()
|
||||||
let response = await runtime.handleInvoke(
|
let response = await runtime.handleInvoke(
|
||||||
@@ -49,7 +49,7 @@ struct MacNodeRuntimeTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func handleInvokeScreenRecordUsesInjectedServices() async throws {
|
@Test func `handle invoke screen record uses injected services`() async throws {
|
||||||
@MainActor
|
@MainActor
|
||||||
final class FakeMainActorServices: MacNodeRuntimeMainActorServices, @unchecked Sendable {
|
final class FakeMainActorServices: MacNodeRuntimeMainActorServices, @unchecked Sendable {
|
||||||
func recordScreen(
|
func recordScreen(
|
||||||
@@ -101,20 +101,23 @@ struct MacNodeRuntimeTests {
|
|||||||
#expect(!payload.base64.isEmpty)
|
#expect(!payload.base64.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func handleInvokeBrowserProxyUsesInjectedRequest() async throws {
|
@Test func `handle invoke browser proxy uses injected request`() async {
|
||||||
let runtime = MacNodeRuntime(browserProxyRequest: { paramsJSON in
|
let runtime = MacNodeRuntime(browserProxyRequest: { paramsJSON in
|
||||||
#expect(paramsJSON?.contains("/tabs") == true)
|
#expect(paramsJSON?.contains("/tabs") == true)
|
||||||
return #"{"result":{"ok":true,"tabs":[{"id":"tab-1"}]}}"#
|
return #"{"result":{"ok":true,"tabs":[{"id":"tab-1"}]}}"#
|
||||||
})
|
})
|
||||||
let paramsJSON = #"{"method":"GET","path":"/tabs","timeoutMs":2500}"#
|
let paramsJSON = #"{"method":"GET","path":"/tabs","timeoutMs":2500}"#
|
||||||
let response = await runtime.handleInvoke(
|
let response = await runtime.handleInvoke(
|
||||||
BridgeInvokeRequest(id: "req-browser", command: OpenClawBrowserCommand.proxy.rawValue, paramsJSON: paramsJSON))
|
BridgeInvokeRequest(
|
||||||
|
id: "req-browser",
|
||||||
|
command: OpenClawBrowserCommand.proxy.rawValue,
|
||||||
|
paramsJSON: paramsJSON))
|
||||||
|
|
||||||
#expect(response.ok == true)
|
#expect(response.ok == true)
|
||||||
#expect(response.payloadJSON == #"{"result":{"ok":true,"tabs":[{"id":"tab-1"}]}}"#)
|
#expect(response.payloadJSON == #"{"result":{"ok":true,"tabs":[{"id":"tab-1"}]}}"#)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func handleInvokeBrowserProxyRejectsDisabledBrowserControl() async throws {
|
@Test func `handle invoke browser proxy rejects disabled browser control`() async throws {
|
||||||
let override = TestIsolation.tempConfigPath()
|
let override = TestIsolation.tempConfigPath()
|
||||||
try await TestIsolation.withEnvValues(["OPENCLAW_CONFIG_PATH": override]) {
|
try await TestIsolation.withEnvValues(["OPENCLAW_CONFIG_PATH": override]) {
|
||||||
try JSONSerialization.data(withJSONObject: ["browser": ["enabled": false]])
|
try JSONSerialization.data(withJSONObject: ["browser": ["enabled": false]])
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Testing
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct MasterDiscoveryMenuSmokeTests {
|
struct MasterDiscoveryMenuSmokeTests {
|
||||||
@Test func inlineListBuildsBodyWhenEmpty() {
|
@Test func `inline list builds body when empty`() {
|
||||||
let discovery = GatewayDiscoveryModel(localDisplayName: InstanceIdentity.displayName)
|
let discovery = GatewayDiscoveryModel(localDisplayName: InstanceIdentity.displayName)
|
||||||
discovery.statusText = "Searching…"
|
discovery.statusText = "Searching…"
|
||||||
discovery.gateways = []
|
discovery.gateways = []
|
||||||
@@ -20,7 +20,7 @@ struct MasterDiscoveryMenuSmokeTests {
|
|||||||
_ = view.body
|
_ = view.body
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func inlineListBuildsBodyWithMasterAndSelection() {
|
@Test func `inline list builds body with master and selection`() {
|
||||||
let discovery = GatewayDiscoveryModel(localDisplayName: InstanceIdentity.displayName)
|
let discovery = GatewayDiscoveryModel(localDisplayName: InstanceIdentity.displayName)
|
||||||
discovery.statusText = "Found 1"
|
discovery.statusText = "Found 1"
|
||||||
discovery.gateways = [
|
discovery.gateways = [
|
||||||
@@ -46,7 +46,7 @@ struct MasterDiscoveryMenuSmokeTests {
|
|||||||
_ = view.body
|
_ = view.body
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func menuBuildsBodyWithMasters() {
|
@Test func `menu builds body with masters`() {
|
||||||
let discovery = GatewayDiscoveryModel(localDisplayName: InstanceIdentity.displayName)
|
let discovery = GatewayDiscoveryModel(localDisplayName: InstanceIdentity.displayName)
|
||||||
discovery.statusText = "Found 2"
|
discovery.statusText = "Found 2"
|
||||||
discovery.gateways = [
|
discovery.gateways = [
|
||||||
|
|||||||
@@ -5,28 +5,28 @@ import Testing
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct MenuContentSmokeTests {
|
struct MenuContentSmokeTests {
|
||||||
@Test func menuContentBuildsBodyLocalMode() {
|
@Test func `menu content builds body local mode`() {
|
||||||
let state = AppState(preview: true)
|
let state = AppState(preview: true)
|
||||||
state.connectionMode = .local
|
state.connectionMode = .local
|
||||||
let view = MenuContent(state: state, updater: nil)
|
let view = MenuContent(state: state, updater: nil)
|
||||||
_ = view.body
|
_ = view.body
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func menuContentBuildsBodyRemoteMode() {
|
@Test func `menu content builds body remote mode`() {
|
||||||
let state = AppState(preview: true)
|
let state = AppState(preview: true)
|
||||||
state.connectionMode = .remote
|
state.connectionMode = .remote
|
||||||
let view = MenuContent(state: state, updater: nil)
|
let view = MenuContent(state: state, updater: nil)
|
||||||
_ = view.body
|
_ = view.body
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func menuContentBuildsBodyUnconfiguredMode() {
|
@Test func `menu content builds body unconfigured mode`() {
|
||||||
let state = AppState(preview: true)
|
let state = AppState(preview: true)
|
||||||
state.connectionMode = .unconfigured
|
state.connectionMode = .unconfigured
|
||||||
let view = MenuContent(state: state, updater: nil)
|
let view = MenuContent(state: state, updater: nil)
|
||||||
_ = view.body
|
_ = view.body
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func menuContentBuildsBodyWithDebugAndCanvas() {
|
@Test func `menu content builds body with debug and canvas`() {
|
||||||
let state = AppState(preview: true)
|
let state = AppState(preview: true)
|
||||||
state.connectionMode = .local
|
state.connectionMode = .local
|
||||||
state.debugPaneEnabled = true
|
state.debugPaneEnabled = true
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Testing
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct MenuSessionsInjectorTests {
|
struct MenuSessionsInjectorTests {
|
||||||
@Test func injectsDisconnectedMessage() {
|
@Test func `injects disconnected message`() {
|
||||||
let injector = MenuSessionsInjector()
|
let injector = MenuSessionsInjector()
|
||||||
injector.setTestingControlChannelConnected(false)
|
injector.setTestingControlChannelConnected(false)
|
||||||
injector.setTestingSnapshot(nil, errorText: nil)
|
injector.setTestingSnapshot(nil, errorText: nil)
|
||||||
@@ -19,7 +19,7 @@ struct MenuSessionsInjectorTests {
|
|||||||
#expect(menu.items.contains { $0.tag == 9_415_557 })
|
#expect(menu.items.contains { $0.tag == 9_415_557 })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func injectsSessionRows() {
|
@Test func `injects session rows`() {
|
||||||
let injector = MenuSessionsInjector()
|
let injector = MenuSessionsInjector()
|
||||||
injector.setTestingControlChannelConnected(true)
|
injector.setTestingControlChannelConnected(true)
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ struct MenuSessionsInjectorTests {
|
|||||||
#expect(menu.items.contains { $0.tag == 9_415_557 && $0.isSeparatorItem })
|
#expect(menu.items.contains { $0.tag == 9_415_557 && $0.isSeparatorItem })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func costUsageSubmenuDoesNotUseInjectorDelegate() {
|
@Test func `cost usage submenu does not use injector delegate`() {
|
||||||
let injector = MenuSessionsInjector()
|
let injector = MenuSessionsInjector()
|
||||||
injector.setTestingControlChannelConnected(true)
|
injector.setTestingControlChannelConnected(true)
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite
|
|
||||||
struct ModelCatalogLoaderTests {
|
struct ModelCatalogLoaderTests {
|
||||||
@Test
|
@Test
|
||||||
func loadParsesModelsFromTypeScriptAndSorts() async throws {
|
func `load parses models from type script and sorts`() async throws {
|
||||||
let src = """
|
let src = """
|
||||||
export const MODELS = {
|
export const MODELS = {
|
||||||
openai: {
|
openai: {
|
||||||
@@ -40,7 +39,7 @@ struct ModelCatalogLoaderTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func loadWithNoExportReturnsEmptyChoices() async throws {
|
func `load with no export returns empty choices`() async throws {
|
||||||
let src = "const NOPE = 1;"
|
let src = "const NOPE = 1;"
|
||||||
let tmp = FileManager().temporaryDirectory
|
let tmp = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("models-\(UUID().uuidString).ts")
|
.appendingPathComponent("models-\(UUID().uuidString).ts")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Testing
|
|||||||
|
|
||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
struct NixModeStableSuiteTests {
|
struct NixModeStableSuiteTests {
|
||||||
@Test func resolvesFromStableSuiteForAppBundles() throws {
|
@Test func `resolves from stable suite for app bundles`() throws {
|
||||||
let suite = try #require(UserDefaults(suiteName: launchdLabel))
|
let suite = try #require(UserDefaults(suiteName: launchdLabel))
|
||||||
let key = "openclaw.nixMode"
|
let key = "openclaw.nixMode"
|
||||||
let prev = suite.object(forKey: key)
|
let prev = suite.object(forKey: key)
|
||||||
@@ -25,7 +25,7 @@ struct NixModeStableSuiteTests {
|
|||||||
#expect(resolved)
|
#expect(resolved)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func ignoresStableSuiteOutsideAppBundles() throws {
|
@Test func `ignores stable suite outside app bundles`() throws {
|
||||||
let suite = try #require(UserDefaults(suiteName: launchdLabel))
|
let suite = try #require(UserDefaults(suiteName: launchdLabel))
|
||||||
let key = "openclaw.nixMode"
|
let key = "openclaw.nixMode"
|
||||||
let prev = suite.object(forKey: key)
|
let prev = suite.object(forKey: key)
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct NodeManagerPathsTests {
|
struct NodeManagerPathsTests {
|
||||||
@Test func fnmNodeBinsPreferNewestInstalledVersion() throws {
|
@Test func `fnm node bins prefer newest installed version`() throws {
|
||||||
let home = try makeTempDirForTests()
|
let home = try makeTempDirForTests()
|
||||||
|
|
||||||
let v20Bin = home
|
let v20Bin = home
|
||||||
@@ -18,7 +18,7 @@ import Testing
|
|||||||
#expect(bins.contains(v20Bin.deletingLastPathComponent().path))
|
#expect(bins.contains(v20Bin.deletingLastPathComponent().path))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func ignoresEntriesWithoutNodeExecutable() throws {
|
@Test func `ignores entries without node executable`() throws {
|
||||||
let home = try makeTempDirForTests()
|
let home = try makeTempDirForTests()
|
||||||
let missingNodeBin = home
|
let missingNodeBin = home
|
||||||
.appendingPathComponent(".local/share/fnm/node-versions/v99.0.0/installation/bin")
|
.appendingPathComponent(".local/share/fnm/node-versions/v99.0.0/installation/bin")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Testing
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct NodePairingApprovalPrompterTests {
|
struct NodePairingApprovalPrompterTests {
|
||||||
@Test func nodePairingApprovalPrompterExercises() async {
|
@Test func `node pairing approval prompter exercises`() async {
|
||||||
await NodePairingApprovalPrompter.exerciseForTesting()
|
await NodePairingApprovalPrompter.exerciseForTesting()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite struct NodePairingReconcilePolicyTests {
|
struct NodePairingReconcilePolicyTests {
|
||||||
@Test func policyPollsOnlyWhenActive() {
|
@Test func `policy polls only when active`() {
|
||||||
#expect(NodePairingReconcilePolicy.shouldPoll(pendingCount: 0, isPresenting: false) == false)
|
#expect(NodePairingReconcilePolicy.shouldPoll(pendingCount: 0, isPresenting: false) == false)
|
||||||
#expect(NodePairingReconcilePolicy.shouldPoll(pendingCount: 1, isPresenting: false))
|
#expect(NodePairingReconcilePolicy.shouldPoll(pendingCount: 1, isPresenting: false))
|
||||||
#expect(NodePairingReconcilePolicy.shouldPoll(pendingCount: 0, isPresenting: true))
|
#expect(NodePairingReconcilePolicy.shouldPoll(pendingCount: 0, isPresenting: true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func policyUsesSlowSafetyInterval() {
|
@Test func `policy uses slow safety interval`() {
|
||||||
#expect(NodePairingReconcilePolicy.activeIntervalMs >= 10000)
|
#expect(NodePairingReconcilePolicy.activeIntervalMs >= 10000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Testing
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct OnboardingCoverageTests {
|
struct OnboardingCoverageTests {
|
||||||
@Test func exerciseOnboardingPages() {
|
@Test func `exercise onboarding pages`() {
|
||||||
OnboardingView.exerciseForTesting()
|
OnboardingView.exerciseForTesting()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Testing
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct OnboardingViewSmokeTests {
|
struct OnboardingViewSmokeTests {
|
||||||
@Test func onboardingViewBuildsBody() {
|
@Test func `onboarding view builds body`() {
|
||||||
let state = AppState(preview: true)
|
let state = AppState(preview: true)
|
||||||
let view = OnboardingView(
|
let view = OnboardingView(
|
||||||
state: state,
|
state: state,
|
||||||
@@ -16,18 +16,18 @@ struct OnboardingViewSmokeTests {
|
|||||||
_ = view.body
|
_ = view.body
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func pageOrderOmitsWorkspaceAndIdentitySteps() {
|
@Test func `page order omits workspace and identity steps`() {
|
||||||
let order = OnboardingView.pageOrder(for: .local, showOnboardingChat: false)
|
let order = OnboardingView.pageOrder(for: .local, showOnboardingChat: false)
|
||||||
#expect(!order.contains(7))
|
#expect(!order.contains(7))
|
||||||
#expect(order.contains(3))
|
#expect(order.contains(3))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func pageOrderOmitsOnboardingChatWhenIdentityKnown() {
|
@Test func `page order omits onboarding chat when identity known`() {
|
||||||
let order = OnboardingView.pageOrder(for: .local, showOnboardingChat: false)
|
let order = OnboardingView.pageOrder(for: .local, showOnboardingChat: false)
|
||||||
#expect(!order.contains(8))
|
#expect(!order.contains(8))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func selectRemoteGatewayClearsStaleSshTargetWhenEndpointUnresolved() async {
|
@Test func `select remote gateway clears stale ssh target when endpoint unresolved`() async {
|
||||||
let override = FileManager().temporaryDirectory
|
let override = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("openclaw-config-\(UUID().uuidString)")
|
.appendingPathComponent("openclaw-config-\(UUID().uuidString)")
|
||||||
.appendingPathComponent("openclaw.json")
|
.appendingPathComponent("openclaw.json")
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ private typealias ProtoAnyCodable = OpenClawProtocol.AnyCodable
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct OnboardingWizardStepViewTests {
|
struct OnboardingWizardStepViewTests {
|
||||||
@Test func noteStepBuilds() {
|
@Test func `note step builds`() {
|
||||||
let step = WizardStep(
|
let step = WizardStep(
|
||||||
id: "step-1",
|
id: "step-1",
|
||||||
type: ProtoAnyCodable("note"),
|
type: ProtoAnyCodable("note"),
|
||||||
@@ -23,7 +23,7 @@ struct OnboardingWizardStepViewTests {
|
|||||||
_ = view.body
|
_ = view.body
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func selectStepBuilds() {
|
@Test func `select step builds`() {
|
||||||
let options: [[String: ProtoAnyCodable]] = [
|
let options: [[String: ProtoAnyCodable]] = [
|
||||||
["value": ProtoAnyCodable("local"), "label": ProtoAnyCodable("Local"), "hint": ProtoAnyCodable("This Mac")],
|
["value": ProtoAnyCodable("local"), "label": ProtoAnyCodable("Local"), "hint": ProtoAnyCodable("This Mac")],
|
||||||
["value": ProtoAnyCodable("remote"), "label": ProtoAnyCodable("Remote")],
|
["value": ProtoAnyCodable("remote"), "label": ProtoAnyCodable("Remote")],
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ struct OpenClawConfigFileTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func configPathRespectsEnvOverride() async {
|
func `config path respects env override`() async {
|
||||||
let override = makeConfigOverridePath()
|
let override = self.makeConfigOverridePath()
|
||||||
|
|
||||||
await TestIsolation.withEnvValues(["OPENCLAW_CONFIG_PATH": override]) {
|
await TestIsolation.withEnvValues(["OPENCLAW_CONFIG_PATH": override]) {
|
||||||
#expect(OpenClawConfigFile.url().path == override)
|
#expect(OpenClawConfigFile.url().path == override)
|
||||||
@@ -22,8 +22,8 @@ struct OpenClawConfigFileTests {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Test
|
@Test
|
||||||
func remoteGatewayPortParsesAndMatchesHost() async {
|
func `remote gateway port parses and matches host`() async {
|
||||||
let override = makeConfigOverridePath()
|
let override = self.makeConfigOverridePath()
|
||||||
|
|
||||||
await TestIsolation.withEnvValues(["OPENCLAW_CONFIG_PATH": override]) {
|
await TestIsolation.withEnvValues(["OPENCLAW_CONFIG_PATH": override]) {
|
||||||
OpenClawConfigFile.saveDict([
|
OpenClawConfigFile.saveDict([
|
||||||
@@ -42,8 +42,8 @@ struct OpenClawConfigFileTests {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Test
|
@Test
|
||||||
func setRemoteGatewayUrlPreservesScheme() async {
|
func `set remote gateway url preserves scheme`() async {
|
||||||
let override = makeConfigOverridePath()
|
let override = self.makeConfigOverridePath()
|
||||||
|
|
||||||
await TestIsolation.withEnvValues(["OPENCLAW_CONFIG_PATH": override]) {
|
await TestIsolation.withEnvValues(["OPENCLAW_CONFIG_PATH": override]) {
|
||||||
OpenClawConfigFile.saveDict([
|
OpenClawConfigFile.saveDict([
|
||||||
@@ -62,8 +62,8 @@ struct OpenClawConfigFileTests {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Test
|
@Test
|
||||||
func clearRemoteGatewayUrlRemovesOnlyUrlField() async {
|
func `clear remote gateway url removes only url field`() async {
|
||||||
let override = makeConfigOverridePath()
|
let override = self.makeConfigOverridePath()
|
||||||
|
|
||||||
await TestIsolation.withEnvValues(["OPENCLAW_CONFIG_PATH": override]) {
|
await TestIsolation.withEnvValues(["OPENCLAW_CONFIG_PATH": override]) {
|
||||||
OpenClawConfigFile.saveDict([
|
OpenClawConfigFile.saveDict([
|
||||||
@@ -83,7 +83,7 @@ struct OpenClawConfigFileTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func stateDirOverrideSetsConfigPath() async {
|
func `state dir override sets config path`() async {
|
||||||
let dir = FileManager().temporaryDirectory
|
let dir = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("openclaw-state-\(UUID().uuidString)", isDirectory: true)
|
.appendingPathComponent("openclaw-state-\(UUID().uuidString)", isDirectory: true)
|
||||||
.path
|
.path
|
||||||
@@ -99,7 +99,7 @@ struct OpenClawConfigFileTests {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Test
|
@Test
|
||||||
func saveDictAppendsConfigAuditLog() async throws {
|
func `save dict appends config audit log`() async throws {
|
||||||
let stateDir = FileManager().temporaryDirectory
|
let stateDir = FileManager().temporaryDirectory
|
||||||
.appendingPathComponent("openclaw-state-\(UUID().uuidString)", isDirectory: true)
|
.appendingPathComponent("openclaw-state-\(UUID().uuidString)", isDirectory: true)
|
||||||
let configPath = stateDir.appendingPathComponent("openclaw.json")
|
let configPath = stateDir.appendingPathComponent("openclaw.json")
|
||||||
|
|||||||
@@ -2,16 +2,15 @@ import CoreLocation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import OpenClaw
|
@testable import OpenClaw
|
||||||
|
|
||||||
@Suite("PermissionManager Location")
|
|
||||||
struct PermissionManagerLocationTests {
|
struct PermissionManagerLocationTests {
|
||||||
@Test("authorizedAlways counts for both modes")
|
@Test
|
||||||
func authorizedAlwaysCountsForBothModes() {
|
func `authorizedAlways counts for both modes`() {
|
||||||
#expect(PermissionManager.isLocationAuthorized(status: .authorizedAlways, requireAlways: false))
|
#expect(PermissionManager.isLocationAuthorized(status: .authorizedAlways, requireAlways: false))
|
||||||
#expect(PermissionManager.isLocationAuthorized(status: .authorizedAlways, requireAlways: true))
|
#expect(PermissionManager.isLocationAuthorized(status: .authorizedAlways, requireAlways: true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("other statuses not authorized")
|
@Test
|
||||||
func otherStatusesNotAuthorized() {
|
func `other statuses not authorized`() {
|
||||||
#expect(!PermissionManager.isLocationAuthorized(status: .notDetermined, requireAlways: false))
|
#expect(!PermissionManager.isLocationAuthorized(status: .notDetermined, requireAlways: false))
|
||||||
#expect(!PermissionManager.isLocationAuthorized(status: .denied, requireAlways: false))
|
#expect(!PermissionManager.isLocationAuthorized(status: .denied, requireAlways: false))
|
||||||
#expect(!PermissionManager.isLocationAuthorized(status: .restricted, requireAlways: false))
|
#expect(!PermissionManager.isLocationAuthorized(status: .restricted, requireAlways: false))
|
||||||
|
|||||||
@@ -6,31 +6,31 @@ import Testing
|
|||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@MainActor
|
@MainActor
|
||||||
struct PermissionManagerTests {
|
struct PermissionManagerTests {
|
||||||
@Test func voiceWakePermissionHelpersMatchStatus() async {
|
@Test func `voice wake permission helpers match status`() async {
|
||||||
let direct = PermissionManager.voiceWakePermissionsGranted()
|
let direct = PermissionManager.voiceWakePermissionsGranted()
|
||||||
let ensured = await PermissionManager.ensureVoiceWakePermissions(interactive: false)
|
let ensured = await PermissionManager.ensureVoiceWakePermissions(interactive: false)
|
||||||
#expect(ensured == direct)
|
#expect(ensured == direct)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func statusCanQueryNonInteractiveCaps() async {
|
@Test func `status can query non interactive caps`() async {
|
||||||
let caps: [Capability] = [.microphone, .speechRecognition, .screenRecording]
|
let caps: [Capability] = [.microphone, .speechRecognition, .screenRecording]
|
||||||
let status = await PermissionManager.status(caps)
|
let status = await PermissionManager.status(caps)
|
||||||
#expect(status.keys.count == caps.count)
|
#expect(status.keys.count == caps.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func ensureNonInteractiveDoesNotThrow() async {
|
@Test func `ensure non interactive does not throw`() async {
|
||||||
let caps: [Capability] = [.microphone, .speechRecognition, .screenRecording]
|
let caps: [Capability] = [.microphone, .speechRecognition, .screenRecording]
|
||||||
let ensured = await PermissionManager.ensure(caps, interactive: false)
|
let ensured = await PermissionManager.ensure(caps, interactive: false)
|
||||||
#expect(ensured.keys.count == caps.count)
|
#expect(ensured.keys.count == caps.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func locationStatusMatchesAuthorizationAlways() async {
|
@Test func `location status matches authorization always`() async {
|
||||||
let status = CLLocationManager().authorizationStatus
|
let status = CLLocationManager().authorizationStatus
|
||||||
let results = await PermissionManager.status([.location])
|
let results = await PermissionManager.status([.location])
|
||||||
#expect(results[.location] == (status == .authorizedAlways))
|
#expect(results[.location] == (status == .authorizedAlways))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func ensureLocationNonInteractiveMatchesAuthorizationAlways() async {
|
@Test func `ensure location non interactive matches authorization always`() async {
|
||||||
let status = CLLocationManager().authorizationStatus
|
let status = CLLocationManager().authorizationStatus
|
||||||
let ensured = await PermissionManager.ensure([.location], interactive: false)
|
let ensured = await PermissionManager.ensure([.location], interactive: false)
|
||||||
#expect(ensured[.location] == (status == .authorizedAlways))
|
#expect(ensured[.location] == (status == .authorizedAlways))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Testing
|
import Testing
|
||||||
|
|
||||||
@Suite struct PlaceholderTests {
|
struct PlaceholderTests {
|
||||||
@Test func placeholder() {
|
@Test func placeholder() {
|
||||||
#expect(true)
|
#expect(true)
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user