From 671f062c0d37eeff8e7aee9f4e6927c012694ec2 Mon Sep 17 00:00:00 2001 From: Katherine Bertelsen Date: Tue, 9 Dec 2025 13:00:33 -0600 Subject: [PATCH 1/3] Service protocol --- .../Biometrics/BiometricsRepository.swift | 23 +++++++++++++++++-- .../Core/Platform/Services/StateService.swift | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/BitwardenShared/Core/Auth/Services/Biometrics/BiometricsRepository.swift b/BitwardenShared/Core/Auth/Services/Biometrics/BiometricsRepository.swift index 91ff7b8f44..c9be3b01a9 100644 --- a/BitwardenShared/Core/Auth/Services/Biometrics/BiometricsRepository.swift +++ b/BitwardenShared/Core/Auth/Services/Biometrics/BiometricsRepository.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenSdk import LocalAuthentication @@ -66,7 +67,7 @@ class DefaultBiometricsRepository: BiometricsRepository { var keychainRepository: KeychainRepository /// A service used to update user preferences. - var stateService: StateService + var stateService: BiometricsStateService // MARK: Initialization @@ -80,7 +81,7 @@ class DefaultBiometricsRepository: BiometricsRepository { init( biometricsService: BiometricsService, keychainService: KeychainRepository, - stateService: StateService, + stateService: BiometricsStateService, ) { self.biometricsService = biometricsService keychainRepository = keychainService @@ -190,3 +191,21 @@ extension DefaultBiometricsRepository { } } } + +public protocol BiometricsStateService: ActiveAccountStateProvider { // sourcery: AutoMockable + /// Get the active user's Biometric Authentication Preference. + /// + /// - Returns: A `Bool` indicating the user's preference for using biometric authentication. + /// If `true`, the device should attempt biometric authentication for authorization events. + /// If `false`, the device should not attempt biometric authentication for authorization events. + /// + func getBiometricAuthenticationEnabled() async throws -> Bool + + /// Sets the user's Biometric Authentication Preference. + /// + /// - Parameter isEnabled: A `Bool` indicating the user's preference for using biometric authentication. + /// If `true`, the device should attempt biometric authentication for authorization events. + /// If `false`, the device should not attempt biometric authentication for authorization events. + /// + func setBiometricAuthenticationEnabled(_ isEnabled: Bool?) async throws +} diff --git a/BitwardenShared/Core/Platform/Services/StateService.swift b/BitwardenShared/Core/Platform/Services/StateService.swift index 202f998f74..b8d7242033 100644 --- a/BitwardenShared/Core/Platform/Services/StateService.swift +++ b/BitwardenShared/Core/Platform/Services/StateService.swift @@ -2376,7 +2376,7 @@ struct AccountVolatileData { // MARK: Biometrics -extension DefaultStateService { +extension DefaultStateService: BiometricsStateService { func getBiometricAuthenticationEnabled() async throws -> Bool { let userId = try getActiveAccountUserId() return appSettingsStore.isBiometricAuthenticationEnabled(userId: userId) From 9ac21ae2156cbf8a5a433054bc7922087ef7965d Mon Sep 17 00:00:00 2001 From: Katherine Bertelsen Date: Thu, 11 Dec 2025 10:39:24 -0600 Subject: [PATCH 2/3] Biometrics State Service --- .../Services/BiometricsStateService.swift | 21 +++ .../Mocks/MockBiometricsStateService.swift | 29 ++++ .../Biometrics/BiometricsRepository.swift | 18 -- .../BiometricsRepositoryTests.swift | 163 +++++++----------- TestHelpers/Support/BitwardenTestError.swift | 5 +- 5 files changed, 113 insertions(+), 123 deletions(-) create mode 100644 BitwardenKit/Core/Platform/Services/BiometricsStateService.swift create mode 100644 BitwardenKit/Core/Platform/Services/Mocks/MockBiometricsStateService.swift diff --git a/BitwardenKit/Core/Platform/Services/BiometricsStateService.swift b/BitwardenKit/Core/Platform/Services/BiometricsStateService.swift new file mode 100644 index 0000000000..13e63b26f7 --- /dev/null +++ b/BitwardenKit/Core/Platform/Services/BiometricsStateService.swift @@ -0,0 +1,21 @@ +// MARK: - BiometricsStateService + +/// A protocol for a service that provides state management functionality around biometrics. +/// +public protocol BiometricsStateService: ActiveAccountStateProvider { + /// Get the active user's Biometric Authentication Preference. + /// + /// - Returns: A `Bool` indicating the user's preference for using biometric authentication. + /// If `true`, the device should attempt biometric authentication for authorization events. + /// If `false`, the device should not attempt biometric authentication for authorization events. + /// + func getBiometricAuthenticationEnabled() async throws -> Bool + + /// Sets the user's Biometric Authentication Preference. + /// + /// - Parameter isEnabled: A `Bool` indicating the user's preference for using biometric authentication. + /// If `true`, the device should attempt biometric authentication for authorization events. + /// If `false`, the device should not attempt biometric authentication for authorization events. + /// + func setBiometricAuthenticationEnabled(_ isEnabled: Bool?) async throws +} diff --git a/BitwardenKit/Core/Platform/Services/Mocks/MockBiometricsStateService.swift b/BitwardenKit/Core/Platform/Services/Mocks/MockBiometricsStateService.swift new file mode 100644 index 0000000000..d09cb7b8ce --- /dev/null +++ b/BitwardenKit/Core/Platform/Services/Mocks/MockBiometricsStateService.swift @@ -0,0 +1,29 @@ +import BitwardenKit +import TestHelpers + +public class MockBiometricsStateService: BiometricsStateService { + public var activeAccountIdError: Error? + public var activeAccountIdResult = Result.failure(BitwardenTestError.mock("Mock error not set")) + public var biometricAuthenticationEnabledResult: Result = .success(false) + public var setBiometricAuthenticationEnabledError: Error? + + public init() {} + + public func getActiveAccountId() async throws -> String { + if let activeAccountIdError { + throw activeAccountIdError + } + return try activeAccountIdResult.get() + } + + public func getBiometricAuthenticationEnabled() async throws -> Bool { + try biometricAuthenticationEnabledResult.get() + } + + public func setBiometricAuthenticationEnabled(_ isEnabled: Bool?) async throws { + if let setBiometricAuthenticationEnabledError { + throw setBiometricAuthenticationEnabledError + } + biometricAuthenticationEnabledResult = .success(isEnabled ?? false) + } +} diff --git a/BitwardenShared/Core/Auth/Services/Biometrics/BiometricsRepository.swift b/BitwardenShared/Core/Auth/Services/Biometrics/BiometricsRepository.swift index c9be3b01a9..11c42376b7 100644 --- a/BitwardenShared/Core/Auth/Services/Biometrics/BiometricsRepository.swift +++ b/BitwardenShared/Core/Auth/Services/Biometrics/BiometricsRepository.swift @@ -191,21 +191,3 @@ extension DefaultBiometricsRepository { } } } - -public protocol BiometricsStateService: ActiveAccountStateProvider { // sourcery: AutoMockable - /// Get the active user's Biometric Authentication Preference. - /// - /// - Returns: A `Bool` indicating the user's preference for using biometric authentication. - /// If `true`, the device should attempt biometric authentication for authorization events. - /// If `false`, the device should not attempt biometric authentication for authorization events. - /// - func getBiometricAuthenticationEnabled() async throws -> Bool - - /// Sets the user's Biometric Authentication Preference. - /// - /// - Parameter isEnabled: A `Bool` indicating the user's preference for using biometric authentication. - /// If `true`, the device should attempt biometric authentication for authorization events. - /// If `false`, the device should not attempt biometric authentication for authorization events. - /// - func setBiometricAuthenticationEnabled(_ isEnabled: Bool?) async throws -} diff --git a/BitwardenShared/Core/Auth/Services/Biometrics/BiometricsRepositoryTests.swift b/BitwardenShared/Core/Auth/Services/Biometrics/BiometricsRepositoryTests.swift index 88f246ae42..f8b682f2e7 100644 --- a/BitwardenShared/Core/Auth/Services/Biometrics/BiometricsRepositoryTests.swift +++ b/BitwardenShared/Core/Auth/Services/Biometrics/BiometricsRepositoryTests.swift @@ -1,3 +1,4 @@ +import BitwardenKitMocks import LocalAuthentication import TestHelpers import XCTest @@ -7,17 +8,11 @@ import XCTest // MARK: - BiometricsRepositoryTests final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_body_length - // MARK: Types - - enum TestError: Error, Equatable { - case mock(String) - } - // MARK: Properties var biometricsService: MockBiometricsService! var keychainService: MockKeychainRepository! - var stateService: MockStateService! + var stateService: MockBiometricsStateService! var subject: DefaultBiometricsRepository! // MARK: Setup & Teardown @@ -27,7 +22,7 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: biometricsService = MockBiometricsService() keychainService = MockKeychainRepository() - stateService = MockStateService() + stateService = MockBiometricsStateService() subject = DefaultBiometricsRepository( biometricsService: biometricsService, @@ -56,17 +51,17 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: XCTAssertNil(subject.getBiometricAuthenticationType()) } - /// `setBiometricUnlockKey` throws for a no user situation. + /// `setBiometricUnlockKey` throws for a no-user situation. func test_getBiometricUnlockKey_noActiveAccount() async throws { - stateService.activeAccount = nil - await assertAsyncThrows(error: StateServiceError.noActiveAccount) { + stateService.activeAccountIdResult = .failure(BitwardenTestError.example) + await assertAsyncThrows(error: BitwardenTestError.example) { _ = try await subject.getUserAuthKey() } } /// `setBiometricUnlockKey` throws for a keychain error. func test_getBiometricUnlockKey_keychainServiceError() async throws { - stateService.activeAccount = .fixture() + stateService.activeAccountIdResult = .success("1") keychainService.getResult = .failure( KeychainServiceError.keyNotFound(.biometrics(userId: "1")), ) @@ -78,7 +73,7 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `setBiometricUnlockKey` throws an error for an empty key. func test_getBiometricUnlockKey_emptyString() async throws { let expectedKey = "" - stateService.activeAccount = .fixture() + stateService.activeAccountIdResult = .success("1") keychainService.getResult = .success(expectedKey) await assertAsyncThrows(error: BiometricsServiceError.getAuthKeyFailed) { _ = try await subject.getUserAuthKey() @@ -88,7 +83,7 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `setBiometricUnlockKey` returns the correct key for the active user. func test_getBiometricUnlockKey_success() async throws { let expectedKey = "expectedKey" - stateService.activeAccount = .fixture() + stateService.activeAccountIdResult = .success("1") keychainService.getResult = .success(expectedKey) let key = try await subject.getUserAuthKey() XCTAssertEqual(key, expectedKey) @@ -97,7 +92,7 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `setBiometricUnlockKey` throws a cancelled error for `errSecAuthFailed` if the device is /// locked while performing biometrics. func test_getBiometricUnlockKey_authFailed() async throws { - stateService.activeAccount = .fixture() + stateService.activeAccountIdResult = .success("1") keychainService.getResult = .failure( KeychainServiceError.osStatusError(errSecAuthFailed), ) @@ -108,12 +103,9 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `getBiometricUnlockStatus` throws an error if the user has locked biometrics. func test_getBiometricUnlockStatus_lockout() async throws { - let active = Account.fixture() - stateService.activeAccount = active + stateService.activeAccountIdResult = .success("1") biometricsService.biometricAuthStatus = .lockedOut(.faceID) - stateService.biometricsEnabled = [ - active.profile.userId: false, - ] + stateService.biometricAuthenticationEnabledResult = .success(false) await assertAsyncThrows(error: BiometricsServiceError.biometryLocked) { _ = try await subject.getBiometricUnlockStatus() } @@ -121,12 +113,9 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `getBiometricUnlockStatus` tracks the availability of biometrics. func test_getBiometricUnlockStatus_success_denied() async throws { - let active = Account.fixture() - stateService.activeAccount = active + stateService.activeAccountIdResult = .success("1") biometricsService.biometricAuthStatus = .denied(.touchID) - stateService.biometricsEnabled = [ - active.profile.userId: false, - ] + stateService.biometricAuthenticationEnabledResult = .success(false) let status = try await subject.getBiometricUnlockStatus() XCTAssertEqual( status, @@ -136,12 +125,9 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `getBiometricUnlockStatus` tracks if a user has enabled or disabled biometrics. func test_getBiometricUnlockStatus_success_disabled() async throws { - let active = Account.fixture() - stateService.activeAccount = active + stateService.activeAccountIdResult = .success("1") + stateService.biometricAuthenticationEnabledResult = .success(false) biometricsService.biometricAuthStatus = .authorized(.touchID) - stateService.biometricsEnabled = [ - active.profile.userId: false, - ] let status = try await subject.getBiometricUnlockStatus() XCTAssertEqual( status, @@ -151,12 +137,9 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `getBiometricUnlockStatus` tracks all biometrics components. func test_getBiometricUnlockStatus_success() async throws { - let active = Account.fixture() - stateService.activeAccount = active + stateService.activeAccountIdResult = .success("1") + stateService.biometricAuthenticationEnabledResult = .success(true) biometricsService.biometricAuthStatus = .authorized(.faceID) - stateService.biometricsEnabled = [ - active.profile.userId: true, - ] let status = try await subject.getBiometricUnlockStatus() XCTAssertEqual( status, @@ -166,11 +149,8 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `getUserAuthKey` throws on empty keys. func test_getUserAuthKey_emptyString() async throws { - let active = Account.fixture() - stateService.activeAccount = active - stateService.biometricsEnabled = [ - active.profile.userId: true, - ] + stateService.activeAccountIdResult = .success("1") + stateService.biometricAuthenticationEnabledResult = .success(true) keychainService.getResult = .success("") await assertAsyncThrows(error: BiometricsServiceError.getAuthKeyFailed) { _ = try await subject.getUserAuthKey() @@ -179,11 +159,8 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `getUserAuthKey` retrieves the key from keychain. func test_getUserAuthKey_success() async throws { - let active = Account.fixture() - stateService.activeAccount = active - stateService.biometricsEnabled = [ - active.profile.userId: true, - ] + stateService.activeAccountIdResult = .success("1") + stateService.biometricAuthenticationEnabledResult = .success(true) keychainService.getResult = .success("Dramatic Masterpiece") let key = try await subject.getUserAuthKey() XCTAssertEqual( @@ -194,11 +171,8 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `getUserAuthKey` throws a biometry locked error if biometrics are locked out. func test_getUserAuthKey_lockedError() async throws { - let active = Account.fixture() - stateService.activeAccount = active - stateService.biometricsEnabled = [ - active.profile.userId: true, - ] + stateService.activeAccountIdResult = .success("1") + stateService.biometricAuthenticationEnabledResult = .success(true) // -8 is the code for kLAErrorBiometryLockout. keychainService.getResult = .failure(KeychainServiceError.osStatusError(-8)) await assertAsyncThrows(error: BiometricsServiceError.biometryLocked) { @@ -208,11 +182,8 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `getUserAuthKey` throws a not found error if the key can't be found. func test_getUserAuthKey_notFoundError() async throws { - let active = Account.fixture() - stateService.activeAccount = active - stateService.biometricsEnabled = [ - active.profile.userId: true, - ] + stateService.activeAccountIdResult = .success("1") + stateService.biometricAuthenticationEnabledResult = .success(true) keychainService.getResult = .failure(KeychainServiceError.osStatusError(errSecItemNotFound)) await assertAsyncThrows(error: BiometricsServiceError.getAuthKeyFailed) { _ = try await subject.getUserAuthKey() @@ -221,11 +192,8 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `getUserAuthKey` throws a biometry failed error if biometrics are disconnected. func test_getUserAuthKey_biometryFailed() async throws { - let active = Account.fixture() - stateService.activeAccount = active - stateService.biometricsEnabled = [ - active.profile.userId: true, - ] + stateService.activeAccountIdResult = .success("1") + stateService.biometricAuthenticationEnabledResult = .success(true) keychainService.getResult = .failure(KeychainServiceError.osStatusError(kLAErrorBiometryDisconnected)) await assertAsyncThrows(error: BiometricsServiceError.biometryFailed) { _ = try await subject.getUserAuthKey() @@ -234,11 +202,8 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `getUserAuthKey` throws a biometry cancelled error if biometrics were cancelled. func test_getUserAuthKey_cancelled() async throws { - let active = Account.fixture() - stateService.activeAccount = active - stateService.biometricsEnabled = [ - active.profile.userId: true, - ] + stateService.activeAccountIdResult = .success("1") + stateService.biometricAuthenticationEnabledResult = .success(true) // Send the user cancelled code. keychainService.getResult = .failure(KeychainServiceError.osStatusError(errSecUserCanceled)) await assertAsyncThrows(error: BiometricsServiceError.biometryCancelled) { @@ -248,11 +213,8 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `getUserAuthKey` throws an error if one occurs. func test_getUserAuthKey_unknownError() async throws { - let active = Account.fixture() - stateService.activeAccount = active - stateService.biometricsEnabled = [ - active.profile.userId: true, - ] + stateService.activeAccountIdResult = .success("1") + stateService.biometricAuthenticationEnabledResult = .success(true) keychainService.getResult = .failure(BitwardenTestError.example) await assertAsyncThrows(error: BitwardenTestError.example) { _ = try await subject.getUserAuthKey() @@ -261,11 +223,8 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `getUserAuthKey` throws an error if an unknown OS error occurs. func test_getUserAuthKey_unknownOSError() async throws { - let active = Account.fixture() - stateService.activeAccount = active - stateService.biometricsEnabled = [ - active.profile.userId: true, - ] + stateService.activeAccountIdResult = .success("1") + stateService.biometricAuthenticationEnabledResult = .success(true) keychainService.getResult = .failure(KeychainServiceError.osStatusError(errSecParam)) await assertAsyncThrows(error: KeychainServiceError.osStatusError(errSecParam)) { _ = try await subject.getUserAuthKey() @@ -274,20 +233,19 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `setBiometricUnlockKey` throws when there is no active account. func test_setBiometricUnlockKey_nilValue_noActiveAccount() async throws { - stateService.activeAccount = nil - await assertAsyncThrows(error: StateServiceError.noActiveAccount) { + stateService.setBiometricAuthenticationEnabledError = BitwardenTestError.mock("NoActiveAccount") + await assertAsyncThrows(error: BitwardenTestError.mock("NoActiveAccount")) { try await subject.setBiometricUnlockKey(authKey: nil) } } /// `setBiometricUnlockKey` throws when there is a state service error. func test_setBiometricUnlockKey_nilValue_setBiometricAuthenticationEnabledFailed() async throws { - stateService.activeAccount = .fixture() - stateService.setBiometricAuthenticationEnabledResult = .failure( - TestError.mock("setBiometricAuthenticationEnabledFailed"), - ) + stateService.activeAccountIdResult = .success("1") + let error = BitwardenTestError.mock("setBiometricAuthenticationEnabledFailed") + stateService.setBiometricAuthenticationEnabledError = error await assertAsyncThrows( - error: TestError.mock("setBiometricAuthenticationEnabledFailed"), + error: BitwardenTestError.mock("setBiometricAuthenticationEnabledFailed"), ) { try await subject.setBiometricUnlockKey(authKey: nil) } @@ -295,39 +253,37 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `setBiometricUnlockKey` A failure in evaluating the biometrics policy clears any auth key. func test_setBiometricUnlockKey_evaluationFalse() async throws { - stateService.activeAccount = .fixture() - try? await stateService.setBiometricAuthenticationEnabled(true) + stateService.activeAccountIdResult = .success("1") + stateService.biometricAuthenticationEnabledResult = .success(true) keychainService.mockStorage = [ keychainService.formattedKey(for: .biometrics(userId: "1")): "storedKey", ] biometricsService.evaluationResult = false - stateService.setBiometricAuthenticationEnabledResult = .success(()) keychainService.deleteResult = .success(()) try await subject.setBiometricUnlockKey(authKey: nil) waitFor(keychainService.mockStorage.isEmpty) - let result = try XCTUnwrap(stateService.biometricsEnabled["1"]) + let result = try stateService.biometricAuthenticationEnabledResult.get() XCTAssertFalse(result) } /// `setBiometricUnlockKey` can remove a user key from the keychain and track the availability in state. func test_setBiometricUnlockKey_nilValue_success() async throws { - stateService.activeAccount = .fixture() - try? await stateService.setBiometricAuthenticationEnabled(true) + stateService.activeAccountIdResult = .success("1") + stateService.biometricAuthenticationEnabledResult = .success(true) keychainService.mockStorage = [ keychainService.formattedKey(for: .biometrics(userId: "1")): "storedKey", ] - stateService.setBiometricAuthenticationEnabledResult = .success(()) keychainService.deleteResult = .success(()) try await subject.setBiometricUnlockKey(authKey: nil) waitFor(keychainService.mockStorage.isEmpty) - let result = try XCTUnwrap(stateService.biometricsEnabled["1"]) + let result = try stateService.biometricAuthenticationEnabledResult.get() XCTAssertFalse(result) } /// `setBiometricUnlockKey` throws on a keychain error. func test_setBiometricUnlockKey_nilValue_successWithKeychainError() async throws { - stateService.activeAccount = .fixture() - stateService.setBiometricAuthenticationEnabledResult = .success(()) + stateService.activeAccountIdResult = .success("1") + stateService.biometricAuthenticationEnabledResult = .success(true) keychainService.deleteResult = .failure(KeychainServiceError.osStatusError(13)) await assertAsyncDoesNotThrow { try await subject.setBiometricUnlockKey(authKey: nil) @@ -336,20 +292,19 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `setBiometricUnlockKey` throws when there is no active account. func test_setBiometricUnlockKey_withValue_noActiveAccount() async throws { - stateService.activeAccount = nil - await assertAsyncThrows(error: StateServiceError.noActiveAccount) { + stateService.activeAccountIdError = BitwardenTestError.mock("NoActiveAccount") + await assertAsyncThrows(error: BitwardenTestError.mock("NoActiveAccount")) { try await subject.setBiometricUnlockKey(authKey: "authKey") } } /// `setBiometricUnlockKey` throws when there is no active account. func test_setBiometricUnlockKey_withValue_setBiometricAuthenticationEnabledFailed() async throws { - stateService.activeAccount = .fixture() - stateService.setBiometricAuthenticationEnabledResult = .failure( - TestError.mock("setBiometricAuthenticationEnabledFailed"), - ) + stateService.activeAccountIdResult = .success("1") + let error = BitwardenTestError.mock("setBiometricAuthenticationEnabledFailed") + stateService.setBiometricAuthenticationEnabledError = error await assertAsyncThrows( - error: TestError.mock("setBiometricAuthenticationEnabledFailed"), + error: BitwardenTestError.mock("setBiometricAuthenticationEnabledFailed"), ) { try await subject.setBiometricUnlockKey(authKey: "authKey") } @@ -357,8 +312,8 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `setBiometricUnlockKey` throws on a keychain error. func test_setBiometricUnlockKey_withValue_keychainError() async throws { - stateService.activeAccount = .fixture() - stateService.setBiometricAuthenticationEnabledResult = .success(()) + stateService.activeAccountIdResult = .success("1") + stateService.biometricAuthenticationEnabledResult = .success(true) keychainService.setResult = .failure(KeychainServiceError.osStatusError(13)) await assertAsyncThrows( error: BiometricsServiceError.setAuthKeyFailed, @@ -369,8 +324,8 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: /// `setBiometricUnlockKey` can store a user key to the keychain and track the availability in state. func test_setBiometricUnlockKey_withValue_success() async throws { - stateService.activeAccount = .fixture() - stateService.setBiometricAuthenticationEnabledResult = .success(()) + stateService.activeAccountIdResult = .success("1") + stateService.biometricAuthenticationEnabledResult = .success(true) keychainService.setResult = .success(()) try await subject.setBiometricUnlockKey(authKey: "authKey") waitFor(!keychainService.mockStorage.isEmpty) @@ -382,7 +337,7 @@ final class BiometricsRepositoryTests: BitwardenTestCase { // swiftlint:disable: ), )], ) - let result = try XCTUnwrap(stateService.biometricsEnabled["1"]) + let result = try stateService.biometricAuthenticationEnabledResult.get() XCTAssertTrue(result) XCTAssertEqual(keychainService.securityType, .biometryCurrentSet) } diff --git a/TestHelpers/Support/BitwardenTestError.swift b/TestHelpers/Support/BitwardenTestError.swift index 0257dbe366..27a8995759 100644 --- a/TestHelpers/Support/BitwardenTestError.swift +++ b/TestHelpers/Support/BitwardenTestError.swift @@ -7,13 +7,16 @@ import Foundation /// /// These errors will typically be provided to a mocked type to be thrown at the /// appropriate time. XCAssertThrows -public enum BitwardenTestError: LocalizedError { +public enum BitwardenTestError: Equatable, LocalizedError { case example + case mock(String) public var errorDescription: String? { switch self { case .example: "An example error used to test throwing capabilities." + case .mock(let string): + "A mock error with the string: \(string)." } } } From 7cd289b99e42baa59298d84e62cf80b32147036d Mon Sep 17 00:00:00 2001 From: Katherine Bertelsen Date: Mon, 15 Dec 2025 09:35:34 -0600 Subject: [PATCH 3/3] Remove --- .../Core/Platform/Services/StateService.swift | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/BitwardenShared/Core/Platform/Services/StateService.swift b/BitwardenShared/Core/Platform/Services/StateService.swift index b8d7242033..b3f34bd61b 100644 --- a/BitwardenShared/Core/Platform/Services/StateService.swift +++ b/BitwardenShared/Core/Platform/Services/StateService.swift @@ -146,14 +146,6 @@ protocol StateService: AnyObject { /// func getAppTheme() async -> AppTheme - /// Get the active user's Biometric Authentication Preference. - /// - /// - Returns: A `Bool` indicating the user's preference for using biometric authentication. - /// If `true`, the device should attempt biometric authentication for authorization events. - /// If `false`, the device should not attempt biometric authentication for authorization events. - /// - func getBiometricAuthenticationEnabled() async throws -> Bool - /// Gets the clear clipboard value for an account. /// /// - Parameter userId: The user ID associated with the clear clipboard value. Defaults to the active @@ -535,14 +527,6 @@ protocol StateService: AnyObject { /// func setAppTheme(_ appTheme: AppTheme) async - /// Sets the user's Biometric Authentication Preference. - /// - /// - Parameter isEnabled: A `Bool` indicating the user's preference for using biometric authentication. - /// If `true`, the device should attempt biometric authentication for authorization events. - /// If `false`, the device should not attempt biometric authentication for authorization events. - /// - func setBiometricAuthenticationEnabled(_ isEnabled: Bool?) async throws - /// Sets the clear clipboard value for an account. /// /// - Parameters: