Files
openclaw/apps/shared/OpenClawKit/Tests/OpenClawKitTests/DeviceAuthPayloadTests.swift
Peter Steinberger 54f45a950b feat(watchos): connect directly to Gateway as a node (#102893)
* feat(watchos): add direct gateway node

* docs: refresh watch node docs map

* chore: leave release notes to release workflow

* chore(ios): refresh native localization inventory

* fix(watchos): keep direct node policy bounded
2026-07-09 15:53:02 +01:00

92 lines
3.8 KiB
Swift

import CryptoKit
import Foundation
import Testing
@testable import OpenClawKit
@Suite("DeviceAuthPayload")
struct DeviceAuthPayloadTests {
@Test
func `builds Swift connect compatibility payload with v2 canonical fields`() {
let signedAtMs: Int64 = 1_800_000_000_000
let payload = GatewayDeviceAuthPayload.buildConnectCompatibilityPayload(
deviceId: "dev-1",
clientId: "openclaw-macos",
clientMode: "ui",
role: "operator",
scopes: ["operator.admin", "operator.read"],
signedAtMs: signedAtMs,
token: "tok-123",
nonce: "nonce-abc")
#expect(
payload
== "v2|dev-1|openclaw-macos|ui|operator|operator.admin,operator.read|1800000000000|tok-123|nonce-abc")
}
@Test
func `builds canonical v3 payload vector`() {
let signedAtMs: Int64 = 1_800_000_000_000
let payload = GatewayDeviceAuthPayload.buildV3(
deviceId: "dev-1",
clientId: "openclaw-macos",
clientMode: "ui",
role: "operator",
scopes: ["operator.admin", "operator.read"],
signedAtMs: signedAtMs,
token: "tok-123",
nonce: "nonce-abc",
platform: " IOS ",
deviceFamily: " iPhone ")
#expect(
payload
==
"v3|dev-1|openclaw-macos|ui|operator|operator.admin,operator.read|1800000000000|tok-123|nonce-abc|ios|iphone")
}
@Test
func `signed device dictionary preserves 64-bit timestamp`() throws {
let tempDir = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString, isDirectory: true)
let identityURL = tempDir.appendingPathComponent("device.json", isDirectory: false)
defer { try? FileManager.default.removeItem(at: tempDir) }
let identity = DeviceIdentityStore.loadOrCreate(fileURL: identityURL)
let signedAtMs: Int64 = 1_800_000_000_000
let payload = GatewayDeviceAuthPayload.buildV3(
deviceId: identity.deviceId,
clientId: "openclaw-watchos",
clientMode: "node",
role: "node",
scopes: [],
signedAtMs: signedAtMs,
token: "device-token",
nonce: "nonce-abc",
platform: "watchOS",
deviceFamily: "Apple Watch")
let device = try #require(GatewayDeviceAuthPayload.signedDeviceDictionary(
payload: payload,
identity: identity,
signedAtMs: signedAtMs,
nonce: "nonce-abc"))
let signature = try #require(device["signature"]?.value as? String)
let signatureBase64 = signature
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/")
let signaturePadding = String(repeating: "=", count: (4 - signatureBase64.count % 4) % 4)
let signatureData = try #require(Data(base64Encoded: signatureBase64 + signaturePadding))
let publicKeyData = try #require(Data(base64Encoded: identity.publicKey))
let publicKey = try Curve25519.Signing.PublicKey(rawRepresentation: publicKeyData)
let data = try JSONEncoder().encode(device)
let object = try #require(JSONSerialization.jsonObject(with: data) as? [String: Any])
#expect(publicKey.isValidSignature(signatureData, for: Data(payload.utf8)))
#expect((object["signedAt"] as? NSNumber)?.int64Value == signedAtMs)
}
@Test
func `normalizes metadata with ASCII-only lowercase`() {
#expect(GatewayDeviceAuthPayload.normalizeMetadataField(" İOS ") == "İos")
#expect(GatewayDeviceAuthPayload.normalizeMetadataField(" MAC ") == "mac")
#expect(GatewayDeviceAuthPayload.normalizeMetadataField(nil) == "")
}
}