Skip to content

Commit 684790a

Browse files
committed
fix failed biometric authentication not throwing error + tests
1 parent 345bddb commit 684790a

File tree

2 files changed

+124
-40
lines changed

2 files changed

+124
-40
lines changed

packages/local_auth/local_auth_ios/example/ios/RunnerTests/FLTLocalAuthPluginTests.m

Lines changed: 104 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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]])
@@ -136,17 +136,111 @@ - (void)testFailedAuthWithBiometrics {
136136
@"localizedReason" : reason,
137137
}];
138138

139+
XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"];
140+
[plugin handleMethodCall:call
141+
result:^(id _Nullable result) {
142+
XCTAssertTrue([NSThread isMainThread]);
143+
XCTAssertTrue([result isMemberOfClass:[FlutterError class]]);
144+
[expectation fulfill];
145+
}];
146+
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
147+
}
148+
149+
- (void)testFailedWithErrorCode {
150+
FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init];
151+
id mockAuthContext = OCMClassMock([LAContext class]);
152+
plugin.authContextOverrides = @[ mockAuthContext ];
153+
154+
FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"handleAuthReplyWithSuccess"
155+
arguments:@{
156+
@"success" : @NO,
157+
@"error" : [NSError errorWithDomain:@"error"
158+
code:LAErrorPasscodeNotSet
159+
userInfo:nil],
160+
@"stickyAuth" : @NO,
161+
}];
162+
163+
XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"];
164+
[plugin handleMethodCall:call
165+
result:^(id _Nullable result) {
166+
XCTAssertTrue([NSThread isMainThread]);
167+
XCTAssertTrue([result isMemberOfClass:[FlutterError class]]);
168+
[expectation fulfill];
169+
}];
170+
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
171+
}
172+
173+
- (void)testFailedWithUnknownErrorCode {
174+
FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init];
175+
id mockAuthContext = OCMClassMock([LAContext class]);
176+
plugin.authContextOverrides = @[ mockAuthContext ];
177+
178+
FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"handleAuthReplyWithSuccess"
179+
arguments:@{
180+
@"success" : @NO,
181+
@"error" : [NSError errorWithDomain:@"error"
182+
code:-9999
183+
userInfo:nil],
184+
@"stickyAuth" : @NO,
185+
}];
186+
187+
XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"];
188+
[plugin handleMethodCall:call
189+
result:^(id _Nullable result) {
190+
XCTAssertTrue([NSThread isMainThread]);
191+
XCTAssertTrue([result isMemberOfClass:[FlutterError class]]);
192+
[expectation fulfill];
193+
}];
194+
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
195+
}
196+
197+
- (void)testHandleAuthReplyFailedWithSystemCancel {
198+
FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init];
199+
id mockAuthContext = OCMClassMock([LAContext class]);
200+
plugin.authContextOverrides = @[ mockAuthContext ];
201+
202+
FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"handleAuthReplyWithSuccess"
203+
arguments:@{
204+
@"success" : @NO,
205+
@"error" : [NSError errorWithDomain:@"error"
206+
code:LAErrorSystemCancel
207+
userInfo:nil],
208+
@"stickyAuth" : @NO,
209+
}];
210+
211+
XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"];
212+
[plugin handleMethodCall:call
213+
result:^(id _Nullable result) {
214+
XCTAssertTrue([NSThread isMainThread]);
215+
XCTAssertFalse([result boolValue]);
216+
[expectation fulfill];
217+
}];
218+
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
219+
}
220+
221+
222+
- (void)testHandleAuthReplySucceeded {
223+
FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init];
224+
id mockAuthContext = OCMClassMock([LAContext class]);
225+
plugin.authContextOverrides = @[ mockAuthContext ];
226+
227+
FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"handleAuthReplyWithSuccess"
228+
arguments:@{
229+
@"success" : @YES
230+
}];
231+
139232
XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"];
140233
[plugin handleMethodCall:call
141234
result:^(id _Nullable result) {
142235
XCTAssertTrue([NSThread isMainThread]);
143236
XCTAssertTrue([result isKindOfClass:[NSNumber class]]);
144-
XCTAssertFalse([result boolValue]);
237+
XCTAssertTrue([result boolValue]);
145238
[expectation fulfill];
146239
}];
147240
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
148241
}
149242

