@@ -124,7 +124,7 @@ - (void)testFailedAuthWithBiometrics {
124124 void (^reply)(BOOL , NSError *);
125125 [invocation getArgument: &reply atIndex: 4 ];
126126 dispatch_async (dispatch_get_global_queue (QOS_CLASS_BACKGROUND, 0 ), ^{
127- reply (NO , [NSError errorWithDomain: @" error" code: 99 userInfo: nil ]);
127+ reply (NO , [NSError errorWithDomain: @" error" code: LAErrorAuthenticationFailed userInfo: nil ]);
128128 });
129129 };
130130 OCMStub ([mockAuthContext evaluatePolicy: policy localizedReason: reason reply: [OCMArg any ]])
@@ -140,14 +140,13 @@ - (void)testFailedAuthWithBiometrics {
140140 [plugin handleMethodCall: call
141141 result: ^(id _Nullable result) {
142142 XCTAssertTrue ([NSThread isMainThread ]);
143- XCTAssertTrue ([result isKindOfClass: [NSNumber class ]]);
144- XCTAssertFalse ([result boolValue ]);
143+ XCTAssertTrue ([result isKindOfClass: [FlutterError class ]]);
145144 [expectation fulfill ];
146145 }];
147146 [self waitForExpectationsWithTimeout: kTimeout handler: nil ];
148147}
149148
150- - (void )testFailedAuthWithoutBiometrics {
149+ - (void )testFailedWithUnknownErrorCode {
151150 FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc ] init ];
152151 id mockAuthContext = OCMClassMock ([LAContext class ]);
153152 plugin.authContextOverrides = @[ mockAuthContext ];
@@ -175,6 +174,45 @@ - (void)testFailedAuthWithoutBiometrics {
175174 @" localizedReason" : reason,
176175 }];
177176
177+ XCTestExpectation *expectation = [self expectationWithDescription: @" Result is called" ];
178+ [plugin handleMethodCall: call
179+ result: ^(id _Nullable result) {
180+ XCTAssertTrue ([NSThread isMainThread ]);
181+ XCTAssertTrue ([result isKindOfClass: [FlutterError class ]]);
182+ [expectation fulfill ];
183+ }];
184+ [self waitForExpectationsWithTimeout: kTimeout handler: nil ];
185+ }
186+
187+ - (void )testSystemCancelledWithoutStickyAuth {
188+ FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc ] init ];
189+ id mockAuthContext = OCMClassMock ([LAContext class ]);
190+ plugin.authContextOverrides = @[ mockAuthContext ];
191+
192+ const LAPolicy policy = LAPolicyDeviceOwnerAuthentication;
193+ NSString *reason = @" a reason" ;
194+ OCMStub ([mockAuthContext canEvaluatePolicy: policy error: [OCMArg setTo: nil ]]).andReturn (YES );
195+
196+ // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not
197+ // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on
198+ // a background thread.
199+ void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) {
200+ void (^reply)(BOOL , NSError *);
201+ [invocation getArgument: &reply atIndex: 4 ];
202+ dispatch_async (dispatch_get_global_queue (QOS_CLASS_BACKGROUND, 0 ), ^{
203+ reply (NO , [NSError errorWithDomain: @" error" code: LAErrorSystemCancel userInfo: nil ]);
204+ });
205+ };
206+ OCMStub ([mockAuthContext evaluatePolicy: policy localizedReason: reason reply: [OCMArg any ]])
207+ .andDo (backgroundThreadReplyCaller);
208+
209+ FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName: @" authenticate"
210+ arguments: @{
211+ @" biometricOnly" : @(NO ),
212+ @" localizedReason" : reason,
213+ @" stickyAuth" : @(NO )
214+ }];
215+
178216 XCTestExpectation *expectation = [self expectationWithDescription: @" Result is called" ];
179217 [plugin handleMethodCall: call
180218 result: ^(id _Nullable result) {
@@ -186,6 +224,44 @@ - (void)testFailedAuthWithoutBiometrics {
186224 [self waitForExpectationsWithTimeout: kTimeout handler: nil ];
187225}
188226
227+ - (void )testFailedAuthWithoutBiometrics {
228+ FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc ] init ];
229+ id mockAuthContext = OCMClassMock ([LAContext class ]);
230+ plugin.authContextOverrides = @[ mockAuthContext ];
231+
232+ const LAPolicy policy = LAPolicyDeviceOwnerAuthentication;
233+ NSString *reason = @" a reason" ;
234+ OCMStub ([mockAuthContext canEvaluatePolicy: policy error: [OCMArg setTo: nil ]]).andReturn (YES );
235+
236+ // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not
237+ // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on
238+ // a background thread.
239+ void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) {
240+ void (^reply)(BOOL , NSError *);
241+ [invocation getArgument: &reply atIndex: 4 ];
242+ dispatch_async (dispatch_get_global_queue (QOS_CLASS_BACKGROUND, 0 ), ^{
243+ reply (NO , [NSError errorWithDomain: @" error" code: LAErrorAuthenticationFailed userInfo: nil ]);
244+ });
245+ };
246+ OCMStub ([mockAuthContext evaluatePolicy: policy localizedReason: reason reply: [OCMArg any ]])
247+ .andDo (backgroundThreadReplyCaller);
248+
249+ FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName: @" authenticate"
250+ arguments: @{
251+ @" biometricOnly" : @(NO ),
252+ @" localizedReason" : reason,
253+ }];
254+
255+ XCTestExpectation *expectation = [self expectationWithDescription: @" Result is called" ];
256+ [plugin handleMethodCall: call
257+ result: ^(id _Nullable result) {
258+ XCTAssertTrue ([NSThread isMainThread ]);
259+ XCTAssertTrue ([result isKindOfClass: [FlutterError class ]]);
260+ [expectation fulfill ];
261+ }];
262+ [self waitForExpectationsWithTimeout: kTimeout handler: nil ];
263+ }
264+
189265- (void )testLocalizedFallbackTitle {
190266 FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc ] init ];
191267 id mockAuthContext = OCMClassMock ([LAContext class ]);
@@ -203,7 +279,7 @@ - (void)testLocalizedFallbackTitle {
203279 void (^reply)(BOOL , NSError *);
204280 [invocation getArgument: &reply atIndex: 4 ];
205281 dispatch_async (dispatch_get_global_queue (QOS_CLASS_BACKGROUND, 0 ), ^{
206- reply (NO , [ NSError errorWithDomain: @" error " code: 99 userInfo: nil ] );
282+ reply (YES , nil );
207283 });
208284 };
209285 OCMStub ([mockAuthContext evaluatePolicy: policy localizedReason: reason reply: [OCMArg any ]])
@@ -220,10 +296,7 @@ - (void)testLocalizedFallbackTitle {
220296 XCTestExpectation *expectation = [self expectationWithDescription: @" Result is called" ];
221297 [plugin handleMethodCall: call
222298 result: ^(id _Nullable result) {
223- XCTAssertTrue ([NSThread isMainThread ]);
224- XCTAssertTrue ([result isKindOfClass: [NSNumber class ]]);
225299 OCMVerify ([mockAuthContext setLocalizedFallbackTitle: localizedFallbackTitle]);
226- XCTAssertFalse ([result boolValue ]);
227300 [expectation fulfill ];
228301 }];
229302 [self waitForExpectationsWithTimeout: kTimeout handler: nil ];
@@ -245,7 +318,7 @@ - (void)testSkippedLocalizedFallbackTitle {
245318 void (^reply)(BOOL , NSError *);
246319 [invocation getArgument: &reply atIndex: 4 ];
247320 dispatch_async (dispatch_get_global_queue (QOS_CLASS_BACKGROUND, 0 ), ^{
248- reply (NO , [ NSError errorWithDomain: @" error " code: 99 userInfo: nil ] );
321+ reply (YES , nil );
249322 });
250323 };
251324 OCMStub ([mockAuthContext evaluatePolicy: policy localizedReason: reason reply: [OCMArg any ]])
@@ -260,10 +333,7 @@ - (void)testSkippedLocalizedFallbackTitle {
260333 XCTestExpectation *expectation = [self expectationWithDescription: @" Result is called" ];
261334 [plugin handleMethodCall: call
262335 result: ^(id _Nullable result) {
263- XCTAssertTrue ([NSThread isMainThread ]);
264- XCTAssertTrue ([result isKindOfClass: [NSNumber class ]]);
265336 OCMVerify ([mockAuthContext setLocalizedFallbackTitle: nil ]);
266- XCTAssertFalse ([result boolValue ]);
267337 [expectation fulfill ];
268338 }];
269339 [self waitForExpectationsWithTimeout: kTimeout handler: nil ];
0 commit comments