Files
openclaw/apps/ios/Tests/WatchApprovalTransportSourceGuardTests.swift
2026-07-11 23:19:37 -07:00

382 lines
23 KiB
Swift

import Foundation
import Testing
@testable import OpenClaw
struct WatchApprovalTransportSourceGuardTests {
@Test func `watch approval loading and screenshot proof are visible`() throws {
let appSource = try Self.readWatchSource("OpenClawWatchApp.swift")
let viewSource = try Self.readWatchSource("WatchInboxView.swift")
let approvalFace = try Self.extract(
viewSource,
from: "private var approvalsFace: some View",
to: "private var connectionFace: some View")
#expect(appSource.contains("--openclaw-watch-approval-screenshot-mode"))
#expect(appSource.contains("includeApproval: WatchScreenshotMode.approvals"))
#expect(viewSource.contains("selectedFace = WatchScreenshotMode.approvals ? 2 : 0"))
#expect(appSource.contains("id: \"watch-screenshot-approval\""))
#expect(appSource.contains("pendingApprovalCount: approvals.count"))
#expect(approvalFace.contains("self.store.isExecApprovalReviewLoading"))
#expect(approvalFace.contains("title: \"Loading approval\""))
#expect(approvalFace.contains(
"self.approvalCount > 0 || self.store.shouldShowExecApprovalReviewStatus"))
#expect(approvalFace.contains("title: \"Approval not loaded\""))
#expect(approvalFace.contains("Approval details have not loaded"))
#expect(approvalFace.contains("WatchSecondaryButton(title: \"Review again\")"))
}
@Test func `watch distinguishes unsent approval from uncertain delivery`() throws {
let source = try Self.readWatchSource("OpenClawWatchApp.swift")
let storeSource = try Self.readWatchSource("WatchInboxStore.swift")
let resolveFlow = try Self.extract(
source,
from: "guard let attemptID = self.inboxStore.beginExecApprovalDecision(",
to: "onRefreshExecApprovalReview:")
let admission = try #require(
resolveFlow.range(of: "guard let attemptID = self.inboxStore.beginExecApprovalDecision("))
let send = try #require(
resolveFlow.range(of: "let result = await receiver.sendExecApprovalResolve("))
let completion = try #require(
resolveFlow.range(of: "self.inboxStore.completeExecApprovalDecision("))
#expect(admission.lowerBound < send.lowerBound)
#expect(send.lowerBound < completion.lowerBound)
#expect(storeSource.contains("!self.execApprovals[index].isResolving"))
#expect(storeSource.contains("approval.allowedDecisions.contains(decision)"))
#expect(storeSource.contains(
"WatchOpaqueUTF8Key(activeResolutionAttemptID) == WatchOpaqueUTF8Key(attemptID)"))
#expect(storeSource.contains("pendingDecision == decision"))
#expect(storeSource.contains("activeResolutionAttemptID = nil"))
#expect(resolveFlow.contains("attemptID: attemptID"))
let receiverSource = try Self.readWatchSource("WatchConnectivityReceiver.swift")
#expect(receiverSource.contains("enum WatchReplyDeliveryState"))
#expect(receiverSource.contains("delivery: .delivered"))
#expect(receiverSource.contains("delivery: .queued"))
#expect(receiverSource.contains("delivery: .notSent"))
#expect(receiverSource.contains("var requiresCanonicalReadback: Bool"))
#expect(receiverSource.contains("requiresCanonicalReadback = true"))
#expect(receiverSource.contains("requiresCanonicalReadback: requiresCanonicalReadback"))
#expect(receiverSource.contains("replyId: attemptID"))
#expect(resolveFlow.contains("if result.requiresCanonicalReadback"))
}
@Test func `forced watch refresh waits for its exact request and owner snapshot`() throws {
let appSource = try Self.readWatchSource("OpenClawWatchApp.swift")
let receiverSource = try Self.readWatchSource("WatchConnectivityReceiver.swift")
let storeSource = try Self.readWatchSource("WatchInboxStore.swift")
let refresh = try Self.extract(
appSource,
from: "private func refreshExecApprovalReview(force: Bool = false)",
to: "}\n}\n\n@MainActor")
let appSnapshotRequestEncoder = try Self.extract(
receiverSource,
from: "private static func encodeAppSnapshotRequestPayload(",
to: "private static func encodeAppCommandPayload(")
let approvalSnapshotRequestEncoder = try Self.extract(
receiverSource,
from: "private static func encodeSnapshotRequestPayload(",
to: "private static func encodeExecApprovalResolvePayload(")
#expect(refresh.contains("var requestTokens: [WatchExecApprovalSnapshotRequestToken] = []"))
#expect(refresh.contains("consumeCurrentOwnerAcknowledgment"))
#expect(refresh.contains("token.matchesGatewayStableID(currentGatewayStableID)"))
#expect(refresh.contains("discardExecApprovalSnapshotAcknowledgments("))
#expect(refresh.contains("requestTokens.append(token)"))
#expect(refresh.contains("receiver.consumeExecApprovalSnapshotAcknowledgment(for: token)"))
let checkBeforeSend = try #require(refresh.range(of: "let receivedBeforeRequest"))
let send = try #require(refresh.range(of: "await receiver.requestExecApprovalSnapshot("))
#expect(checkBeforeSend.lowerBound < send.lowerBound)
#expect(!refresh.contains("execApprovalSnapshotRevision"))
#expect(refresh.contains("let reviewAlreadyAvailable = !force"))
#expect(refresh.contains("!self.inboxStore.execApprovals.contains(where: \\.isResolving)"))
#expect(receiverSource.contains("struct WatchExecApprovalSnapshotRequestToken: Hashable"))
#expect(receiverSource.contains("self.requestKey = WatchOpaqueUTF8Key(requestId)"))
#expect(receiverSource.contains("self.gatewayKey = WatchOpaqueUTF8Key(gatewayStableID)"))
#expect(receiverSource.contains("func matchesGatewayStableID("))
#expect(approvalSnapshotRequestEncoder.contains(
"WatchGatewayID.exact(request.gatewayStableID)"))
#expect(approvalSnapshotRequestEncoder.contains("\"heldApprovals\": request.heldApprovals.map"))
#expect(approvalSnapshotRequestEncoder.contains("\"activeResolutionAttemptId\""))
#expect(storeSource.contains("func execApprovalSnapshotRequestItems("))
#expect(storeSource.contains(
"WatchGatewayID.key(record.approval.gatewayStableID) == gatewayKey"))
#expect(storeSource.contains("self.hasCompletedExecApprovalSnapshotRefreshInSession = false"))
#expect(refresh.contains(
"heldApprovals: self.inboxStore.execApprovalSnapshotRequestItems("))
#expect(receiverSource.components(
separatedBy: "discardExecApprovalSnapshotAcknowledgments(").count >= 4)
#expect(!appSnapshotRequestEncoder.contains("gatewayStableID"))
#expect(receiverSource.contains(
"WatchGatewayID.exact(payload[\"requestGatewayStableID\"] as? String)"))
#expect(receiverSource.contains("recordAcceptedExecApprovalSnapshot"))
#expect(receiverSource.contains(
"WatchGatewayID.key(snapshot.gatewayStableID) == WatchGatewayID.key(token.gatewayStableID)"))
}
@Test func `watch applies retry reset only to its exact active attempt`() throws {
let storeSource = try Self.readWatchSource("WatchInboxStore.swift")
let promptConsume = try Self.extract(
storeSource,
from: "func consume(\n execApprovalPrompt",
to: "func consume(\n execApprovalSnapshot")
let upsert = try Self.extract(
storeSource,
from: "private func upsertExecApproval(",
to: "private static func snapshotCanReplace(")
let guardedUpsert = try #require(promptConsume.range(of: "guard self.upsertExecApproval("))
let notificationOwnerCheck = try #require(promptConsume.range(of: "guard let approvalOwnerKey"))
#expect(guardedUpsert.lowerBound < notificationOwnerCheck.lowerBound)
let upsertAdmission = promptConsume[guardedUpsert.lowerBound..<notificationOwnerCheck.lowerBound]
#expect(upsertAdmission.contains("else { return }"))
#expect(upsert.contains("resetResolutionAttemptID: String? = nil) -> Bool"))
#expect(upsert.components(separatedBy: "return false").count >= 3)
#expect(upsert.contains("return true"))
#expect(promptConsume.contains(
"resetResolutionAttemptID: message.resetResolutionAttemptId"))
#expect(upsert.contains("let activeResolutionAttemptID ="))
#expect(upsert.contains(
"WatchOpaqueUTF8Key(resetResolutionAttemptID) == WatchOpaqueUTF8Key(activeResolutionAttemptID)"))
#expect(upsert.contains("activeResolutionAttemptID = resetResolvingState ? nil"))
#expect(!storeSource.contains("appliedResetDeliveryIDs"))
#expect(!storeSource.contains("deliveryId"))
#expect(!storeSource.contains("resetResolvingState: Bool?"))
}
@Test func `watch rejects partial or missing approval snapshot arrays`() throws {
let receiverSource = try Self.readWatchSource("WatchConnectivityReceiver.swift")
let parser = try Self.extract(
receiverSource,
from: "private static func parseExecApprovalSnapshotPayload(",
to: "private static func parseAppSnapshotPayload(")
#expect(parser.contains("guard let rawApprovals = payload[\"approvals\"] as? [Any]"))
#expect(parser.contains("guard let approval = Self.parseExecApprovalItem(item) else { return nil }"))
#expect(!parser.contains("compactMap"))
#expect(!parser.contains("?? []"))
}
@Test func `watch reuses exact compound identifier policy`() throws {
let receiverSource = try Self.readWatchSource("WatchConnectivityReceiver.swift")
let storeSource = try Self.readWatchSource("WatchInboxStore.swift")
let messagesSource = try Self.readWatchSource("WatchInboxMessages.swift")
let viewSource = try Self.readWatchSource("WatchInboxView.swift")
let parser = try Self.extract(
receiverSource,
from: "private static func parseExecApprovalItem(",
to: "private static func parseExecApprovalPromptPayload(")
let ownerKey = try Self.extract(
storeSource,
from: "private static func execApprovalOwnerKey(",
to: "private func isExecApprovalTerminal(")
let snapshotConsume = try Self.extract(
storeSource,
from: "func consume(\n execApprovalSnapshot",
to: "func consume(appSnapshot")
let restore = try Self.extract(
storeSource,
from: "private func restorePersistedState()",
to: "private func persistState()")
let prefixed = "\u{001C}approval"
#expect(ExecApprovalIdentifier.exact(prefixed) == prefixed)
#expect(ExecApprovalIdentifier.exact(" approval ") == " approval ")
#expect(ExecApprovalIdentifier.exact(".") == nil)
#expect(GatewayStableIdentifier.exact(" gateway ") == " gateway ")
#expect(messagesSource.contains("typealias WatchApprovalID = ExecApprovalIdentifier"))
#expect(messagesSource.contains("typealias WatchGatewayID = GatewayStableIdentifier"))
#expect(parser.contains("WatchApprovalID.exact(payload[\"id\"] as? String)"))
#expect(!parser.contains("id = (payload[\"id\"] as? String)?.trimmingCharacters"))
#expect(ownerKey.contains("WatchApprovalID.key(approvalId)"))
#expect(!ownerKey.contains("approvalId.trimmingCharacters"))
#expect(snapshotConsume.contains("WatchApprovalID.exact(approval.id) != nil"))
#expect(snapshotConsume.contains("let hasCanonicalRequestCorrelation ="))
#expect(snapshotConsume.contains("guard hasCanonicalRequestCorrelation else { return true }"))
#expect(restore.contains("WatchApprovalID.exact(record.approvalID) != nil"))
let composedID = "approval-\u{00E9}"
let decomposedID = "approval-e\u{0301}"
let composedKey = ExactOpaqueIdentifierKey(composedID)
let decomposedKey = ExactOpaqueIdentifierKey(decomposedID)
#expect(composedID == decomposedID)
#expect(composedKey != decomposedKey)
var pending = [composedKey: composedID, decomposedKey: decomposedID]
pending.removeValue(forKey: composedKey)
let remainingID = try #require(pending[decomposedKey])
#expect(Array(remainingID.utf8) == Array(decomposedID.utf8))
#expect(messagesSource.contains("typealias WatchOpaqueUTF8Key = ExactOpaqueIdentifierKey"))
#expect(messagesSource.contains("var id: WatchExecApprovalIdentityKey"))
#expect(messagesSource.contains("var approvalID: WatchApprovalID.Key"))
#expect(messagesSource.contains("var gatewayID: WatchGatewayID.Key"))
#expect(storeSource.contains("WatchApprovalID.key(tombstone.approvalId) == key.approvalID"))
#expect(storeSource.contains("WatchGatewayID.key(tombstone.gatewayStableID) == key.gatewayID"))
#expect(storeSource.contains("approvalKey.notificationComponent"))
#expect(storeSource.contains("gatewayKey.notificationComponent"))
#expect(!storeSource.contains("record.id == approval.id"))
#expect(!messagesSource.contains("record.id == approval.id"))
#expect(!storeSource.contains("tombstone.approvalId == key.approvalId"))
#expect(receiverSource.contains("WatchGatewayID.exact(payload[\"gatewayStableID\"] as? String)"))
#expect(!receiverSource.contains("gatewayStableID?.trimmingCharacters"))
#expect(viewSource.contains("record.approvalID"))
#expect(viewSource.contains("$0.id == self.record.id"))
}
@Test func `watch requires full accessible command review before allow`() throws {
let viewSource = try Self.readWatchSource("WatchInboxView.swift")
let typographySource = try Self.readWatchSource("WatchClawTypography.swift")
let approvalFace = try Self.extract(
viewSource,
from: "private var approvalsFace: some View",
to: "private var connectionFace: some View")
let commandReview = try Self.extract(
viewSource,
from: "private struct WatchApprovalCommandReview: View",
to: "private enum WatchExecApprovalDisplay")
let approvalDetail = try Self.extract(
viewSource,
from: "private struct WatchExecApprovalDetailView: View",
to: "private struct WatchDetailScroll")
let decisionButton = try Self.extract(
viewSource,
from: "private struct WatchDecisionButton: View",
to: "private struct WatchTinyStatus")
let detailScrollStart = try #require(
viewSource.range(of: "private struct WatchDetailScroll<Content: View>: View"))
let detailScroll = String(viewSource[detailScrollStart.lowerBound...])
#expect(approvalFace.contains("WatchSecondaryLabel(title: \"Review Command\")"))
#expect(approvalFace.contains(
".accessibilityHint(\"Opens the full command before decisions are available\")"))
#expect(!approvalFace.contains("WatchDecisionButton("))
#expect(commandReview.contains("Text(verbatim: self.commandText)"))
#expect(commandReview.contains(".font(WatchClawType.command)"))
#expect(commandReview.contains(".fixedSize(horizontal: false, vertical: true)"))
#expect(!commandReview.contains(".lineLimit("))
#expect(commandReview.contains(".accessibilityLabel(\"Command to review\")"))
#expect(commandReview.contains(".accessibilityValue(self.commandText)"))
#expect(typographySource.contains(
".custom(\"JetBrainsMono-Regular\", size: 11, relativeTo: .body)"))
#expect(approvalDetail.contains("WatchDetailScroll(title: \"Review Command\")"))
#expect(approvalDetail.contains("WatchApprovalCommandReview(commandText: self.commandText)"))
#expect(approvalDetail.contains("VStack(spacing: 8)"))
#expect(approvalDetail.contains("WatchDecisionButton(title: \"Allow Once\""))
#expect(approvalDetail.contains("WatchDecisionButton(title: \"Deny\""))
#expect(!approvalDetail.contains("WatchDecisionButton(title: \"Approve\""))
let fullCommand = try #require(
approvalDetail.range(of: "WatchApprovalCommandReview(commandText: self.commandText)"))
let allowAction = try #require(
approvalDetail.range(of: "WatchDecisionButton(title: \"Allow Once\""))
let denyAction = try #require(
approvalDetail.range(of: "WatchDecisionButton(title: \"Deny\""))
#expect(fullCommand.lowerBound < allowAction.lowerBound)
#expect(fullCommand.lowerBound < denyAction.lowerBound)
#expect(decisionButton.contains(".fixedSize(horizontal: false, vertical: true)"))
#expect(decisionButton.contains(".accessibilityLabel(self.title)"))
#expect(!decisionButton.contains(".lineLimit("))
#expect(detailScroll.contains("ScrollView {"))
#expect(detailScroll.contains("self.content"))
}
@Test func `watch notification identity frames dotted components`() throws {
let storeSource = try Self.readWatchSource("WatchInboxStore.swift")
let messagesSource = try Self.readWatchSource("WatchInboxMessages.swift")
let identifierSource = try Self.readIOSServiceSource("ExactOpaqueIdentifier.swift")
let promptConsume = try Self.extract(
storeSource,
from: "func consume(\n execApprovalPrompt",
to: "func consume(\n execApprovalSnapshot")
let rawLeft = "a.b" + "." + "c"
let rawRight = "a" + "." + "b.c"
#expect(rawLeft == rawRight)
let framedLeft = ExactOpaqueIdentifierKey("a.b").notificationComponent + "."
+ ExactOpaqueIdentifierKey("c").notificationComponent
let framedRight = ExactOpaqueIdentifierKey("a").notificationComponent + "."
+ ExactOpaqueIdentifierKey("b.c").notificationComponent
#expect(framedLeft != framedRight)
#expect(messagesSource.contains("typealias WatchOpaqueUTF8Key = ExactOpaqueIdentifierKey"))
#expect(identifierSource.contains("0x2D, 0x5F, 0x7E"))
#expect(!identifierSource.contains("0x2D, 0x2E, 0x5F, 0x7E"))
#expect(!storeSource.contains("0x2D, 0x2E, 0x5F, 0x7E"))
#expect(storeSource.contains("gatewayKey.notificationComponent).\\(approvalKey.notificationComponent"))
#expect(storeSource.contains("legacyExecApprovalNotificationIdentifier"))
#expect(storeSource.contains("hasLiveLegacyNotificationCollision"))
#expect(storeSource.contains("recordKey != excludedKey"))
let legacyCleanup = try #require(
promptConsume.range(of: "if let legacyNotificationIdentifier"))
let notificationSchedule = try #require(
promptConsume.range(of: "await self.postLocalNotification("))
#expect(legacyCleanup.lowerBound < notificationSchedule.lowerBound)
#expect(promptConsume.contains("!self.hasLiveLegacyNotificationCollision("))
#expect(promptConsume.contains(
"self.removeLocalNotifications(identifiers: [legacyNotificationIdentifier])"))
}
@Test func `watch snapshot acknowledgment advances only after store acceptance`() throws {
let storeSource = try Self.readWatchSource("WatchInboxStore.swift")
let receiverSource = try Self.readWatchSource("WatchConnectivityReceiver.swift")
let snapshotConsume = try Self.extract(
storeSource,
from: "func consume(\n execApprovalSnapshot",
to: "func consume(appSnapshot")
let replay = try Self.extract(
storeSource,
from: "func replayDeferredGatewayPayloads()",
to: "private func clearMessagePrompt()")
let correlation = try #require(snapshotConsume.range(of: "let hasCanonicalRequestCorrelation"))
let ownerValidation = try #require(snapshotConsume.range(of: "let allApprovalOwnersMatch"))
let existingRecords = try #require(snapshotConsume.range(of: "let existingRecords = self.execApprovals"))
#expect(snapshotConsume.contains("transport: String) -> Bool"))
#expect(snapshotConsume.components(separatedBy: "return false").count >= 4)
#expect(snapshotConsume.contains("self.persistState()\n return true"))
#expect(receiverSource.contains(
"if self.store.consume(execApprovalSnapshot: execApprovalSnapshot, transport: transport)"))
#expect(receiverSource.contains(
"if self.store.consume(execApprovalSnapshot: snapshot, transport: transport)"))
#expect(replay.contains(
"func replayDeferredGatewayPayloads() -> [WatchExecApprovalSnapshotMessage]"))
#expect(replay.contains(
"var appliedExecApprovalSnapshots: [WatchExecApprovalSnapshotMessage] = []"))
#expect(replay.contains("if self.consume(execApprovalSnapshot: message, transport: transport)"))
#expect(replay.contains("appliedExecApprovalSnapshots.append(message)"))
#expect(replay.contains("return appliedExecApprovalSnapshots"))
#expect(receiverSource.components(
separatedBy: "for snapshot in self.store.replayDeferredGatewayPayloads()").count == 3)
#expect(receiverSource.components(
separatedBy: "self.recordAcceptedExecApprovalSnapshot(snapshot)").count >= 3)
#expect(!receiverSource.contains("execApprovalSnapshotRevision"))
#expect(correlation.lowerBound < ownerValidation.lowerBound)
#expect(ownerValidation.lowerBound < existingRecords.lowerBound)
#expect(snapshotConsume.contains("guard allApprovalOwnersMatch else { return false }"))
#expect(snapshotConsume.contains(
"Self.gatewayIDsMatch(approval.gatewayStableID, snapshotGatewayID)"))
}
private static func readWatchSource(_ filename: String) throws -> String {
let url = URL(fileURLWithPath: #filePath)
.deletingLastPathComponent()
.deletingLastPathComponent()
.appendingPathComponent("WatchApp/Sources")
.appendingPathComponent(filename)
return try String(contentsOf: url, encoding: .utf8)
}
private static func readIOSServiceSource(_ filename: String) throws -> String {
let url = URL(fileURLWithPath: #filePath)
.deletingLastPathComponent()
.deletingLastPathComponent()
.appendingPathComponent("Sources/Services")
.appendingPathComponent(filename)
return try String(contentsOf: url, encoding: .utf8)
}
private static func extract(_ source: String, from start: String, to end: String) throws -> String {
let startRange = try #require(source.range(of: start))
let tail = source[startRange.lowerBound...]
let endRange = try #require(tail.range(of: end))
return String(tail[..<endRange.lowerBound])
}
}