mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-17 08:02:12 -06:00
8862cc46b3
* fix(ios): harden share relay persistence * fix(ios): make relay migration transactional * fix(ios): make relay migration host-owned * fix(ios): reject legacy auth in share extension * fix(ios): reject failed relay credential migration * style(ios): fix relay settings indentation
136 lines
4.7 KiB
Swift
136 lines
4.7 KiB
Swift
import Foundation
|
|
import Security
|
|
import Testing
|
|
@testable import OpenClawKit
|
|
|
|
struct GenericPasswordKeychainStoreTests {
|
|
@Test func `existing item is replaced with update only`() {
|
|
var updateCalls = 0
|
|
var addCalls = 0
|
|
|
|
let result = GenericPasswordKeychainStore.saveDataResult(
|
|
Data("replacement".utf8),
|
|
service: "test-service",
|
|
account: "test-account",
|
|
accessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
|
|
updateItem: { _, _ in
|
|
updateCalls += 1
|
|
return errSecSuccess
|
|
},
|
|
addItem: { _ in
|
|
addCalls += 1
|
|
return errSecSuccess
|
|
})
|
|
|
|
guard case .success = result else {
|
|
Issue.record("expected successful update")
|
|
return
|
|
}
|
|
#expect(updateCalls == 1)
|
|
#expect(addCalls == 0)
|
|
}
|
|
|
|
@Test func `missing item is added after update miss`() {
|
|
var addCalls = 0
|
|
|
|
let result = GenericPasswordKeychainStore.saveDataResult(
|
|
Data("new-value".utf8),
|
|
service: "test-service",
|
|
account: "test-account",
|
|
accessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
|
|
updateItem: { _, _ in errSecItemNotFound },
|
|
addItem: { _ in
|
|
addCalls += 1
|
|
return errSecSuccess
|
|
})
|
|
|
|
guard case .success = result else {
|
|
Issue.record("expected successful add")
|
|
return
|
|
}
|
|
#expect(addCalls == 1)
|
|
}
|
|
|
|
@Test func `shared access group scopes keychain mutation queries`() {
|
|
let accessGroup = "group.ai.openclawfoundation.app.shared"
|
|
var updateQuery: [String: Any] = [:]
|
|
var addQuery: [String: Any] = [:]
|
|
|
|
let result = GenericPasswordKeychainStore.saveDataResult(
|
|
Data("shared-value".utf8),
|
|
service: "test-service",
|
|
account: "test-account",
|
|
accessGroup: accessGroup,
|
|
accessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
|
|
updateItem: { query, _ in
|
|
updateQuery = query as? [String: Any] ?? [:]
|
|
return errSecItemNotFound
|
|
},
|
|
addItem: { query in
|
|
addQuery = query as? [String: Any] ?? [:]
|
|
return errSecSuccess
|
|
})
|
|
|
|
guard case .success = result else {
|
|
Issue.record("expected shared keychain add to succeed")
|
|
return
|
|
}
|
|
#expect(updateQuery[kSecAttrAccessGroup as String] as? String == accessGroup)
|
|
#expect(addQuery[kSecAttrAccessGroup as String] as? String == accessGroup)
|
|
}
|
|
|
|
@Test func `add race retries atomic update`() {
|
|
var updateCalls = 0
|
|
|
|
let result = GenericPasswordKeychainStore.saveDataResult(
|
|
Data("raced-value".utf8),
|
|
service: "test-service",
|
|
account: "test-account",
|
|
accessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
|
|
updateItem: { _, _ in
|
|
updateCalls += 1
|
|
return updateCalls == 1 ? errSecItemNotFound : errSecSuccess
|
|
},
|
|
addItem: { _ in errSecDuplicateItem })
|
|
|
|
guard case .success = result else {
|
|
Issue.record("expected retry update to succeed")
|
|
return
|
|
}
|
|
#expect(updateCalls == 2)
|
|
}
|
|
|
|
@Test func `update failure preserves exact operation and status`() {
|
|
let result = GenericPasswordKeychainStore.saveDataResult(
|
|
Data("replacement".utf8),
|
|
service: "test-service",
|
|
account: "test-account",
|
|
accessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
|
|
updateItem: { _, _ in errSecInteractionNotAllowed },
|
|
addItem: { _ in
|
|
Issue.record("add must not run after a non-missing update failure")
|
|
return errSecSuccess
|
|
})
|
|
|
|
guard case let .failure(error) = result else {
|
|
Issue.record("expected update failure")
|
|
return
|
|
}
|
|
#expect(error == .init(operation: .update, status: errSecInteractionNotAllowed))
|
|
#expect(error.localizedDescription.contains("OSStatus \(errSecInteractionNotAllowed)"))
|
|
}
|
|
|
|
@Test func `delete failure preserves exact status`() {
|
|
let result = GenericPasswordKeychainStore.deleteResult(
|
|
service: "test-service",
|
|
account: "test-account",
|
|
deleteItem: { _ in errSecAuthFailed })
|
|
|
|
guard case let .failure(error) = result else {
|
|
Issue.record("expected delete failure")
|
|
return
|
|
}
|
|
#expect(error == .init(operation: .delete, status: errSecAuthFailed))
|
|
}
|
|
}
|