This commit is contained in:
Mark Gregory
2026-11-04 15:16:11 +00:00
parent 110840cd38
commit 5bebd30041
13 changed files with 315 additions and 28 deletions
+46
View File
@@ -1,4 +1,6 @@
import Foundation
import SwiftUI
import LocalAuthentication
final class MockProtocol: URLProtocol, @unchecked Sendable {
static var handler: ((URLRequest) throws -> (Int, String))!
@@ -152,6 +154,50 @@ final class MockProtocol: URLProtocol, @unchecked Sendable {
disconnected.disconnect()
await disconnected.restoreSessionIfNeeded()
check(disconnected.error == nil && !disconnected.connected, "Explicit disconnect suppresses automatic login for this launch")
let protection = AppModel()
protection.locked = true; protection.biometric = true
var authCalls = 0
protection.authenticate = { _ in authCalls += 1; return true }
await protection.updateScene(.active)
check(!protection.locked && authCalls == 1, "Face ID starts automatically on opening the locked app")
await protection.updateScene(.inactive)
await protection.updateScene(.active)
check(authCalls == 1, "Authentication inactive/active transitions do not prompt again")
await protection.updateScene(.background)
check(protection.locked, "Background locks protected app")
protection.authenticate = { _ in authCalls += 1; throw LAError(.userCancel) }
await protection.updateScene(.active)
check(protection.locked && protection.error == nil, "Cancelled Face ID stays locked without an alert")
await protection.updateScene(.inactive)
await protection.updateScene(.active)
check(authCalls == 2, "Cancelled authentication does not automatically loop")
protection.authenticate = { _ in authCalls += 1; return true }
await protection.unlock()
check(!protection.locked && authCalls == 3, "Manual unlock remains available after cancellation")
await protection.updateScene(.background)
await protection.updateScene(.active)
check(!protection.locked && authCalls == 4, "Returning from background automatically authenticates again")
await protection.updateScene(.background)
protection.authenticate = { _ in
authCalls += 1
await protection.unlock()
return true
}
await protection.updateScene(.active)
check(authCalls == 5 && !protection.authenticating, "Overlapping unlock attempts share one authentication prompt")
await protection.updateScene(.background)
protection.authenticate = { _ in
await protection.updateScene(.background)
return true
}
await protection.updateScene(.active)
check(protection.locked, "An old authentication result cannot unlock after backgrounding")
protection.authenticate = { _ in
await protection.updateScene(.inactive)
return true
}
await protection.updateScene(.active)
check(!protection.locked, "Face ID success while system prompt is inactive unlocks the app")
another.close(); transport.close(); print("\(checks) contract checks passed.")
}
}