243+
150244
- (void)testFailedAuthWithoutBiometrics {
151245
FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init];
152246
id mockAuthContext = OCMClassMock([LAContext class]);
@@ -163,7 +257,7 @@ - (void)testFailedAuthWithoutBiometrics {
163257
void (^reply)(BOOL, NSError *);
164258
[invocation getArgument:&reply atIndex:4];
165259
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
166-
reply(NO, [NSError errorWithDomain:@"error" code:99 userInfo:nil]);
260+
reply(NO, [NSError errorWithDomain:@"error" code:LAErrorAuthenticationFailed userInfo:nil]);
167261
});
168262
};
169263
OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]])
@@ -179,8 +273,7 @@ - (void)testFailedAuthWithoutBiometrics {
179273
[plugin handleMethodCall:call
180274
result:^(id _Nullable result) {
181275
XCTAssertTrue([NSThread isMainThread]);
182-
XCTAssertTrue([result isKindOfClass:[NSNumber class]]);
183-
XCTAssertFalse([result boolValue]);
276+
XCTAssertTrue([result isMemberOfClass:[FlutterError class]]);
184277
[expectation fulfill];
185278
}];
186279
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
@@ -203,7 +296,7 @@ - (void)testLocalizedFallbackTitle {
203296
void (^reply)(BOOL, NSError *);
204297
[invocation getArgument:&reply atIndex:4];
205298
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
206-
reply(NO, [NSError errorWithDomain:@"error" code:99 userInfo:nil]);
299+
reply(NO, [NSError errorWithDomain:@"error" code:LAErrorUserFallback userInfo:nil]);
207300
});
208301
};
209302
OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]])
@@ -220,10 +313,9 @@ - (void)testLocalizedFallbackTitle {
220313
XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"];
221314
[plugin handleMethodCall:call
222315
result:^(id _Nullable result) {
223-
XCTAssertTrue([NSThread isMainThread]);
224-
XCTAssertTrue([result isKindOfClass:[NSNumber class]]);
225316
OCMVerify([mockAuthContext setLocalizedFallbackTitle:localizedFallbackTitle]);
226-
XCTAssertFalse([result boolValue]);
317+
XCTAssertTrue([NSThread isMainThread]);
318+
XCTAssertTrue([result isMemberOfClass:[FlutterError class]]);
227319
[expectation fulfill];
228320
}];
229321
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
@@ -245,7 +337,7 @@ - (void)testSkippedLocalizedFallbackTitle {
245337
void (^reply)(BOOL, NSError *);
246338
[invocation getArgument:&reply atIndex:4];
247339
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
248-
reply(NO, [NSError errorWithDomain:@"error" code:99 userInfo:nil]);
340+
reply(NO, [NSError errorWithDomain:@"error" code:LAErrorUserFallback userInfo:nil]);
249341
});
250342
};
251343
OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]])
@@ -260,10 +352,9 @@ - (void)testSkippedLocalizedFallbackTitle {
260352
XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"];
261353
[plugin handleMethodCall:call
262354
result:^(id _Nullable result) {
263-
XCTAssertTrue([NSThread isMainThread]);
264-
XCTAssertTrue([result isKindOfClass:[NSNumber class]]);
265355
OCMVerify([mockAuthContext setLocalizedFallbackTitle:nil]);
266-
XCTAssertFalse([result boolValue]);
356+
XCTAssertTrue([NSThread isMainThread]);
357+
XCTAssertTrue([result isMemberOfClass:[FlutterError class]]);
267358
[expectation fulfill];
268359
}];
269360
[self waitForExpectationsWithTimeout:kTimeout handler:nil];

packages/local_auth/local_auth_ios/ios/Classes/FLTLocalAuthPlugin.m

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
4141
[self deviceSupportsBiometrics:result];
4242
} else if ([@"isDeviceSupported" isEqualToString:call.method]) {
4343
result(@YES);
44+
} else if ([@"handleAuthReplyWithSuccess" isEqualToString:call.method]) {
45+
bool success = [call.arguments[@"success"] boolValue];
46+
NSError* error = call.arguments[@"error"];
47+
[self handleAuthReplyWithSuccess:success error:error flutterArguments:call.arguments flutterResult:result];
4448
} else {
4549
result(FlutterMethodNotImplemented);
4650
}
@@ -216,41 +220,35 @@ - (void)handleAuthReplyWithSuccess:(BOOL)success
216220
result(@YES);
217221
} else {
218222
switch (error.code) {
223+
case LAErrorSystemCancel:
224+
if ([arguments[@"stickyAuth"] boolValue]) {
225+
self->_lastCallArgs = arguments;
226+
self->_lastResult = result;
227+
} else {
228+
result(@NO);
229+
}
230+
return;
219231
case LAErrorPasscodeNotSet:
220-
#pragma clang diagnostic push
221-
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
222-
// TODO(stuartmorgan): Remove the pragma and s/TouchID/Biometry/ in these constants when
223-
// iOS 10 support is dropped. The values are the same, only the names have changed.
224-
case LAErrorTouchIDNotAvailable:
225-
case LAErrorTouchIDNotEnrolled:
226-
case LAErrorTouchIDLockout:
227-
#pragma clang diagnostic pop
232+
case LAErrorAuthenticationFailed:
233+
case LAErrorBiometryNotAvailable:
234+
case LAErrorBiometryNotEnrolled:
235+
case LAErrorBiometryLockout:
228236
case LAErrorUserFallback:
237+
default:
229238
[self handleErrors:error flutterArguments:arguments withFlutterResult:result];
230239
return;
231-
case LAErrorSystemCancel:
232-
if ([arguments[@"stickyAuth"] boolValue]) {
233-
self->_lastCallArgs = arguments;
234-
self->_lastResult = result;
235-
return;
236-
}
237240
}
238-
result(@NO);
239241
}
240242
}
241243

244+
242245
- (void)handleErrors:(NSError *)authError
243246
flutterArguments:(NSDictionary *)arguments
244247
withFlutterResult:(FlutterResult)result {
245248
NSString *errorCode = @"NotAvailable";
246249
switch (authError.code) {
247250
case LAErrorPasscodeNotSet:
248-
#pragma clang diagnostic push
249-
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
250-
// TODO(stuartmorgan): Remove the pragma and s/TouchID/Biometry/ in this constant when
251-
// iOS 10 support is dropped. The values are the same, only the names have changed.
252-
case LAErrorTouchIDNotEnrolled:
253-
#pragma clang diagnostic pop
251+
case LAErrorBiometryNotEnrolled:
254252
if ([arguments[@"useErrorDialogs"] boolValue]) {
255253
[self alertMessage:arguments[@"goToSettingDescriptionIOS"]
256254
firstButton:arguments[@"okButton"]
@@ -260,12 +258,7 @@ - (void)handleErrors:(NSError *)authError
260258
}
261259
errorCode = authError.code == LAErrorPasscodeNotSet ? @"PasscodeNotSet" : @"NotEnrolled";
262260
break;
263-
#pragma clang diagnostic push
264-
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
265-
// TODO(stuartmorgan): Remove the pragma and s/TouchID/Biometry/ in this constant when
266-
// iOS 10 support is dropped. The values are the same, only the names have changed.
267-
case LAErrorTouchIDLockout:
268-
#pragma clang diagnostic pop
261+
case LAErrorBiometryLockout:
269262
[self alertMessage:arguments[@"lockOut"]
270263
firstButton:arguments[@"okButton"]
271264
flutterResult:result

0 commit comments

Comments
 (0)