@@ -124,7 +124,7 @@ - (void)testFailedAuthWithBiometrics {
124
124
void (^reply)(BOOL , NSError *);
125
125
[invocation getArgument: &reply atIndex: 4 ];
126
126
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 ]);
128
128
});
129
129
};
130
130
OCMStub ([mockAuthContext evaluatePolicy: policy localizedReason: reason reply: [OCMArg any ]])
@@ -140,14 +140,13 @@ - (void)testFailedAuthWithBiometrics {
140
140
[plugin handleMethodCall: call
141
141
result: ^(id _Nullable result) {
142
142
XCTAssertTrue ([NSThread isMainThread ]);
143
- XCTAssertTrue ([result isKindOfClass: [NSNumber class ]]);
144
- XCTAssertFalse ([result boolValue ]);
143
+ XCTAssertTrue ([result isKindOfClass: [FlutterError class ]]);
145
144
[expectation fulfill ];
146
145
}];
147
146
[self waitForExpectationsWithTimeout: kTimeout handler: nil ];
148
147
}
149
148
150
- - (void )testFailedAuthWithoutBiometrics {
149
+ - (void )testFailedWithUnknownErrorCode {
151
150
FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc ] init ];
152
151
id mockAuthContext = OCMClassMock ([LAContext class ]);
153
152
plugin.authContextOverrides = @[ mockAuthContext ];
@@ -175,6 +174,45 @@ - (void)testFailedAuthWithoutBiometrics {
175
174
@" localizedReason" : reason,
176
175
}];
177
176
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
+
178
216
XCTestExpectation *expectation = [self expectationWithDescription: @" Result is called" ];
179
217
[plugin handleMethodCall: call
180
218
result: ^(id _Nullable result) {
@@ -186,6 +224,44 @@ - (void)testFailedAuthWithoutBiometrics {
186
224
[self waitForExpectationsWithTimeout: kTimeout handler: nil ];
187
225
}
188
226
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
+
189
265
- (void )testLocalizedFallbackTitle {
190
266
FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc ] init ];
191
267
id mockAuthContext = OCMClassMock ([LAContext class ]);
@@ -203,7 +279,7 @@ - (void)testLocalizedFallbackTitle {
203
279
void (^reply)(BOOL , NSError *);
204
280
[invocation getArgument: &reply atIndex: 4 ];
205
281
dispatch_async (dispatch_get_global_queue (QOS_CLASS_BACKGROUND , 0 ), ^{
206
- reply (NO , [ NSError errorWithDomain: @" error " code: 99 userInfo: nil ] );
282
+ reply (YES , nil );
207
283
});
208
284
};
209
285
OCMStub ([mockAuthContext evaluatePolicy: policy localizedReason: reason reply: [OCMArg any ]])
@@ -220,10 +296,7 @@ - (void)testLocalizedFallbackTitle {
220
296
XCTestExpectation *expectation = [self expectationWithDescription: @" Result is called" ];
221
297
[plugin handleMethodCall: call
222
298
result: ^(id _Nullable result) {
223
- XCTAssertTrue ([NSThread isMainThread ]);
224
- XCTAssertTrue ([result isKindOfClass: [NSNumber class ]]);
225
299
OCMVerify ([mockAuthContext setLocalizedFallbackTitle: localizedFallbackTitle]);
226
- XCTAssertFalse ([result boolValue ]);
227
300
[expectation fulfill ];
228
301
}];
229
302
[self waitForExpectationsWithTimeout: kTimeout handler: nil ];
@@ -245,7 +318,7 @@ - (void)testSkippedLocalizedFallbackTitle {
245
318
void (^reply)(BOOL , NSError *);
246
319
[invocation getArgument: &reply atIndex: 4 ];
247
320
dispatch_async (dispatch_get_global_queue (QOS_CLASS_BACKGROUND , 0 ), ^{
248
- reply (NO , [ NSError errorWithDomain: @" error " code: 99 userInfo: nil ] );
321
+ reply (YES , nil );
249
322
});
250
323
};
251
324
OCMStub ([mockAuthContext evaluatePolicy: policy localizedReason: reason reply: [OCMArg any ]])
@@ -260,10 +333,7 @@ - (void)testSkippedLocalizedFallbackTitle {
260
333
XCTestExpectation *expectation = [self expectationWithDescription: @" Result is called" ];
261
334
[plugin handleMethodCall: call
262
335
result: ^(id _Nullable result) {
263
- XCTAssertTrue ([NSThread isMainThread ]);
264
- XCTAssertTrue ([result isKindOfClass: [NSNumber class ]]);
265
336
OCMVerify ([mockAuthContext setLocalizedFallbackTitle: nil ]);
266
- XCTAssertFalse ([result boolValue ]);
267
337
[expectation fulfill ];
268
338
}];
269
339
[self waitForExpectationsWithTimeout: kTimeout handler: nil ];
0 commit comments