import Foundation import Testing @testable import LocalData @Suite(.serialized) struct KeychainHelperTests { private let testService = "LocalDataTests.Keychain.\(UUID().uuidString)" private let keychain = MockKeychainHelper() // MARK: - Basic Round Trip @Test func keychainRoundTrip() async throws { let key = "test.credential" let data = Data("secret-password".utf8) defer { let k = keychain let s = testService Task { try? await k.delete(service: s, key: key) } } try await keychain.set( data, service: testService, key: key, accessibility: .afterFirstUnlock ) let retrieved = try await keychain.get(service: testService, key: key) #expect(retrieved == data) } @Test func keychainNotFoundReturnsNil() async throws { let result = try await keychain.get( service: testService, key: "nonexistent.\(UUID().uuidString)" ) #expect(result == nil) } @Test func keychainDeleteNonexistentDoesNotThrow() async throws { // Should not throw even if item doesn't exist try await keychain.delete( service: testService, key: "nonexistent.\(UUID().uuidString)" ) } @Test func keychainExists() async throws { let key = "test.exists.\(UUID().uuidString)" let data = Data("test".utf8) defer { let k = keychain let s = testService Task { try? await k.delete(service: s, key: key) } } let beforeExists = try await keychain.exists(service: testService, key: key) #expect(beforeExists == false) try await keychain.set( data, service: testService, key: key, accessibility: .whenUnlocked ) let afterExists = try await keychain.exists(service: testService, key: key) #expect(afterExists == true) } @Test func keychainUpdateReplacesData() async throws { let key = "test.update.\(UUID().uuidString)" let originalData = Data("original".utf8) let updatedData = Data("updated".utf8) defer { let k = keychain let s = testService Task { try? await k.delete(service: s, key: key) } } try await keychain.set( originalData, service: testService, key: key, accessibility: .afterFirstUnlock ) try await keychain.set( updatedData, service: testService, key: key, accessibility: .afterFirstUnlock ) let retrieved = try await keychain.get(service: testService, key: key) #expect(retrieved == updatedData) } @Test func keychainDeleteAll() async throws { let deleteAllService = "LocalDataTests.DeleteAll.\(UUID().uuidString)" let data = Data("test".utf8) // Create multiple items for i in 0..<3 { try await keychain.set( data, service: deleteAllService, key: "key\(i)", accessibility: .afterFirstUnlock ) } // Verify they exist let exists0 = try await keychain.exists(service: deleteAllService, key: "key0") #expect(exists0 == true) // Delete all try await keychain.deleteAll(service: deleteAllService) // Verify they're gone for i in 0..<3 { let exists = try await keychain.exists(service: deleteAllService, key: "key\(i)") #expect(exists == false) } } // MARK: - Accessibility Options @Test(arguments: KeychainAccessibility.allCases) func keychainAccessibilityOptions(accessibility: KeychainAccessibility) async throws { let key = "test.accessibility.\(accessibility).\(UUID().uuidString)" let data = Data("data-for-\(accessibility)".utf8) defer { let k = keychain let s = testService Task { try? await k.delete(service: s, key: key) } } try await keychain.set( data, service: testService, key: key, accessibility: accessibility ) let retrieved = try await keychain.get(service: testService, key: key) #expect(retrieved == data) } }