mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 07:05:19 +00:00
* Isolate tests from the developer's real login keychain Every test run prompted for the login keychain password (repeatedly, since the xctest runner's code signature changes each build, so "Always Allow" can never stick) and silently deleted the developer's real Nostr identity via panic-mode tests. Two holes let tests reach the real keychain: - NostrIdentityBridge() defaulted to the real KeychainManager. Tests inject mocks, but app-side constructions with no injection point (LocationNotesManager's static bridge, GeohashPresenceService, BoardManager, AppRuntime) read the real chat.bitchat.nostr item when exercised under test. - clearAllAssociations() used raw SecItem* calls that bypassed the injected keychain entirely, so panicClearAllData tests wiped the real Nostr identity items on every run. Fix: centralize FavoritesPersistenceService's test-guarded in-memory default as KeychainManager.makeDefault() and use it for all default keychain parameters, and add deleteAll(service:) to KeychainManagerProtocol so clearAllAssociations() goes through the injected keychain like every other operation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Share one in-memory test keychain per process (Codex P2) A fresh store per makeDefault() call diverges from production, where separate default-constructed bridges share chat.bitchat.nostr — BoardManager's publish and NIP-09 delete paths would derive different geohash identities under test. PreviewKeychainManager gains a lock since the shared instance is reached from arbitrary threads. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
102 lines
2.8 KiB
Swift
102 lines
2.8 KiB
Swift
//
|
|
// PreviewKeychainManager.swift
|
|
// bitchat
|
|
//
|
|
// This is free and unencumbered software released into the public domain.
|
|
// For more information, see <https://unlicense.org>
|
|
//
|
|
|
|
import BitFoundation
|
|
import Foundation
|
|
|
|
final class PreviewKeychainManager: KeychainManagerProtocol {
|
|
// Locked: KeychainManager.makeDefault() hands one shared instance to
|
|
// every default-constructed component under test, which access it from
|
|
// arbitrary threads.
|
|
private let lock = NSLock()
|
|
private var storage: [String: Data] = [:]
|
|
private var serviceStorage: [String: [String: Data]] = [:]
|
|
init() {}
|
|
|
|
func saveIdentityKey(_ keyData: Data, forKey key: String) -> Bool {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
storage[key] = keyData
|
|
return true
|
|
}
|
|
|
|
func getIdentityKey(forKey key: String) -> Data? {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
return storage[key]
|
|
}
|
|
|
|
func deleteIdentityKey(forKey key: String) -> Bool {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
storage.removeValue(forKey: key)
|
|
return true
|
|
}
|
|
|
|
func deleteAllKeychainData() -> Bool {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
storage.removeAll()
|
|
serviceStorage.removeAll()
|
|
return true
|
|
}
|
|
|
|
func secureClear(_ data: inout Data) {}
|
|
|
|
func secureClear(_ string: inout String) {}
|
|
|
|
func verifyIdentityKeyExists() -> Bool {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
return storage["identity_noiseStaticKey"] != nil
|
|
}
|
|
|
|
// BCH-01-009: New methods with proper error classification
|
|
func getIdentityKeyWithResult(forKey key: String) -> KeychainReadResult {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
if let data = storage[key] {
|
|
return .success(data)
|
|
}
|
|
return .itemNotFound
|
|
}
|
|
|
|
func saveIdentityKeyWithResult(_ keyData: Data, forKey key: String) -> KeychainSaveResult {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
storage[key] = keyData
|
|
return .success
|
|
}
|
|
|
|
// MARK: - Generic Data Storage (consolidated from KeychainHelper)
|
|
|
|
func save(key: String, data: Data, service: String, accessible: CFString?) {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
serviceStorage[service, default: [:]][key] = data
|
|
}
|
|
|
|
func load(key: String, service: String) -> Data? {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
return serviceStorage[service]?[key]
|
|
}
|
|
|
|
func delete(key: String, service: String) {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
serviceStorage[service]?.removeValue(forKey: key)
|
|
}
|
|
|
|
func deleteAll(service: String) {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
serviceStorage.removeValue(forKey: service)
|
|
}
|
|
}
|