Index: components/password_manager/core/browser/affiliation_fetcher_unittest.cc |
diff --git a/components/password_manager/core/browser/affiliation_fetcher_unittest.cc b/components/password_manager/core/browser/affiliation_fetcher_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..983e91c2d381a98c481a7b444bccf61f2dd3c76e |
--- /dev/null |
+++ b/components/password_manager/core/browser/affiliation_fetcher_unittest.cc |
@@ -0,0 +1,328 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "components/password_manager/core/browser/affiliation_fetcher.h" |
+ |
+#include "base/test/null_task_runner.h" |
+#include "net/url_request/test_url_fetcher_factory.h" |
+#include "net/url_request/url_request_test_util.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace password_manager { |
+ |
+namespace { |
+ |
+const char kExampleAndroidFacetURI[] = "android://hash@com.example/"; |
+const char kExampleWebFacet1URI[] = "https://www.example.com/"; |
+const char kExampleWebFacet2URI[] = "https://www.example.org/"; |
+ |
+class MockAffiliationFetcherDelegate |
+ : public testing::StrictMock<AffiliationFetcherDelegate> { |
+ public: |
+ MockAffiliationFetcherDelegate() {} |
+ ~MockAffiliationFetcherDelegate() {} |
+ |
+ MOCK_METHOD0(OnFetchSucceededProxy, void()); |
+ MOCK_METHOD0(OnFetchFailed, void()); |
+ MOCK_METHOD0(OnMalformedResponse, void()); |
+ |
+ void OnFetchSucceeded( |
+ scoped_ptr<AffiliationFetcher::Result> result) override { |
+ OnFetchSucceededProxy(); |
+ result_ = result.Pass(); |
+ } |
+ |
+ const AffiliationFetcher::Result& result() const { return *result_.get(); } |
+ |
+ private: |
+ scoped_ptr<AffiliationFetcher::Result> result_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(MockAffiliationFetcherDelegate); |
+}; |
+ |
+} // namespace |
+ |
+class AffiliationFetcherTest : public testing::Test { |
+ public: |
+ AffiliationFetcherTest() |
+ : request_context_getter_(new net::TestURLRequestContextGetter( |
+ make_scoped_refptr(new base::NullTaskRunner))) {} |
+ |
+ ~AffiliationFetcherTest() override {} |
+ |
+ protected: |
+ void VerifyRequestPayload(std::string expected_payload) { |
+ net::TestURLFetcher* url_fetcher = |
+ test_url_fetcher_factory_.GetFetcherByID(0); |
+ ASSERT_NE(nullptr, url_fetcher); |
+ |
+ ReplaceSubstringsAfterOffset(&expected_payload, 0, " ", ""); |
+ EXPECT_EQ("application/json", url_fetcher->upload_content_type()); |
+ EXPECT_EQ(expected_payload, url_fetcher->upload_data()); |
+ } |
+ |
+ void ServiceURLRequest(const std::string& response) { |
+ net::TestURLFetcher* url_fetcher = |
+ test_url_fetcher_factory_.GetFetcherByID(0); |
+ ASSERT_NE(nullptr, url_fetcher); |
+ |
+ url_fetcher->set_response_code(200); |
+ url_fetcher->SetResponseString(response); |
+ url_fetcher->delegate()->OnURLFetchComplete(url_fetcher); |
+ } |
+ |
+ void SimulateServerError() { |
+ net::TestURLFetcher* url_fetcher = |
+ test_url_fetcher_factory_.GetFetcherByID(0); |
+ ASSERT_NE(nullptr, url_fetcher); |
+ |
+ url_fetcher->set_response_code(500); |
+ url_fetcher->delegate()->OnURLFetchComplete(url_fetcher); |
+ } |
+ |
+ void SimulateNetworkError() { |
+ net::TestURLFetcher* url_fetcher = |
+ test_url_fetcher_factory_.GetFetcherByID(0); |
+ ASSERT_NE(nullptr, url_fetcher); |
+ url_fetcher->set_status(net::URLRequestStatus(net::URLRequestStatus::FAILED, |
+ net::ERR_NETWORK_CHANGED)); |
+ url_fetcher->delegate()->OnURLFetchComplete(url_fetcher); |
+ } |
+ |
+ net::TestURLRequestContextGetter* request_context_getter() { |
+ return request_context_getter_.get(); |
+ } |
+ |
+ private: |
+ scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_; |
+ net::TestURLFetcherFactory test_url_fetcher_factory_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(AffiliationFetcherTest); |
+}; |
+ |
+TEST_F(AffiliationFetcherTest, BasicReqestAndResponse) { |
+ const char kNotExampleAndroidFacetURI[] = "android://hash2@com.example.not/"; |
+ const char kNotExampleWebFacetURI[] = "https://not.example.com/"; |
+ |
+ const char kExpectedRequestString[] = |
+ "{" |
+ " \"facet\": [" |
+ " \"https://www.example.com/\"," |
+ " \"android://hash2@com.example.not/\"" |
+ " ]" |
+ "}"; |
+ |
+ const char kTestResponseString[] = |
+ "{" |
+ " \"affiliation\": [" |
+ " {" |
+ " \"facet\": [" |
+ " \"https://www.example.com/\"," |
+ " \"https://www.example.org/\"," |
+ " \"android://hash@com.example/\"" |
+ " ]" |
+ " }," |
+ " {" |
+ " \"facet\": [" |
+ " \"https://not.example.com/\"," |
+ " \"android://hash2@com.example.not/\"" |
+ " ]" |
+ " }" |
+ " ]" |
+ "}"; |
+ |
+ std::vector<std::string> uris; |
+ uris.push_back(kExampleWebFacet1URI); |
+ uris.push_back(kNotExampleAndroidFacetURI); |
+ |
+ MockAffiliationFetcherDelegate mock_delegate; |
+ scoped_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create( |
+ request_context_getter(), uris, &mock_delegate)); |
+ fetcher->StartRequest(); |
+ |
+ EXPECT_CALL(mock_delegate, OnFetchSucceededProxy()); |
+ ASSERT_NO_FATAL_FAILURE(VerifyRequestPayload(kExpectedRequestString)); |
+ ASSERT_NO_FATAL_FAILURE(ServiceURLRequest(kTestResponseString)); |
+ ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&mock_delegate)); |
+ |
+ EXPECT_THAT(mock_delegate.result()[0], |
+ testing::ElementsAre(kExampleWebFacet1URI, kExampleWebFacet2URI, |
+ kExampleAndroidFacetURI)); |
+ EXPECT_THAT( |
+ mock_delegate.result()[1], |
+ testing::ElementsAre(kNotExampleWebFacetURI, kNotExampleAndroidFacetURI)); |
+} |
+ |
+// The API does not return facet URIs that are not affiliated with anything, or |
+// facet URIs that it does not know about. However, the AffiliationFetcher has |
+// promised to return an equivalence class for each requested facet. |
+TEST_F(AffiliationFetcherTest, MissingEquivalenceClassesAreCreated) { |
+ // Pretend the service does not know about example.com. |
+ const char kTestResponseString[] = "{\"affiliation\": []}"; |
+ |
+ std::vector<std::string> uris; |
+ uris.push_back(kExampleWebFacet1URI); |
+ |
+ MockAffiliationFetcherDelegate mock_delegate; |
+ scoped_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create( |
+ request_context_getter(), uris, &mock_delegate)); |
+ fetcher->StartRequest(); |
+ |
+ EXPECT_CALL(mock_delegate, OnFetchSucceededProxy()); |
+ ASSERT_NO_FATAL_FAILURE(ServiceURLRequest(kTestResponseString)); |
+ ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&mock_delegate)); |
+ |
+ ASSERT_EQ(1u, mock_delegate.result().size()); |
+ EXPECT_THAT(mock_delegate.result()[0], |
+ testing::ElementsAre(kExampleWebFacet1URI)); |
+} |
+ |
+TEST_F(AffiliationFetcherTest, DuplicateEquivalenceClassesAreIgnored) { |
+ const char kTestResponseString[] = |
+ "{" |
+ " \"affiliation\": [" |
+ " {" |
+ " \"facet\": [" |
+ " \"https://www.example.com/\"," |
+ " \"https://www.example.org/\"," |
+ " \"android://hash@com.example/\"" |
+ " ]" |
+ " }," |
+ " {" |
+ " \"facet\": [" |
+ " \"android://hash@com.example/\"," |
+ " \"https://www.example.com/\"," |
+ " \"https://www.example.org/\"" |
+ " ]" |
+ " }" |
+ " ]" |
+ "}"; |
+ |
+ std::vector<std::string> uris; |
+ uris.push_back(kExampleWebFacet1URI); |
+ |
+ MockAffiliationFetcherDelegate mock_delegate; |
+ scoped_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create( |
+ request_context_getter(), uris, &mock_delegate)); |
+ fetcher->StartRequest(); |
+ |
+ EXPECT_CALL(mock_delegate, OnFetchSucceededProxy()); |
+ ASSERT_NO_FATAL_FAILURE(ServiceURLRequest(kTestResponseString)); |
+ ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&mock_delegate)); |
+ |
+ ASSERT_EQ(1u, mock_delegate.result().size()); |
+ EXPECT_THAT(mock_delegate.result()[0], |
+ testing::ElementsAre(kExampleWebFacet1URI, kExampleWebFacet2URI, |
+ kExampleAndroidFacetURI)); |
+} |
+ |
+TEST_F(AffiliationFetcherTest, UnrecognizedFieldsAreIgnoredWhenSafe) { |
+ const char kTestResponseString[] = |
+ "{" |
+ " \"affiliation\": [" |
+ " {" |
+ " \"facet\": [" |
+ " \"https://www.example.com/\"," |
+ " \"https://www.example.org/\"," |
+ " \"android://hash@com.example/\"," |
+ // Facet IDs corresponding to new platform. |
+ " \"new-platform://app-id-on-new-platform/\"" |
+ " ]," |
+ // Unknown dictionary keys. |
+ " \"ignored_dictionary_entry\": true" |
+ " }," |
+ " {" |
+ // Equivalence classes only unknown facet IDs. |
+ " \"facet\": [" |
+ " \"new-platform://app-id-on-new-platform/\"" |
+ " ]" |
+ " }," |
+ " {" |
+ // Empty equivalence classes. |
+ " \"facet\": []" |
+ " }" |
+ " ]," |
+ // Unknown dictionary keys. |
+ " \"ignored_dictionary_entry\": true" |
+ "}"; |
+ |
+ std::vector<std::string> uris; |
+ uris.push_back(kExampleWebFacet1URI); |
+ |
+ MockAffiliationFetcherDelegate mock_delegate; |
+ scoped_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create( |
+ request_context_getter(), uris, &mock_delegate)); |
+ fetcher->StartRequest(); |
+ |
+ EXPECT_CALL(mock_delegate, OnFetchSucceededProxy()); |
+ ASSERT_NO_FATAL_FAILURE(ServiceURLRequest(kTestResponseString)); |
+ ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&mock_delegate)); |
+ |
+ ASSERT_EQ(1u, mock_delegate.result().size()); |
+ EXPECT_THAT(mock_delegate.result()[0], |
+ testing::ElementsAre(kExampleWebFacet1URI, kExampleWebFacet2URI, |
+ kExampleAndroidFacetURI)); |
+} |
+ |
+TEST_F(AffiliationFetcherTest, FailDueToMalformedResponse) { |
+ const char* kMalformedResponses[] = { |
+ "Not a valid JSON.", |
+ "\"Not a dictionary.\"", |
+ "{\"no_affiliation_key\": true}", |
+ "{\"affiliation\": \"is not a list\"}", |
+ "{\"affiliation\": [\"is not a dictionary\"]}", |
+ "{\"affiliation\": [{\"no_facet_key\": true}]}", |
+ "{\"affiliation\": [{\"facet\": \"is not a list\"}]}", |
+ // List of URIs contains non-string. |
+ "{\"affiliation\": [{\"facet\": [123456789]}]}", |
+ // Response can not be part of an equivalence relation. |
+ "{\"affiliation\": [" |
+ " {\"facet\": [\"https://www.example.com/\", \"https://foo/\"]}" |
+ " {\"facet\": [\"https://www.example.com/\", \"https://bar/\"]}" |
+ "]}"}; |
+ |
+ std::vector<std::string> uris; |
+ uris.push_back(kExampleWebFacet1URI); |
+ for (size_t i = 0; i < arraysize(kMalformedResponses); ++i) { |
+ SCOPED_TRACE(kMalformedResponses[i]); |
+ |
+ MockAffiliationFetcherDelegate mock_delegate; |
+ scoped_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create( |
+ request_context_getter(), uris, &mock_delegate)); |
+ fetcher->StartRequest(); |
+ |
+ EXPECT_CALL(mock_delegate, OnMalformedResponse()); |
+ ASSERT_NO_FATAL_FAILURE(ServiceURLRequest(kMalformedResponses[i])); |
+ ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&mock_delegate)); |
+ } |
+} |
+ |
+TEST_F(AffiliationFetcherTest, FailOnFirstServerError) { |
+ std::vector<std::string> uris; |
+ uris.push_back(kExampleWebFacet1URI); |
+ |
+ MockAffiliationFetcherDelegate mock_delegate; |
+ scoped_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create( |
+ request_context_getter(), uris, &mock_delegate)); |
+ fetcher->StartRequest(); |
+ |
+ EXPECT_CALL(mock_delegate, OnFetchFailed()); |
+ ASSERT_NO_FATAL_FAILURE(SimulateServerError()); |
+} |
+ |
+TEST_F(AffiliationFetcherTest, FailOnFirstNetworkError) { |
+ std::vector<std::string> uris; |
+ uris.push_back(kExampleWebFacet1URI); |
+ |
+ MockAffiliationFetcherDelegate mock_delegate; |
+ scoped_ptr<AffiliationFetcher> fetcher(AffiliationFetcher::Create( |
+ request_context_getter(), uris, &mock_delegate)); |
+ fetcher->StartRequest(); |
+ |
+ EXPECT_CALL(mock_delegate, OnFetchFailed()); |
+ ASSERT_NO_FATAL_FAILURE(SimulateNetworkError()); |
+} |
+ |
+} // namespace password_manager |