Chromium Code Reviews| Index: components/password_manager/core/browser/affiliation_database_unittest.cc |
| diff --git a/components/password_manager/core/browser/affiliation_database_unittest.cc b/components/password_manager/core/browser/affiliation_database_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..a4ac37ddc19561b5e37a9b5268685805828f8c82 |
| --- /dev/null |
| +++ b/components/password_manager/core/browser/affiliation_database_unittest.cc |
| @@ -0,0 +1,279 @@ |
| +// 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_database.h" |
| + |
| +#include "base/files/file_util.h" |
| +#include "sql/test/scoped_error_ignorer.h" |
| +#include "sql/test/test_helpers.h" |
| +#include "testing/gmock/include/gmock/gmock.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| +#include "third_party/sqlite/sqlite3.h" |
| + |
| +namespace password_manager { |
| + |
| +namespace { |
| + |
| +const char kTestFacetURI1[] = "https://alpha.example.com"; |
| +const char kTestFacetURI2[] = "https://beta.example.com"; |
| +const char kTestFacetURI3[] = "https://gamma.example.com"; |
| +const char kTestFacetURI4[] = "https://delta.example.com"; |
| +const char kTestFacetURI5[] = "https://epsilon.example.com"; |
| +const char kTestFacetURI6[] = "https://zeta.example.com"; |
| + |
| +const char kTestAndroidFacetURI[] = "android://hash@com.example.android"; |
| + |
| +const int64 kTestTimeUs1 = 1000000; |
| +const int64 kTestTimeUs2 = 2000000; |
| +const int64 kTestTimeUs3 = 3000000; |
| + |
| +void ExpectEquivalenceClassesAreEqual( |
| + const AffiliatedFacetsWithUpdateTime& expectation, |
| + const AffiliatedFacetsWithUpdateTime& reality) { |
| + EXPECT_EQ(expectation.last_update_time, reality.last_update_time); |
| + EXPECT_THAT(reality.facets, |
| + testing::UnorderedElementsAreArray(reality.facets)); |
| +} |
| + |
| +AffiliatedFacetsWithUpdateTime TestEquivalenceClass1() { |
| + AffiliatedFacetsWithUpdateTime affiliation; |
| + affiliation.last_update_time = base::Time::FromInternalValue(kTestTimeUs1); |
| + affiliation.facets.push_back(FacetURI::FromCanonicalSpec(kTestFacetURI1)); |
| + affiliation.facets.push_back(FacetURI::FromCanonicalSpec(kTestFacetURI2)); |
| + affiliation.facets.push_back(FacetURI::FromCanonicalSpec(kTestFacetURI3)); |
| + return affiliation; |
| +} |
| + |
| +AffiliatedFacetsWithUpdateTime TestEquivalenceClass2() { |
| + AffiliatedFacetsWithUpdateTime affiliation; |
| + affiliation.last_update_time = base::Time::FromInternalValue(kTestTimeUs2); |
| + affiliation.facets.push_back(FacetURI::FromCanonicalSpec(kTestFacetURI4)); |
| + affiliation.facets.push_back(FacetURI::FromCanonicalSpec(kTestFacetURI5)); |
| + return affiliation; |
| +} |
| + |
| +AffiliatedFacetsWithUpdateTime TestEquivalenceClass3() { |
| + AffiliatedFacetsWithUpdateTime affiliation; |
| + affiliation.last_update_time = base::Time::FromInternalValue(kTestTimeUs3); |
| + affiliation.facets.push_back( |
| + FacetURI::FromCanonicalSpec(kTestAndroidFacetURI)); |
| + return affiliation; |
| +} |
| + |
| +} // namespace |
| + |
| +class AffiliationDatabaseTest : public testing::Test { |
| + public: |
| + AffiliationDatabaseTest() {} |
| + ~AffiliationDatabaseTest() override {} |
| + |
| + void SetUp() override { |
| + ASSERT_TRUE(CreateTemporaryFile(&db_path_)); |
| + OpenDatabase(); |
| + } |
| + |
| + void OpenDatabase() { |
| + db_.reset(new AffiliationDatabase); |
| + ASSERT_TRUE(db_->Init(db_path())); |
| + } |
| + |
| + void CloseDatabase() { |
| + db_.reset(); |
| + } |
| + |
| + void StoreInitialTestData() { |
| + ASSERT_TRUE(db_->Store(TestEquivalenceClass1())); |
| + ASSERT_TRUE(db_->Store(TestEquivalenceClass2())); |
| + ASSERT_TRUE(db_->Store(TestEquivalenceClass3())); |
| + } |
| + |
| + AffiliationDatabase& db() { return *db_; } |
| + |
| + const base::FilePath& db_path() { return db_path_; } |
| + |
| + private: |
| + base::FilePath db_path_; |
| + scoped_ptr<AffiliationDatabase> db_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(AffiliationDatabaseTest); |
| +}; |
| + |
| +TEST_F(AffiliationDatabaseTest, Store) { |
| + LOG(ERROR) << "During this test, SQL errors (number 19) will be logged to " |
| + "the console. This is expected."; |
| + |
| + ASSERT_NO_FATAL_FAILURE(StoreInitialTestData()); |
| + |
| + // Verify that duplicate equivalence classes are not allowed to be stored. |
| + { |
| + sql::ScopedErrorIgnorer error_ignorer; |
| + error_ignorer.IgnoreError(SQLITE_CONSTRAINT); |
| + AffiliatedFacetsWithUpdateTime duplicate = TestEquivalenceClass1(); |
| + EXPECT_FALSE(db().Store(duplicate)); |
| + EXPECT_TRUE(error_ignorer.CheckIgnoredErrors()); |
| + } |
| + |
| + // Verify that intersecting equivalence classes are not allowed to be stored. |
| + { |
| + sql::ScopedErrorIgnorer error_ignorer; |
| + error_ignorer.IgnoreError(SQLITE_CONSTRAINT); |
| + AffiliatedFacetsWithUpdateTime intersecting; |
| + intersecting.facets.push_back(FacetURI::FromCanonicalSpec(kTestFacetURI3)); |
| + intersecting.facets.push_back(FacetURI::FromCanonicalSpec(kTestFacetURI4)); |
| + EXPECT_FALSE(db().Store(intersecting)); |
| + EXPECT_TRUE(error_ignorer.CheckIgnoredErrors()); |
| + } |
| +} |
| + |
| +TEST_F(AffiliationDatabaseTest, GetAllAffiliations) { |
| + std::vector<AffiliatedFacetsWithUpdateTime> affiliations; |
| + |
| + // Empty database should not return any equivalence classes. |
| + db().GetAllAffiliations(&affiliations); |
| + EXPECT_EQ(0u, affiliations.size()); |
| + |
| + ASSERT_NO_FATAL_FAILURE(StoreInitialTestData()); |
| + |
| + // The test data should be returned in order. |
| + db().GetAllAffiliations(&affiliations); |
| + ASSERT_EQ(3u, affiliations.size()); |
| + ExpectEquivalenceClassesAreEqual(TestEquivalenceClass1(), affiliations[0]); |
| + ExpectEquivalenceClassesAreEqual(TestEquivalenceClass2(), affiliations[1]); |
| + ExpectEquivalenceClassesAreEqual(TestEquivalenceClass3(), affiliations[2]); |
| +} |
| + |
| +TEST_F(AffiliationDatabaseTest, GetAffiliationForFacet) { |
| + ASSERT_NO_FATAL_FAILURE(StoreInitialTestData()); |
| + |
| + // Verify that querying any element in the first equivalence class yields that |
| + // class. |
| + for (const auto& facet_uri : TestEquivalenceClass1().facets) { |
| + AffiliatedFacetsWithUpdateTime affiliation; |
| + EXPECT_TRUE(db().GetAffiliationsForFacet(facet_uri, &affiliation)); |
| + ExpectEquivalenceClassesAreEqual(TestEquivalenceClass1(), affiliation); |
| + } |
| + |
| + // Verify that querying the sole element in the third equivalence class yields |
| + // that class. |
| + { |
| + AffiliatedFacetsWithUpdateTime affiliation; |
| + EXPECT_TRUE(db().GetAffiliationsForFacet( |
| + FacetURI::FromCanonicalSpec(kTestAndroidFacetURI), &affiliation)); |
| + ExpectEquivalenceClassesAreEqual(TestEquivalenceClass3(), affiliation); |
| + } |
| + |
| + // Verify that querying a facet not in the database yields no result. |
| + { |
| + AffiliatedFacetsWithUpdateTime affiliation; |
| + EXPECT_FALSE(db().GetAffiliationsForFacet( |
| + FacetURI::FromCanonicalSpec(kTestFacetURI6), &affiliation)); |
| + ExpectEquivalenceClassesAreEqual(AffiliatedFacetsWithUpdateTime(), |
| + affiliation); |
| + } |
| +} |
| + |
| +TEST_F(AffiliationDatabaseTest, StoreAndRemoveConflicting) { |
| + ASSERT_NO_FATAL_FAILURE(StoreInitialTestData()); |
| + |
| + AffiliatedFacetsWithUpdateTime updated = TestEquivalenceClass1(); |
| + updated.last_update_time = base::Time::FromInternalValue(4000000); |
| + |
| + // Verify that duplicate equivalence classes are now allowed to be stored, and |
| + // the last update timestamp is updated. |
| + { |
| + std::vector<AffiliatedFacetsWithUpdateTime> removed; |
| + db().StoreAndRemoveConflicting(updated, &removed); |
| + EXPECT_EQ(0u, removed.size()); |
| + |
| + AffiliatedFacetsWithUpdateTime affiliation; |
| + EXPECT_TRUE(db().GetAffiliationsForFacet( |
| + FacetURI::FromCanonicalSpec(kTestFacetURI1), &affiliation)); |
| + ExpectEquivalenceClassesAreEqual(updated, affiliation); |
| + } |
| + |
| + // Verify that intersecting equivalence classes are now allowed to be stored, |
| + // the conflicting classes are removed, but unaffected classes are retained. |
| + { |
| + AffiliatedFacetsWithUpdateTime intersecting; |
| + std::vector<AffiliatedFacetsWithUpdateTime> removed; |
| + intersecting.last_update_time = base::Time::FromInternalValue(5000000); |
| + intersecting.facets.push_back(FacetURI::FromCanonicalSpec(kTestFacetURI3)); |
| + intersecting.facets.push_back(FacetURI::FromCanonicalSpec(kTestFacetURI4)); |
| + db().StoreAndRemoveConflicting(intersecting, &removed); |
| + |
| + ASSERT_EQ(2u, removed.size()); |
| + ExpectEquivalenceClassesAreEqual(updated, removed[0]); |
| + ExpectEquivalenceClassesAreEqual(TestEquivalenceClass2(), removed[1]); |
| + |
| + std::vector<AffiliatedFacetsWithUpdateTime> affiliations; |
| + db().GetAllAffiliations(&affiliations); |
| + ASSERT_EQ(2u, affiliations.size()); |
| + ExpectEquivalenceClassesAreEqual(TestEquivalenceClass3(), affiliations[0]); |
| + ExpectEquivalenceClassesAreEqual(intersecting, affiliations[1]); |
| + } |
| +} |
| + |
| +TEST_F(AffiliationDatabaseTest, DeleteAllAffiliations) { |
| + db().DeleteAllAffiliations(); |
| + |
| + ASSERT_NO_FATAL_FAILURE(StoreInitialTestData()); |
| + |
| + db().DeleteAllAffiliations(); |
| + |
| + std::vector<AffiliatedFacetsWithUpdateTime> affiliations; |
| + db().GetAllAffiliations(&affiliations); |
| + ASSERT_EQ(0u, affiliations.size()); |
| +} |
| + |
| +TEST_F(AffiliationDatabaseTest, DeleteAffiliationsOlderThan) { |
| + db().DeleteAffiliationsOlderThan(base::Time::FromInternalValue(0)); |
| + |
| + ASSERT_NO_FATAL_FAILURE(StoreInitialTestData()); |
| + |
| + db().DeleteAffiliationsOlderThan(base::Time::FromInternalValue(kTestTimeUs2)); |
| + |
| + std::vector<AffiliatedFacetsWithUpdateTime> affiliations; |
| + db().GetAllAffiliations(&affiliations); |
| + ASSERT_EQ(2u, affiliations.size()); |
| + ExpectEquivalenceClassesAreEqual(TestEquivalenceClass2(), affiliations[0]); |
| + ExpectEquivalenceClassesAreEqual(TestEquivalenceClass3(), affiliations[1]); |
| + |
| + db().DeleteAffiliationsOlderThan(base::Time::Max()); |
| + |
| + db().GetAllAffiliations(&affiliations); |
| + ASSERT_EQ(0u, affiliations.size()); |
| +} |
| + |
| +// Verify that when it is discovered during opening that a DB is corrupt, it |
| +// gets razed, and then an empty (but again usable) DB is produced. |
| +TEST_F(AffiliationDatabaseTest, CorruptDBIsRazedThenOpened) { |
|
Scott Hess - ex-Googler
2015/01/20 23:21:38
Thanks!
|
| + ASSERT_NO_FATAL_FAILURE(StoreInitialTestData()); |
| + |
| + CloseDatabase(); |
| + ASSERT_TRUE(sql::test::CorruptSizeInHeader(db_path())); |
| + ASSERT_NO_FATAL_FAILURE(OpenDatabase()); |
| + |
| + std::vector<AffiliatedFacetsWithUpdateTime> affiliations; |
| + db().GetAllAffiliations(&affiliations); |
| + EXPECT_EQ(0u, affiliations.size()); |
| + |
| + ASSERT_NO_FATAL_FAILURE(StoreInitialTestData()); |
| + db().GetAllAffiliations(&affiliations); |
| + EXPECT_EQ(3u, affiliations.size()); |
| +} |
| + |
| +// Verify that when the DB becomes corrupt after it has been opened, it gets |
| +// poisoned so that operations fail silently without side effects. |
| +TEST_F(AffiliationDatabaseTest, CorruptDBGetsPoisoned) { |
| + ASSERT_TRUE(db().Store(TestEquivalenceClass1())); |
| + |
| + ASSERT_TRUE(sql::test::CorruptSizeInHeader(db_path())); |
| + |
| + EXPECT_FALSE(db().Store(TestEquivalenceClass2())); |
| + std::vector<AffiliatedFacetsWithUpdateTime> affiliations; |
| + db().GetAllAffiliations(&affiliations); |
| + EXPECT_EQ(0u, affiliations.size()); |
| +} |
| + |
| +} // namespace password_manager |