mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Initial implementation for AppLockSetupFlowCoordinator. (#1949)
Rename AppLockSettingsScreen to AppLockSetupSettingsScreen. Implement forced logout and persist PIN entry attempts.
This commit is contained in:
@@ -20,6 +20,7 @@ import XCTest
|
||||
|
||||
@MainActor
|
||||
class AppLockScreenViewModelTests: XCTestCase {
|
||||
var appSettings: AppSettings!
|
||||
var appLockService: AppLockService!
|
||||
var keychainController: KeychainControllerMock!
|
||||
var viewModel: AppLockScreenViewModelProtocol!
|
||||
@@ -28,8 +29,9 @@ class AppLockScreenViewModelTests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
AppSettings.reset()
|
||||
appSettings = AppSettings()
|
||||
keychainController = KeychainControllerMock()
|
||||
appLockService = AppLockService(keychainController: keychainController, appSettings: AppSettings())
|
||||
appLockService = AppLockService(keychainController: keychainController, appSettings: appSettings)
|
||||
viewModel = AppLockScreenViewModel(appLockService: appLockService)
|
||||
}
|
||||
|
||||
@@ -51,4 +53,60 @@ class AppLockScreenViewModelTests: XCTestCase {
|
||||
// The app should become unlocked.
|
||||
XCTAssertEqual(result, .appUnlocked)
|
||||
}
|
||||
|
||||
func testForgotPIN() {
|
||||
// Given a fresh launch of the app.
|
||||
XCTAssertNil(context.alertInfo, "No alert should be shown initially.")
|
||||
|
||||
// When the user has forgotten their PIN.
|
||||
context.send(viewAction: .forgotPIN)
|
||||
|
||||
// Then an alert should be shown before logging out.
|
||||
XCTAssertEqual(context.alertInfo?.id, .confirmResetPIN, "An alert should be shown before logging out.")
|
||||
}
|
||||
|
||||
func testUnlockFailure() async throws {
|
||||
// Given an invalid PIN code.
|
||||
let pinCode = "2024"
|
||||
keychainController.pinCodeReturnValue = "2023"
|
||||
XCTAssertEqual(context.viewState.numberOfPINAttempts, 0, "The shouldn't be any attempts yet.")
|
||||
XCTAssertFalse(context.viewState.isSubtitleWarning, "No warning should be shown yet.")
|
||||
XCTAssertNil(context.alertInfo, "No alert should be shown yet.")
|
||||
|
||||
// When entering it on the lock screen.
|
||||
viewModel.context.pinCode = pinCode
|
||||
context.send(viewAction: .submitPINCode)
|
||||
|
||||
// Then a failed attempt should be shown.
|
||||
XCTAssertEqual(context.viewState.numberOfPINAttempts, 1, "A failed attempt should have been recorded.")
|
||||
XCTAssertTrue(context.viewState.isSubtitleWarning, "A warning should now be shown.")
|
||||
XCTAssertNil(context.alertInfo, "No alert should be shown yet.")
|
||||
|
||||
// When entering twice more
|
||||
context.send(viewAction: .submitPINCode)
|
||||
context.send(viewAction: .submitPINCode)
|
||||
|
||||
// Then an alert should be shown
|
||||
XCTAssertEqual(context.viewState.numberOfPINAttempts, 3, "All the attempts should have been recorded.")
|
||||
XCTAssertTrue(context.viewState.isSubtitleWarning, "The warning should still be shown.")
|
||||
XCTAssertEqual(context.alertInfo?.id, .forcedLogout, "An alert should now be shown.")
|
||||
}
|
||||
|
||||
func testForceQuitRequiresLogout() {
|
||||
// Given an app with a PIN set where the user attempted to unlock 3 times.
|
||||
keychainController.pinCodeReturnValue = "2023"
|
||||
appSettings.appLockNumberOfPINAttempts = 2
|
||||
XCTAssertNil(context.alertInfo)
|
||||
viewModel.context.pinCode = "0000"
|
||||
context.send(viewAction: .submitPINCode)
|
||||
XCTAssertEqual(appSettings.appLockNumberOfPINAttempts, 3, "The app should have 3 failed attempts before the force quit.")
|
||||
XCTAssertEqual(context.alertInfo?.id, .forcedLogout, "The app should be showing the alert before the force quit.")
|
||||
|
||||
// When force quitting the app and relaunching.
|
||||
viewModel = nil
|
||||
let freshViewModel = AppLockScreenViewModel(appLockService: appLockService)
|
||||
|
||||
// Then the alert should remain in place
|
||||
XCTAssertEqual(freshViewModel.context.alertInfo?.id, .forcedLogout, "The new view model from the fresh launch should also show the alert")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,4 +175,47 @@ class AppLockServiceTests: XCTestCase {
|
||||
XCTAssertFalse(service.isEnabled, "The service should no longer be enabled.")
|
||||
XCTAssertFalse(service.unlock(with: pinCode), "The initial PIN shouldn't work any more.")
|
||||
}
|
||||
|
||||
func testResetAttemptsOnUnlock() {
|
||||
// Given a service that is enabled and has failed unlock attempts.
|
||||
let pinCode = "2023"
|
||||
guard case .success = service.setupPINCode(pinCode) else {
|
||||
XCTFail("The PIN should be valid.")
|
||||
return
|
||||
}
|
||||
appSettings.appLockNumberOfPINAttempts = 2
|
||||
appSettings.appLockNumberOfBiometricAttempts = 2
|
||||
XCTAssertEqual(appSettings.appLockNumberOfPINAttempts, 2, "The initial conditions should be stored.")
|
||||
XCTAssertEqual(appSettings.appLockNumberOfBiometricAttempts, 2, "The initial conditions should be stored.")
|
||||
XCTAssertTrue(service.isEnabled, "The service should be enabled.")
|
||||
|
||||
// When unlocking the service
|
||||
XCTAssertTrue(service.unlock(with: pinCode), "The PIN should work.")
|
||||
|
||||
// Then the attempts counts should both be reset.
|
||||
XCTAssertEqual(appSettings.appLockNumberOfPINAttempts, 0, "The PIN attempts should be reset.")
|
||||
XCTAssertEqual(appSettings.appLockNumberOfBiometricAttempts, 0, "The biometric attempts should be reset.")
|
||||
}
|
||||
|
||||
func testResetAttemptsOnDisable() {
|
||||
// Given a service that is enabled and has failed unlock attempts.
|
||||
let pinCode = "2023"
|
||||
guard case .success = service.setupPINCode(pinCode) else {
|
||||
XCTFail("The PIN should be valid.")
|
||||
return
|
||||
}
|
||||
appSettings.appLockNumberOfPINAttempts = 2
|
||||
appSettings.appLockNumberOfBiometricAttempts = 2
|
||||
XCTAssertEqual(appSettings.appLockNumberOfPINAttempts, 2, "The initial conditions should be stored.")
|
||||
XCTAssertEqual(appSettings.appLockNumberOfBiometricAttempts, 2, "The initial conditions should be stored.")
|
||||
XCTAssertTrue(service.isEnabled, "The service should be enabled.")
|
||||
|
||||
// When disabling the service
|
||||
service.disable()
|
||||
XCTAssertFalse(service.isEnabled, "The service should be disabled.")
|
||||
|
||||
// Then the attempts counts should both be reset.
|
||||
XCTAssertEqual(appSettings.appLockNumberOfPINAttempts, 0, "The PIN attempts should be reset.")
|
||||
XCTAssertEqual(appSettings.appLockNumberOfBiometricAttempts, 0, "The biometric attempts should be reset.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,12 @@ import XCTest
|
||||
@testable import ElementX
|
||||
|
||||
@MainActor
|
||||
class AppLockSettingsScreenViewModelTests: XCTestCase {
|
||||
class AppLockSetupSettingsScreenViewModelTests: XCTestCase {
|
||||
var appLockService: AppLockServiceProtocol!
|
||||
var keychainController: KeychainControllerMock!
|
||||
var viewModel: AppLockSettingsScreenViewModelProtocol!
|
||||
var viewModel: AppLockSetupSettingsScreenViewModelProtocol!
|
||||
|
||||
var context: AppLockSettingsScreenViewModelType.Context {
|
||||
var context: AppLockSetupSettingsScreenViewModelType.Context {
|
||||
viewModel.context
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class AppLockSettingsScreenViewModelTests: XCTestCase {
|
||||
keychainController = KeychainControllerMock()
|
||||
appLockService = AppLockService(keychainController: keychainController, appSettings: appSettings)
|
||||
|
||||
viewModel = AppLockSettingsScreenViewModel(appLockService: AppLockServiceMock.mock())
|
||||
viewModel = AppLockSetupSettingsScreenViewModel(appLockService: AppLockServiceMock.mock())
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
||||
@@ -37,7 +37,7 @@ class AppLockSetupPINScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testCreatePIN() async throws {
|
||||
viewModel = AppLockSetupPINScreenViewModel(initialMode: .create, appLockService: appLockService)
|
||||
viewModel = AppLockSetupPINScreenViewModel(initialMode: .create, isMandatory: false, appLockService: appLockService)
|
||||
XCTAssertEqual(context.viewState.mode, .create, "The mode should start as creation.")
|
||||
|
||||
let createDeferred = deferFulfillment(context.$viewState, message: "A valid PIN needs confirming.") { $0.mode == .confirm }
|
||||
@@ -53,7 +53,7 @@ class AppLockSetupPINScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testCreateWeakPIN() async throws {
|
||||
viewModel = AppLockSetupPINScreenViewModel(initialMode: .create, appLockService: appLockService)
|
||||
viewModel = AppLockSetupPINScreenViewModel(initialMode: .create, isMandatory: false, appLockService: appLockService)
|
||||
XCTAssertEqual(context.viewState.mode, .create, "The mode should start as creation.")
|
||||
XCTAssertNil(context.alertInfo, "There shouldn't be an alert to begin with.")
|
||||
|
||||
@@ -65,7 +65,7 @@ class AppLockSetupPINScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testCreatePINMismatch() async throws {
|
||||
viewModel = AppLockSetupPINScreenViewModel(initialMode: .create, appLockService: appLockService)
|
||||
viewModel = AppLockSetupPINScreenViewModel(initialMode: .create, isMandatory: false, appLockService: appLockService)
|
||||
XCTAssertEqual(context.viewState.mode, .create, "The mode should start as creation.")
|
||||
XCTAssertNil(context.alertInfo, "There shouldn't be an alert to begin with.")
|
||||
|
||||
@@ -83,7 +83,7 @@ class AppLockSetupPINScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testUnlock() async throws {
|
||||
viewModel = AppLockSetupPINScreenViewModel(initialMode: .unlock, appLockService: appLockService)
|
||||
viewModel = AppLockSetupPINScreenViewModel(initialMode: .unlock, isMandatory: false, appLockService: appLockService)
|
||||
let pinCode = "2023"
|
||||
keychainController.pinCodeReturnValue = pinCode
|
||||
keychainController.containsPINCodeReturnValue = true
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c78cada10d7c15137a635b0e841fe20e124814921d953296fca5ed16dea93fef
|
||||
size 98997
|
||||
oid sha256:3f1fa2ac91c3c5966db4e7cf34e8f4d724547d583147ac23bd392c536b0357bb
|
||||
size 105733
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f2aa8303cb959ebeae8a6b6bced04f2c3ecdfe429c97f153a38a2d650dddadf4
|
||||
size 99386
|
||||
oid sha256:a3de8520c297d0c53f500a5cbe1322d50ae1f76d7c4678ab7c7e1d243c6a8657
|
||||
size 106000
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e53aa446554e19bc47b31f59038ec3caeeaba8c4c15a1884f5e510f30eac4c61
|
||||
size 72867
|
||||
oid sha256:641c74d77ed0b43113fda06653a3a7eda92906a81a682cbd6b781aabcce51ab0
|
||||
size 76727
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:905d8d16cfce1540a91a3c8ef7d4295b844d2aab4c3ed3035230277acb56e228
|
||||
size 94357
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f918886d8da2987f6ae26762267557ce2f7d7f0b02ce985de56caa3fb7ee305c
|
||||
size 83101
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:08af67d397dafe03f60b36e2d1650a9031c12041ce5e015f6c2f81af4e9731fa
|
||||
size 88510
|
||||
Reference in New Issue
Block a user