Update Helpers and tests
Summary: - Sources: update Helpers - Tests: update tests for AppGroupTests.swift, RouterDomainTests.swift, RouterSecurityTests.swift (+1 more) Stats: - 5 files changed, 283 insertions(+), 2 deletions(-)
This commit is contained in:
parent
f5be4b8140
commit
74ec1a8666
@ -128,8 +128,8 @@ actor SyncHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/// A private proxy class to handle WCSessionDelegate callbacks and route them to the SyncHelper actor.
|
||||
private final class SessionDelegateProxy: NSObject, WCSessionDelegate {
|
||||
/// An internal proxy class to handle WCSessionDelegate callbacks and route them to the SyncHelper actor.
|
||||
internal final class SessionDelegateProxy: NSObject, WCSessionDelegate {
|
||||
static let shared = SessionDelegateProxy()
|
||||
|
||||
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
|
||||
|
||||
@ -53,4 +53,33 @@ import Testing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test func fileStorageAppGroupRoundTrip() async throws {
|
||||
// In simulator, this usually returns a placeholder path
|
||||
let identifier = "group.com.test.localdata"
|
||||
let fileName = "appgroup_file.txt"
|
||||
let data = Data("appgroup-file-content".utf8)
|
||||
|
||||
// Only run if the simulator/environment gives us a container
|
||||
if let _ = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: identifier) {
|
||||
try await fileStorageHelper.write(data, to: .documents, fileName: fileName, appGroupIdentifier: identifier)
|
||||
|
||||
let exists = await fileStorageHelper.exists(in: .documents, fileName: fileName, appGroupIdentifier: identifier)
|
||||
#expect(exists == true)
|
||||
|
||||
let retrieved = try await fileStorageHelper.read(from: .documents, fileName: fileName, appGroupIdentifier: identifier)
|
||||
#expect(retrieved == data)
|
||||
|
||||
let list = try await fileStorageHelper.list(in: .documents, appGroupIdentifier: identifier)
|
||||
#expect(list.contains(fileName))
|
||||
|
||||
let size = try await fileStorageHelper.size(of: .documents, fileName: fileName, appGroupIdentifier: identifier)
|
||||
#expect(size == Int64(data.count))
|
||||
|
||||
try await fileStorageHelper.delete(from: .documents, fileName: fileName, appGroupIdentifier: identifier)
|
||||
|
||||
let afterDelete = await fileStorageHelper.exists(in: .documents, fileName: fileName, appGroupIdentifier: identifier)
|
||||
#expect(afterDelete == false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
117
Tests/LocalDataTests/RouterDomainTests.swift
Normal file
117
Tests/LocalDataTests/RouterDomainTests.swift
Normal file
@ -0,0 +1,117 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import LocalData
|
||||
|
||||
@Suite struct RouterDomainTests {
|
||||
private let router: StorageRouter
|
||||
private let mockKeychain = MockKeychainHelper()
|
||||
|
||||
init() {
|
||||
let testBaseURL = FileManager.default.temporaryDirectory.appending(path: "RouterDomainTests-\(UUID().uuidString)")
|
||||
router = StorageRouter(
|
||||
keychain: mockKeychain,
|
||||
encryption: EncryptionHelper(keychain: mockKeychain),
|
||||
file: FileStorageHelper(configuration: FileStorageConfiguration(baseURL: testBaseURL)),
|
||||
defaults: UserDefaultsHelper(defaults: UserDefaults(suiteName: "RouterDomainTests-\(UUID().uuidString)")!)
|
||||
)
|
||||
}
|
||||
|
||||
private struct DomainKey: StorageKey {
|
||||
typealias Value = String
|
||||
let name: String
|
||||
let domain: StorageDomain
|
||||
let security: SecurityPolicy
|
||||
let serializer: Serializer<String> = .json
|
||||
let owner: String = "DomainTests"
|
||||
let description: String = "Domain test key"
|
||||
let availability: PlatformAvailability = .all
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
|
||||
init(name: String, domain: StorageDomain, security: SecurityPolicy = .none) {
|
||||
self.name = name
|
||||
self.domain = domain
|
||||
self.security = security
|
||||
}
|
||||
}
|
||||
|
||||
@Test func domainUserDefaults() async throws {
|
||||
let key = DomainKey(name: "defaults.key", domain: .userDefaults(suite: nil))
|
||||
try await router.set("value", for: key)
|
||||
#expect(try await router.get(key) == "value")
|
||||
try await router.remove(key)
|
||||
#expect(await (try? router.exists(key)) == false)
|
||||
}
|
||||
|
||||
@Test func domainAppGroupUserDefaults() async throws {
|
||||
// We use a mock configuration to avoid requiring a real app group
|
||||
await router.updateStorageConfiguration(StorageConfiguration(defaultAppGroupIdentifier: "group.test"))
|
||||
|
||||
let key = DomainKey(name: "appgroup.defaults.key", domain: .appGroupUserDefaults(identifier: "group.test"))
|
||||
try await router.set("value", for: key)
|
||||
#expect(try await router.get(key) == "value")
|
||||
try await router.remove(key)
|
||||
}
|
||||
|
||||
@Test func domainKeychain() async throws {
|
||||
let key = DomainKey(
|
||||
name: "keychain.key",
|
||||
domain: .keychain(service: "test"),
|
||||
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
|
||||
)
|
||||
try await router.set("value", for: key)
|
||||
#expect(try await router.get(key) == "value")
|
||||
try await router.remove(key)
|
||||
}
|
||||
|
||||
@Test func domainFileSystem() async throws {
|
||||
let key = DomainKey(name: "file.key", domain: .fileSystem(directory: .documents))
|
||||
try await router.set("value", for: key)
|
||||
#expect(try await router.get(key) == "value")
|
||||
try await router.remove(key)
|
||||
}
|
||||
|
||||
@Test func domainEncryptedFileSystem() async throws {
|
||||
let key = DomainKey(name: "encfile.key", domain: .encryptedFileSystem(directory: .documents))
|
||||
try await router.set("value", for: key)
|
||||
#expect(try await router.get(key) == "value")
|
||||
try await router.remove(key)
|
||||
}
|
||||
|
||||
@Test func domainAppGroupFileSystem() async throws {
|
||||
// App blocks usually fail or return nil in tests, but we exercise the path
|
||||
await router.updateStorageConfiguration(StorageConfiguration(defaultAppGroupIdentifier: "group.test"))
|
||||
let key = DomainKey(name: "appgroup.file.key", domain: .appGroupFileSystem(identifier: "group.test", directory: .documents))
|
||||
|
||||
do {
|
||||
try await router.set("value", for: key)
|
||||
#expect(try await router.get(key) == "value")
|
||||
try await router.remove(key)
|
||||
} catch StorageError.invalidAppGroupIdentifier {
|
||||
// Path covered
|
||||
}
|
||||
}
|
||||
|
||||
@Test func resolutionFailureService() async throws {
|
||||
// Clear default service
|
||||
await router.updateStorageConfiguration(StorageConfiguration(defaultKeychainService: nil))
|
||||
let key = DomainKey(
|
||||
name: "bad.service.key",
|
||||
domain: .keychain(service: nil),
|
||||
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
|
||||
)
|
||||
|
||||
await #expect(throws: StorageError.keychainError(errSecBadReq)) {
|
||||
try await router.set("value", for: key)
|
||||
}
|
||||
}
|
||||
|
||||
@Test func resolutionFailureIdentifier() async throws {
|
||||
// Clear default identifier
|
||||
await router.updateStorageConfiguration(StorageConfiguration(defaultAppGroupIdentifier: nil))
|
||||
let key = DomainKey(name: "bad.id.key", domain: .appGroupUserDefaults(identifier: nil))
|
||||
|
||||
await #expect(throws: StorageError.invalidAppGroupIdentifier("none")) {
|
||||
try await router.set("value", for: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
92
Tests/LocalDataTests/RouterSecurityTests.swift
Normal file
92
Tests/LocalDataTests/RouterSecurityTests.swift
Normal file
@ -0,0 +1,92 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
import Security
|
||||
@testable import LocalData
|
||||
|
||||
@Suite struct RouterSecurityTests {
|
||||
private let router: StorageRouter
|
||||
private let mockKeychain = MockKeychainHelper()
|
||||
|
||||
init() {
|
||||
let testBaseURL = FileManager.default.temporaryDirectory.appending(path: "RouterSecurityTests-\(UUID().uuidString)")
|
||||
router = StorageRouter(
|
||||
keychain: mockKeychain,
|
||||
encryption: EncryptionHelper(keychain: mockKeychain),
|
||||
file: FileStorageHelper(configuration: FileStorageConfiguration(baseURL: testBaseURL)),
|
||||
defaults: UserDefaultsHelper(defaults: UserDefaults(suiteName: "RouterSecurityTests-\(UUID().uuidString)")!)
|
||||
)
|
||||
}
|
||||
|
||||
private struct SecurityKey: StorageKey {
|
||||
typealias Value = String
|
||||
let name: String
|
||||
let domain: StorageDomain
|
||||
let security: SecurityPolicy
|
||||
let serializer: Serializer<String> = .json
|
||||
let owner: String = "SecurityTests"
|
||||
let description: String = "Security test key"
|
||||
let availability: PlatformAvailability = .all
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
|
||||
@Test func applySecurityNone() async throws {
|
||||
let key = SecurityKey(name: "none.key", domain: .userDefaults(suite: nil), security: .none)
|
||||
let value = "test-value"
|
||||
|
||||
try await router.set(value, for: key)
|
||||
let retrieved: String = try await router.get(key)
|
||||
#expect(retrieved == value)
|
||||
}
|
||||
|
||||
@Test func applySecurityEncryptedAES() async throws {
|
||||
let key = SecurityKey(
|
||||
name: "aes.key",
|
||||
domain: .userDefaults(suite: nil),
|
||||
security: .encrypted(.aes256(keyDerivation: .hkdf()))
|
||||
)
|
||||
let value = "aes-secret"
|
||||
|
||||
try await router.set(value, for: key)
|
||||
let retrieved: String = try await router.get(key)
|
||||
#expect(retrieved == value)
|
||||
}
|
||||
|
||||
@Test func applySecurityEncryptedChaCha() async throws {
|
||||
let key = SecurityKey(
|
||||
name: "chacha.key",
|
||||
domain: .userDefaults(suite: nil),
|
||||
security: .encrypted(.chacha20Poly1305(keyDerivation: .hkdf()))
|
||||
)
|
||||
let value = "chacha-secret"
|
||||
|
||||
try await router.set(value, for: key)
|
||||
let retrieved: String = try await router.get(key)
|
||||
#expect(retrieved == value)
|
||||
}
|
||||
|
||||
@Test func applySecurityKeychain() async throws {
|
||||
let key = SecurityKey(
|
||||
name: "keychain.key",
|
||||
domain: .keychain(service: "test-service"),
|
||||
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
|
||||
)
|
||||
let value = "keychain-secret"
|
||||
|
||||
try await router.set(value, for: key)
|
||||
let retrieved: String = try await router.get(key)
|
||||
#expect(retrieved == value)
|
||||
}
|
||||
|
||||
@Test func applySecurityPBKDF2() async throws {
|
||||
let key = SecurityKey(
|
||||
name: "pbkdf2.key",
|
||||
domain: .userDefaults(suite: nil),
|
||||
security: .encrypted(.aes256(keyDerivation: .pbkdf2()))
|
||||
)
|
||||
let value = "pbkdf2-secret"
|
||||
|
||||
try await router.set(value, for: key)
|
||||
let retrieved: String = try await router.get(key)
|
||||
#expect(retrieved == value)
|
||||
}
|
||||
}
|
||||
43
Tests/LocalDataTests/SyncDelegateTests.swift
Normal file
43
Tests/LocalDataTests/SyncDelegateTests.swift
Normal file
@ -0,0 +1,43 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
import WatchConnectivity
|
||||
@testable import LocalData
|
||||
|
||||
@Suite struct SyncDelegateTests {
|
||||
|
||||
@Test func delegateProxyActivationCallbacks() {
|
||||
let proxy = SessionDelegateProxy.shared
|
||||
let session = WCSession.default
|
||||
|
||||
// Exercise activation completion (success)
|
||||
proxy.session(session, activationDidCompleteWith: .activated, error: nil)
|
||||
|
||||
// Exercise activation completion (error)
|
||||
let error = NSError(domain: "test", code: 1, userInfo: nil)
|
||||
proxy.session(session, activationDidCompleteWith: .notActivated, error: error)
|
||||
}
|
||||
|
||||
@Test func delegateProxyContextReceived() async throws {
|
||||
let proxy = SessionDelegateProxy.shared
|
||||
let session = WCSession.default
|
||||
let context: [String: Any] = [
|
||||
"test.sync.key": Data("sync-data".utf8)
|
||||
]
|
||||
|
||||
// This triggers SyncHelper.handleReceivedContext
|
||||
proxy.session(session, didReceiveApplicationContext: context)
|
||||
|
||||
// Wait a bit for the Task to start/finish
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
@Test func delegateProxyiOSCallbacks() {
|
||||
let proxy = SessionDelegateProxy.shared
|
||||
let session = WCSession.default
|
||||
|
||||
proxy.sessionDidBecomeInactive(session)
|
||||
proxy.sessionDidDeactivate(session)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user