Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(191)

Side by Side Diff: ios/chrome/browser/passwords/credential_manager_js_unittest.mm

Issue 2152593002: Upstream password manager unit tests (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix GN Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <memory>
6
7 #include "base/mac/foundation_util.h"
8 #include "base/mac/scoped_nsobject.h"
Eugene But (OOO till 7-30) 2016/07/13 22:30:31 s/include/import
vabr (Chromium) 2016/07/14 07:36:31 Done.
9 #include "base/strings/utf_string_conversions.h"
10 #include "base/values.h"
11 #import "ios/chrome/browser/passwords/js_credential_manager.h"
12 #include "ios/web/public/web_state/credential.h"
13 #import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
14 #import "ios/web/public/web_state/web_state.h"
15 #include "ios/web/public/web_state/web_state_observer.h"
16 #import "ios/web/public/test/web_test_with_web_state.h"
17 #include "testing/gmock/include/gmock/gmock.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 #include "testing/gtest_mac.h"
20 #include "url/gurl.h"
21
22 namespace {
23
24 using ::testing::_;
25
26 // Matcher to match web::Credential.
27 MATCHER_P(IsEqualTo, value, "") {
28 return arg.type == value.type && arg.id == value.id &&
29 arg.name == value.name && arg.avatar_url == value.avatar_url &&
30 arg.password == value.password &&
31 arg.federation_origin.Serialize() ==
32 value.federation_origin.Serialize();
33 }
34
35 // A mock WebStateObserver for testing the Credential Manager API.
36 class MockWebStateObserver : public web::WebStateObserver {
37 public:
38 explicit MockWebStateObserver(web::WebState* web_state)
39 : web::WebStateObserver(web_state) {}
40 ~MockWebStateObserver() override {}
41
42 MOCK_METHOD5(
43 CredentialsRequested,
44 void(int, const GURL&, bool, const std::vector<std::string>&, bool));
45 MOCK_METHOD3(SignedIn, void(int, const GURL&, const web::Credential&));
46 MOCK_METHOD2(SignedIn, void(int, const GURL&));
47 MOCK_METHOD2(SignedOut, void(int, const GURL&));
48 MOCK_METHOD3(SignInFailed, void(int, const GURL&, const web::Credential&));
49 MOCK_METHOD2(SignInFailed, void(int, const GURL&));
50
51 private:
52 DISALLOW_COPY_AND_ASSIGN(MockWebStateObserver);
53 };
54
55 // Unit tests for the Credential Manager JavaScript and associated plumbing.
56 class CredentialManagerJsTest : public web::WebTestWithWebState {
57 public:
58 CredentialManagerJsTest() {}
59
60 void SetUp() override {
61 web::WebTestWithWebState::SetUp();
62 js_credential_manager_.reset(base::mac::ObjCCastStrict<JSCredentialManager>(
63 [[web_state()->GetJSInjectionReceiver()
64 instanceOfClass:[JSCredentialManager class]] retain]));
65 observer_.reset(new MockWebStateObserver(web_state()));
66 }
67
68 // Sets up a web page and injects the JSCredentialManager. Must be called
69 // before any interaction with the page.
70 void Inject() {
71 LoadHtml(@"");
72 [js_credential_manager_ inject];
73 }
74
75 // Returns the mock observer.
76 MockWebStateObserver& observer() { return *observer_; }
77
78 // Returns a string that creates a Credential object for JavaScript testing.
79 NSString* test_credential_js() {
80 return @"new PasswordCredential('bob', 'bobiscool', 'Bob Boblaw',"
81 @"'https://bobboblawslawblog.com/bob.jpg')";
82 }
83
84 // Returns a Credential to match the one returned by |test_credential_js()|.
85 web::Credential test_credential() {
86 web::Credential test_credential;
87 test_credential.type = web::CredentialType::CREDENTIAL_TYPE_PASSWORD;
88 test_credential.id = base::ASCIIToUTF16("bob");
89 test_credential.password = base::ASCIIToUTF16("bobiscool");
90 test_credential.name = base::ASCIIToUTF16("Bob Boblaw");
91 test_credential.avatar_url = GURL("https://bobboblawslawblog.com/bob.jpg");
92 return test_credential;
93 }
94
95 // Adds handlers for resolving and rejecting the promise returned by
96 // executing the code in |promise|.
97 void PrepareResolverAndRejecter(NSString* promise) {
98 EvaluateJavaScriptAsString(
99 [NSString stringWithFormat:@"var resolved = false; "
100 @"var rejected = false; "
101 @"var resolvedCredential = null; "
102 @"var rejectedError = null; "
103 @"function resolve(credential) { "
104 @" resolved = true; "
105 @" resolvedCredential = credential;"
106 @"} "
107 @"function reject(error) { "
108 @" rejected = true; "
109 @" rejectedError = error; "
110 @"} "
111 @"%@.then(resolve, reject); ",
112 promise]);
113 // Wait until the promise executor has executed.
114 WaitForCondition(^bool {
115 return [EvaluateJavaScriptAsString(
116 @"Object.keys(__gCrWeb.credentialManager.resolvers_).length > 0")
117 isEqualToString:@"true"];
118 });
119 }
120
121 // Checks that the Credential returned to the resolve handler in JavaScript
122 // matches the structure of |test_credential()|.
123 void CheckResolvedCredentialMatchesTestCredential() {
124 EXPECT_NSEQ(@"true", EvaluateJavaScriptAsString(@"resolved"));
125 EXPECT_NSEQ(
126 @"PasswordCredential",
127 EvaluateJavaScriptAsString(@"resolvedCredential.constructor.name"));
128 EXPECT_NSEQ(@"bob", EvaluateJavaScriptAsString(@"resolvedCredential.id"));
129 EXPECT_NSEQ(@"bobiscool",
130 EvaluateJavaScriptAsString(@"resolvedCredential.password_"));
131 EXPECT_NSEQ(@"Bob Boblaw",
132 EvaluateJavaScriptAsString(@"resolvedCredential.name"));
133 EXPECT_NSEQ(@"https://bobboblawslawblog.com/bob.jpg",
134 EvaluateJavaScriptAsString(@"resolvedCredential.avatarURL"));
135 }
136
137 // Checks that the promise set up by |PrepareResolverAndRejecter| was resolved
138 // without a credential.
139 void CheckResolvedWithoutCredential() {
140 EXPECT_NSEQ(@"true", EvaluateJavaScriptAsString(@"resolved"));
141 EXPECT_NSEQ(@"false", EvaluateJavaScriptAsString(@"!!resolvedCredential"));
142 }
143
144 // Checks that the promise set up by |PrepareResolverAndRejecter| was rejected
145 // with an error with name |error_name| and |message|.
146 void CheckRejected(NSString* error_name, NSString* message) {
147 EXPECT_NSEQ(@"true", EvaluateJavaScriptAsString(@"rejected"));
148 EXPECT_NSEQ(error_name, EvaluateJavaScriptAsString(@"rejectedError.name"));
149 EXPECT_NSEQ(message, EvaluateJavaScriptAsString(@"rejectedError.message"));
150 }
151
152 // Waits until the promise set up by |PrepareResolverAndRejecter| has been
153 // either resolved or rejected.
154 void WaitUntilPromiseResolvedOrRejected() {
155 WaitForCondition(^bool {
156 return [EvaluateJavaScriptAsString(@"resolved || rejected")
157 isEqualToString:@"true"];
158 });
159 }
160
161 // Resolves the promise set up by |PrepareResolverAndRejecter| and associated
162 // with |request_id| with |test_credential()|.
163 void ResolvePromiseWithTestCredential(int request_id) {
164 __block bool finished = false;
165 [js_credential_manager() resolvePromiseWithRequestID:request_id
166 credential:test_credential()
167 completionHandler:^(BOOL success) {
168 EXPECT_TRUE(success);
169 finished = true;
170 }];
171 WaitForCondition(^bool {
172 return finished;
173 });
174 WaitUntilPromiseResolvedOrRejected();
175 }
176
177 // Resolves the promise set up by |PrepareResolverAndRejecter| and associated
178 // with |request_id| without a credential.
179 void ResolvePromiseWithoutCredential(int request_id) {
180 __block bool finished = false;
181 [js_credential_manager() resolvePromiseWithRequestID:request_id
182 completionHandler:^(BOOL success) {
183 EXPECT_TRUE(success);
184 finished = true;
185 }];
186 WaitForCondition(^bool {
187 return finished;
188 });
189 WaitUntilPromiseResolvedOrRejected();
190 }
191
192 // Rejects the promise set up by |PrepareResolverAndRejecter| and associated
193 // with |request_id| with an error of type |error_type| and |message|.
194 void RejectPromise(int request_id, NSString* error_type, NSString* message) {
195 __block bool finished = false;
196 [js_credential_manager() rejectPromiseWithRequestID:request_id
197 errorType:error_type
198 message:message
199 completionHandler:^(BOOL success) {
200 EXPECT_TRUE(success);
201 finished = true;
202 }];
203 WaitForCondition(^bool {
204 return finished;
205 });
206 WaitUntilPromiseResolvedOrRejected();
207 }
208
209 // Tests that the promise set up by |PrepareResolverAndRejecter| wasn't
210 // rejected.
211 void CheckNeverRejected() {
212 EXPECT_NSEQ(@"false", EvaluateJavaScriptAsString(@"rejected"));
213 }
214
215 // Tests that the promise set up by |PrepareResolverAndRejecter| wasn't
216 // resolved.
217 void CheckNeverResolved() {
218 EXPECT_NSEQ(@"false", EvaluateJavaScriptAsString(@"resolved"));
219 }
220
221 // Returns the JSCredentialManager for testing.
222 JSCredentialManager* js_credential_manager() {
223 return js_credential_manager_;
224 }
225
226 // Tests that resolving the promise returned by |promise| and associated with
227 // |request_id| with |test_credential()| correctly forwards that credential
228 // to the client.
229 void TestPromiseResolutionWithCredential(int request_id, NSString* promise) {
230 PrepareResolverAndRejecter(promise);
231 ResolvePromiseWithTestCredential(request_id);
232 CheckResolvedCredentialMatchesTestCredential();
233 CheckNeverRejected();
234 }
235
236 // Tests that resolving the promise returned by |promise| and associated with
237 // |request_id| without a credential correctly invokes the client.
238 void TestPromiseResolutionWithoutCredential(int request_id,
239 NSString* promise) {
240 PrepareResolverAndRejecter(promise);
241 ResolvePromiseWithoutCredential(request_id);
242 CheckResolvedWithoutCredential();
243 CheckNeverRejected();
244 }
245
246 // Tests that rejecting the promise returned by |promise| and associated with
247 // |request_id| with an error of type |error| and message |message| correctly
248 // forwards that error to the client.
249 void TestPromiseRejection(int request_id,
250 NSString* error,
251 NSString* message,
252 NSString* promise) {
253 PrepareResolverAndRejecter(promise);
254 RejectPromise(request_id, error, message);
255 CheckRejected(error, message);
256 CheckNeverResolved();
257 }
258
259 private:
260 // Manager for injected credential manager JavaScript.
261 base::scoped_nsobject<JSCredentialManager> js_credential_manager_;
262
263 // Mock observer for testing.
264 std::unique_ptr<MockWebStateObserver> observer_;
265
266 DISALLOW_COPY_AND_ASSIGN(CredentialManagerJsTest);
267 };
268
269 // Tests that navigator.credentials calls use distinct request identifiers.
270 TEST_F(CredentialManagerJsTest, RequestIdentifiersDiffer) {
271 Inject();
272 EXPECT_CALL(observer(), CredentialsRequested(0, _, _, _, _));
273 EvaluateJavaScriptAsString(@"navigator.credentials.request()");
274 EXPECT_CALL(observer(), SignInFailed(1, _));
275 EvaluateJavaScriptAsString(@"navigator.credentials.notifyFailedSignIn()");
276 EXPECT_CALL(observer(), SignInFailed(2, _));
277 EvaluateJavaScriptAsString(@"navigator.credentials.notifyFailedSignIn()");
278 EXPECT_CALL(observer(), SignedIn(3, _));
279 EvaluateJavaScriptAsString(@"navigator.credentials.notifySignedIn()");
280 EXPECT_CALL(observer(), SignedOut(4, _));
281 EvaluateJavaScriptAsString(@"navigator.credentials.notifySignedOut()");
282 EXPECT_CALL(observer(), CredentialsRequested(5, _, _, _, _));
283 EvaluateJavaScriptAsString(@"navigator.credentials.request()");
284 }
285
286 // Tests that navigator.credentials.request() creates and forwards the right
287 // arguments to the app side.
288 // TODO(rohitrao): Fails after merge r376674. https://crbug.com/588706.
289 TEST_F(CredentialManagerJsTest, DISABLED_RequestToApp) {
290 Inject();
291 std::vector<std::string> empty_federations;
292 std::vector<std::string> nonempty_federations;
293 nonempty_federations.push_back("foo");
294 nonempty_federations.push_back("bar");
295
296 EXPECT_CALL(observer(),
297 CredentialsRequested(0, _, false, empty_federations, _));
298 EvaluateJavaScriptAsString(@"navigator.credentials.request()");
299
300 EXPECT_CALL(observer(),
301 CredentialsRequested(1, _, false, empty_federations, _));
302 EvaluateJavaScriptAsString(@"navigator.credentials.request({})");
303
304 EXPECT_CALL(observer(),
305 CredentialsRequested(2, _, true, empty_federations, _));
306 EvaluateJavaScriptAsString(
307 @"navigator.credentials.request({suppressUI: true})");
308
309 EXPECT_CALL(observer(),
310 CredentialsRequested(3, _, false, nonempty_federations, _));
311 EvaluateJavaScriptAsString(
312 @"navigator.credentials.request({federations: ['foo', 'bar']})");
313
314 EXPECT_CALL(observer(),
315 CredentialsRequested(4, _, true, nonempty_federations, _));
316 EvaluateJavaScriptAsString(
317 @"navigator.credentials.request("
318 @" { suppressUI: true, federations: ['foo', 'bar'] })");
319
320 EXPECT_CALL(observer(),
321 CredentialsRequested(5, _, false, empty_federations, _));
322 EvaluateJavaScriptAsString(@"navigator.credentials.request("
323 @" { suppressUI: false, federations: [] })");
324 }
325
326 // Tests that navigator.credentials.notifySignedIn() creates and forwards the
327 // right arguments to the app side.
328 TEST_F(CredentialManagerJsTest, NotifySignedInToApp) {
329 Inject();
330 EXPECT_CALL(observer(), SignedIn(0, _));
331 EvaluateJavaScriptAsString(@"navigator.credentials.notifySignedIn()");
332
333 EXPECT_CALL(observer(), SignedIn(1, _, IsEqualTo(test_credential())));
334 EvaluateJavaScriptAsString(
335 [NSString stringWithFormat:@"navigator.credentials.notifySignedIn(%@)",
336 test_credential_js()]);
337 }
338
339 // Tests that navigator.credentials.notifySignedOut() creates and forwards the
340 // right arguments to the app side.
341 TEST_F(CredentialManagerJsTest, NotifySignedOutToApp) {
342 Inject();
343 EXPECT_CALL(observer(), SignedOut(0, _));
344 EvaluateJavaScriptAsString(@"navigator.credentials.notifySignedOut()");
345 }
346
347 // Tests that navigator.credentials.notifyFailedSignIn() creates and forwards
348 // the right arguments to the app side.
349 TEST_F(CredentialManagerJsTest, NotifyFailedSignInToApp) {
350 Inject();
351 EXPECT_CALL(observer(), SignInFailed(0, _));
352 EvaluateJavaScriptAsString(@"navigator.credentials.notifyFailedSignIn()");
353
354 EXPECT_CALL(observer(), SignInFailed(1, _, IsEqualTo(test_credential())));
355 EvaluateJavaScriptAsString([NSString
356 stringWithFormat:@"navigator.credentials.notifyFailedSignIn(%@)",
357 test_credential_js()]);
358 }
359
360 // Tests that resolving the promise returned by a call to
361 // navigator.credentials.request() with a credential correctly forwards that
362 // credential to the client.
363 TEST_F(CredentialManagerJsTest, ResolveRequestPromiseWithCredential) {
364 Inject();
365 const int request_id = 0;
366 EXPECT_CALL(observer(), CredentialsRequested(request_id, _, _, _, _));
367 TestPromiseResolutionWithCredential(request_id,
368 @"navigator.credentials.request()");
369 }
370
371 // Tests that resolving the promise returned by a call to
372 // navigator.credentials.request() without a credential correctly invokes the
373 // client handler.
374 TEST_F(CredentialManagerJsTest, ResolveRequestPromiseWithoutCredential) {
375 Inject();
376 const int request_id = 0;
377 EXPECT_CALL(observer(), CredentialsRequested(request_id, _, _, _, _));
378 TestPromiseResolutionWithoutCredential(request_id,
379 @"navigator.credentials.request()");
380 }
381
382 // Tests that resolving the promise returned by a call to
383 // navigator.credentials.notifySignedIn() without a credential correctly invokes
384 // the client handler.
385 TEST_F(CredentialManagerJsTest, ResolveNotifySignedInPromiseWithoutCredential) {
386 Inject();
387 const int request_id = 0;
388 EXPECT_CALL(observer(), SignedIn(request_id, _));
389 TestPromiseResolutionWithoutCredential(
390 request_id, @"navigator.credentials.notifySignedIn()");
391 }
392
393 // Tests that resolving the promise returned by a call to
394 // navigator.credentials.notifyFailedSignIn() without a credential correctly
395 // invokes the client handler.
396 TEST_F(CredentialManagerJsTest,
397 ResolveNotifyFailedSignInPromiseWithoutCredential) {
398 Inject();
399 const int request_id = 0;
400 EXPECT_CALL(observer(), SignInFailed(request_id, _));
401 TestPromiseResolutionWithoutCredential(
402 request_id, @"navigator.credentials.notifyFailedSignIn()");
403 }
404
405 // Tests that resolving the promise returned by a call to
406 // navigator.credentials.notifyFailedSignIn() without a credential correctly
407 // invokes the client handler.
408 TEST_F(CredentialManagerJsTest,
409 ResolveNotifySignedOutPromiseWithoutCredential) {
410 Inject();
411 const int request_id = 0;
412 EXPECT_CALL(observer(), SignedOut(request_id, _));
413 TestPromiseResolutionWithoutCredential(
414 request_id, @"navigator.credentials.notifySignedOut()");
415 }
416
417 // Tests that rejecting the promise returned by a call to
418 // navigator.credentials.request() with a InvalidStateError correctly forwards
419 // that error to the client.
420 TEST_F(CredentialManagerJsTest, RejectRequestPromiseWithInvalidStateError) {
421 Inject();
422 const int request_id = 0;
423 EXPECT_CALL(observer(), CredentialsRequested(request_id, _, _, _, _));
424 TestPromiseRejection(request_id, @"InvalidStateError", @"foo",
425 @"navigator.credentials.request()");
426 }
427
428 // Tests that rejecting the promise returned by a call to
429 // navigator.credentials.notifySignedIn() with a InvalidStateError correctly
430 // forwards that error to the client.
431 TEST_F(CredentialManagerJsTest,
432 RejectNotifySignedInPromiseWithInvalidStateError) {
433 Inject();
434 const int request_id = 0;
435 EXPECT_CALL(observer(), SignedIn(request_id, _));
436 TestPromiseRejection(request_id, @"InvalidStateError", @"foo",
437 @"navigator.credentials.notifySignedIn()");
438 }
439
440 // Tests that rejecting the promise returned by a call to
441 // navigator.credentials.notifyFailedSignIn() with a InvalidStateError correctly
442 // forwards that error to the client.
443 TEST_F(CredentialManagerJsTest,
444 RejectNotifyFailedSignInPromiseWithInvalidStateError) {
445 Inject();
446 const int request_id = 0;
447 EXPECT_CALL(observer(), SignInFailed(request_id, _));
448 TestPromiseRejection(request_id, @"InvalidStateError", @"foo",
449 @"navigator.credentials.notifyFailedSignIn()");
450 }
451
452 // Tests that rejecting the promise returned by a call to
453 // navigator.credentials.notifySignedOut() with a InvalidStateError correctly
454 // forwards that error to the client.
455 TEST_F(CredentialManagerJsTest,
456 RejectNotifySignedOutPromiseWithInvalidStateError) {
457 Inject();
458 const int request_id = 0;
459 EXPECT_CALL(observer(), SignedOut(request_id, _));
460 TestPromiseRejection(request_id, @"InvalidStateError", @"foo",
461 @"navigator.credentials.notifySignedOut()");
462 }
463
464 // Tests that rejecting the promise returned by a call to
465 // navigator.credentials.request() with a SecurityError correctly forwards that
466 // error to the client.
467 TEST_F(CredentialManagerJsTest, RejectRequestPromiseWithSecurityError) {
468 Inject();
469 const int request_id = 0;
470 EXPECT_CALL(observer(), CredentialsRequested(request_id, _, _, _, _));
471 TestPromiseRejection(request_id, @"SecurityError", @"foo",
472 @"navigator.credentials.request()");
473 }
474
475 // Tests that rejecting the promise returned by a call to
476 // navigator.credentials.notifySignedIn() with a SecurityError correctly
477 // forwards that error to the client.
478 TEST_F(CredentialManagerJsTest, RejectNotifySignedInPromiseWithSecurityError) {
479 Inject();
480 const int request_id = 0;
481 EXPECT_CALL(observer(), SignedIn(request_id, _));
482 TestPromiseRejection(request_id, @"SecurityError", @"foo",
483 @"navigator.credentials.notifySignedIn()");
484 }
485
486 // Tests that rejecting the promise returned by a call to
487 // navigator.credentials.notifyFailedSignIn() with a SecurityError correctly
488 // forwards that error to the client.
489 TEST_F(CredentialManagerJsTest,
490 RejectPromiseWithSecurityError_notifyFailedSignIn) {
491 Inject();
492 const int request_id = 0;
493 EXPECT_CALL(observer(), SignInFailed(request_id, _));
494 TestPromiseRejection(request_id, @"SecurityError", @"foo",
495 @"navigator.credentials.notifyFailedSignIn()");
496 }
497
498 // Tests that rejecting the promise returned by a call to
499 // navigator.credentials.notifySignedOut() with a SecurityError correctly
500 // forwards that error to the client.
501 TEST_F(CredentialManagerJsTest,
502 RejectPromiseWithSecurityError_notifySignedOut) {
503 Inject();
504 const int request_id = 0;
505 EXPECT_CALL(observer(), SignedOut(request_id, _));
506 TestPromiseRejection(request_id, @"SecurityError", @"foo",
507 @"navigator.credentials.notifySignedOut()");
508 }
509
510 } // namespace
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698