22// Use of this source code is governed by a BSD-style license that can be
33// found in the LICENSE file.
44
5- import Flutter
65import XCTest
76
87@testable import local_auth_darwin
98
9+ #if os(iOS)
10+ import Flutter
11+ #else
12+ import FlutterMacOS
13+ #endif
14+
1015// Set a long timeout to avoid flake due to slow CI.
1116private let timeout : TimeInterval = 30.0
1217
1318/// A context factory that returns preset contexts.
1419final class StubAuthContextFactory : NSObject , FLADAuthContextFactory {
20+
1521 var contexts : [ FLADAuthContext ]
1622 init ( contexts: [ FLADAuthContext ] ) {
1723 self . contexts = contexts
@@ -23,6 +29,74 @@ final class StubAuthContextFactory: NSObject, FLADAuthContextFactory {
2329 }
2430}
2531
32+ final class StubViewProvider : NSObject , FLAViewProvider {
33+ #if os(macOS)
34+ var view : NSView
35+ var window : NSWindow
36+ override init ( ) {
37+ self . window = NSWindow ( )
38+ self . view = NSView ( )
39+ self . window. contentView = self . view
40+ }
41+ #endif
42+ }
43+
44+ #if os(macOS)
45+ final class TestAlert : NSObject , FLANSAlert {
46+ var messageText : String = " "
47+ var buttons : [ String ] = [ ]
48+ var presentingWindow : NSWindow ?
49+
50+ func addButton( withTitle title: String ) -> NSButton {
51+ buttons. append ( title)
52+ return NSButton ( ) // The return value is not used by the plugin.
53+ }
54+
55+ func beginSheetModal( for sheetWindow: NSWindow ) async -> NSApplication . ModalResponse {
56+ presentingWindow = sheetWindow
57+ return NSApplication . ModalResponse. OK
58+ }
59+ }
60+ #else
61+ final class TestAlertController : NSObject , FLAUIAlertController {
62+ var actions : [ UIAlertAction ] = [ ]
63+ var presented = false
64+ var presentingViewController : UIViewController ?
65+
66+ func add( _ action: UIAlertAction ) {
67+ actions. append ( action)
68+ }
69+
70+ func present(
71+ on presentingViewController: UIViewController , animated flag: Bool ,
72+ completion: ( ( ) -> Void ) ? = nil
73+ ) {
74+ presented = true
75+ self . presentingViewController = presentingViewController
76+ }
77+ }
78+ #endif
79+
80+ final class StubAlertFactory : NSObject , FLADAlertFactory {
81+ #if os(macOS)
82+ var alert : TestAlert = TestAlert ( )
83+ #else
84+ var alertController : TestAlertController = TestAlertController ( )
85+ #endif
86+
87+ #if os(macOS)
88+ func createAlert( ) -> FLANSAlert {
89+ return self . alert
90+ }
91+ #else
92+ func createAlertController(
93+ withTitle title: String ? , message: String ? , preferredStyle: UIAlertController . Style
94+ ) -> FLAUIAlertController {
95+ return self . alertController
96+ }
97+ #endif
98+ }
99+
26100final class StubAuthContext : NSObject , FLADAuthContext {
27101 /// Whether calls to this stub are expected to be for biometric authentication.
28102 ///
@@ -75,8 +149,12 @@ class FLALocalAuthPluginTests: XCTestCase {
75149
76150 func testSuccessfullAuthWithBiometrics( ) throws {
77151 let stubAuthContext = StubAuthContext ( )
152+ let alertFactory = StubAlertFactory ( )
153+ let viewProvider = StubViewProvider ( )
78154 let plugin = FLALocalAuthPlugin (
79- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
155+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
156+ alertFactory: alertFactory, viewProvider: viewProvider
157+ )
80158
81159 let strings = createAuthStrings ( )
82160 stubAuthContext. expectBiometrics = true
@@ -99,8 +177,12 @@ class FLALocalAuthPluginTests: XCTestCase {
99177
100178 func testSuccessfullAuthWithoutBiometrics( ) {
101179 let stubAuthContext = StubAuthContext ( )
180+ let alertFactory = StubAlertFactory ( )
181+ let viewProvider = StubViewProvider ( )
182+
102183 let plugin = FLALocalAuthPlugin (
103- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
184+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
185+ alertFactory: alertFactory, viewProvider: viewProvider)
104186
105187 let strings = createAuthStrings ( )
106188 stubAuthContext. evaluateResponse = true
@@ -123,8 +205,11 @@ class FLALocalAuthPluginTests: XCTestCase {
123205
124206 func testFailedAuthWithBiometrics( ) {
125207 let stubAuthContext = StubAuthContext ( )
208+ let alertFactory = StubAlertFactory ( )
209+ let viewProvider = StubViewProvider ( )
126210 let plugin = FLALocalAuthPlugin (
127- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
211+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
212+ alertFactory: alertFactory, viewProvider: viewProvider)
128213
129214 let strings = createAuthStrings ( )
130215 stubAuthContext. expectBiometrics = true
@@ -153,8 +238,11 @@ class FLALocalAuthPluginTests: XCTestCase {
153238
154239 func testFailedWithUnknownErrorCode( ) {
155240 let stubAuthContext = StubAuthContext ( )
241+ let alertFactory = StubAlertFactory ( )
242+ let viewProvider = StubViewProvider ( )
156243 let plugin = FLALocalAuthPlugin (
157- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
244+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
245+ alertFactory: alertFactory, viewProvider: viewProvider)
158246
159247 let strings = createAuthStrings ( )
160248 stubAuthContext. evaluateError = NSError ( domain: " error " , code: 99 )
@@ -177,8 +265,11 @@ class FLALocalAuthPluginTests: XCTestCase {
177265
178266 func testSystemCancelledWithoutStickyAuth( ) {
179267 let stubAuthContext = StubAuthContext ( )
268+ let alertFactory = StubAlertFactory ( )
269+ let viewProvider = StubViewProvider ( )
180270 let plugin = FLALocalAuthPlugin (
181- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
271+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
272+ alertFactory: alertFactory, viewProvider: viewProvider)
182273
183274 let strings = createAuthStrings ( )
184275 stubAuthContext. evaluateError = NSError ( domain: " error " , code: LAError . systemCancel. rawValue)
@@ -201,8 +292,11 @@ class FLALocalAuthPluginTests: XCTestCase {
201292
202293 func testFailedAuthWithoutBiometrics( ) {
203294 let stubAuthContext = StubAuthContext ( )
295+ let alertFactory = StubAlertFactory ( )
296+ let viewProvider = StubViewProvider ( )
204297 let plugin = FLALocalAuthPlugin (
205- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
298+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
299+ alertFactory: alertFactory, viewProvider: viewProvider)
206300
207301 let strings = createAuthStrings ( )
208302 stubAuthContext. evaluateError = NSError (
@@ -228,10 +322,50 @@ class FLALocalAuthPluginTests: XCTestCase {
228322 self . waitForExpectations ( timeout: timeout)
229323 }
230324
325+ func testFailedAuthShowsAlert( ) {
326+ let stubAuthContext = StubAuthContext ( )
327+ let alertFactory = StubAlertFactory ( )
328+ let viewProvider = StubViewProvider ( )
329+ let plugin = FLALocalAuthPlugin (
330+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
331+ alertFactory: alertFactory, viewProvider: viewProvider)
332+
333+ let strings = createAuthStrings ( )
334+ stubAuthContext. canEvaluateError = NSError (
335+ domain: " error " , code: LAError . biometryNotEnrolled. rawValue)
336+
337+ #if os(macOS)
338+ let expectation = expectation ( description: " Result is called " )
339+ #endif
340+ plugin. authenticate (
341+ with: FLADAuthOptions . make (
342+ withBiometricOnly: false ,
343+ sticky: false ,
344+ useErrorDialogs: true ) ,
345+ strings: strings
346+ ) { resultDetails, error in
347+ // TODO(stuartmorgan): Add a wrapper around UIAction to allow accessing the handler, so
348+ // that the test can trigger the callback on iOS as well, and then unfork this.
349+ #if os(macOS)
350+ expectation. fulfill ( )
351+ #endif
352+ }
353+ #if os(macOS)
354+ self . waitForExpectations ( timeout: timeout)
355+ XCTAssertEqual ( alertFactory. alert. presentingWindow, viewProvider. view. window)
356+ #else
357+ XCTAssertTrue ( alertFactory. alertController. presented)
358+ XCTAssertEqual ( alertFactory. alertController. actions. count, 2 )
359+ #endif
360+ }
361+
231362 func testLocalizedFallbackTitle( ) {
232363 let stubAuthContext = StubAuthContext ( )
364+ let alertFactory = StubAlertFactory ( )
365+ let viewProvider = StubViewProvider ( )
233366 let plugin = FLALocalAuthPlugin (
234- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
367+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
368+ alertFactory: alertFactory, viewProvider: viewProvider)
235369
236370 let strings = createAuthStrings ( )
237371 strings. localizedFallbackTitle = " a title "
@@ -255,8 +389,11 @@ class FLALocalAuthPluginTests: XCTestCase {
255389
256390 func testSkippedLocalizedFallbackTitle( ) {
257391 let stubAuthContext = StubAuthContext ( )
392+ let alertFactory = StubAlertFactory ( )
393+ let viewProvider = StubViewProvider ( )
258394 let plugin = FLALocalAuthPlugin (
259- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
395+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
396+ alertFactory: alertFactory, viewProvider: viewProvider)
260397
261398 let strings = createAuthStrings ( )
262399 strings. localizedFallbackTitle = nil
@@ -278,8 +415,11 @@ class FLALocalAuthPluginTests: XCTestCase {
278415
279416 func testDeviceSupportsBiometrics_withEnrolledHardware( ) {
280417 let stubAuthContext = StubAuthContext ( )
418+ let alertFactory = StubAlertFactory ( )
419+ let viewProvider = StubViewProvider ( )
281420 let plugin = FLALocalAuthPlugin (
282- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
421+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
422+ alertFactory: alertFactory, viewProvider: viewProvider)
283423
284424 stubAuthContext. expectBiometrics = true
285425
@@ -291,8 +431,11 @@ class FLALocalAuthPluginTests: XCTestCase {
291431
292432 func testDeviceSupportsBiometrics_withNonEnrolledHardware( ) {
293433 let stubAuthContext = StubAuthContext ( )
434+ let alertFactory = StubAlertFactory ( )
435+ let viewProvider = StubViewProvider ( )
294436 let plugin = FLALocalAuthPlugin (
295- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
437+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
438+ alertFactory: alertFactory, viewProvider: viewProvider)
296439
297440 stubAuthContext. expectBiometrics = true
298441 stubAuthContext. canEvaluateError = NSError (
@@ -306,8 +449,11 @@ class FLALocalAuthPluginTests: XCTestCase {
306449
307450 func testDeviceSupportsBiometrics_withNoBiometricHardware( ) {
308451 let stubAuthContext = StubAuthContext ( )
452+ let alertFactory = StubAlertFactory ( )
453+ let viewProvider = StubViewProvider ( )
309454 let plugin = FLALocalAuthPlugin (
310- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
455+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
456+ alertFactory: alertFactory, viewProvider: viewProvider)
311457
312458 stubAuthContext. expectBiometrics = true
313459 stubAuthContext. canEvaluateError = NSError ( domain: " error " , code: 0 )
@@ -320,11 +466,17 @@ class FLALocalAuthPluginTests: XCTestCase {
320466
321467 func testGetEnrolledBiometricsWithFaceID( ) {
322468 let stubAuthContext = StubAuthContext ( )
469+ let alertFactory = StubAlertFactory ( )
470+ let viewProvider = StubViewProvider ( )
323471 let plugin = FLALocalAuthPlugin (
324- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
472+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
473+ alertFactory: alertFactory, viewProvider: viewProvider)
325474
326475 stubAuthContext. expectBiometrics = true
327- stubAuthContext. biometryType = . faceID
476+ if #available( iOS 11 , macOS 10 . 15 , * ) {
477+ stubAuthContext. biometryType = . faceID
478+
479+ }
328480
329481 var error : FlutterError ?
330482 let result = plugin. getEnrolledBiometricsWithError ( & error)
@@ -335,8 +487,11 @@ class FLALocalAuthPluginTests: XCTestCase {
335487
336488 func testGetEnrolledBiometricsWithTouchID( ) {
337489 let stubAuthContext = StubAuthContext ( )
490+ let alertFactory = StubAlertFactory ( )
491+ let viewProvider = StubViewProvider ( )
338492 let plugin = FLALocalAuthPlugin (
339- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
493+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
494+ alertFactory: alertFactory, viewProvider: viewProvider)
340495
341496 stubAuthContext. expectBiometrics = true
342497 stubAuthContext. biometryType = . touchID
@@ -350,8 +505,11 @@ class FLALocalAuthPluginTests: XCTestCase {
350505
351506 func testGetEnrolledBiometricsWithoutEnrolledHardware( ) {
352507 let stubAuthContext = StubAuthContext ( )
508+ let alertFactory = StubAlertFactory ( )
509+ let viewProvider = StubViewProvider ( )
353510 let plugin = FLALocalAuthPlugin (
354- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
511+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
512+ alertFactory: alertFactory, viewProvider: viewProvider)
355513
356514 stubAuthContext. expectBiometrics = true
357515 stubAuthContext. canEvaluateError = NSError (
@@ -365,8 +523,11 @@ class FLALocalAuthPluginTests: XCTestCase {
365523
366524 func testIsDeviceSupportedHandlesSupported( ) {
367525 let stubAuthContext = StubAuthContext ( )
526+ let alertFactory = StubAlertFactory ( )
527+ let viewProvider = StubViewProvider ( )
368528 let plugin = FLALocalAuthPlugin (
369- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
529+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
530+ alertFactory: alertFactory, viewProvider: viewProvider)
370531
371532 var error : FlutterError ?
372533 let result = plugin. isDeviceSupportedWithError ( & error)
@@ -378,8 +539,11 @@ class FLALocalAuthPluginTests: XCTestCase {
378539 let stubAuthContext = StubAuthContext ( )
379540 // An arbitrary error to cause canEvaluatePolicy to return false.
380541 stubAuthContext. canEvaluateError = NSError ( domain: " error " , code: 1 )
542+ let alertFactory = StubAlertFactory ( )
543+ let viewProvider = StubViewProvider ( )
381544 let plugin = FLALocalAuthPlugin (
382- contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) )
545+ contextFactory: StubAuthContextFactory ( contexts: [ stubAuthContext] ) ,
546+ alertFactory: alertFactory, viewProvider: viewProvider)
383547
384548 var error : FlutterError ?
385549 let result = plugin. isDeviceSupportedWithError ( & error)
0 commit comments