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

Unified Diff: components/password_manager/core/browser/affiliation_database_unittest.cc

Issue 797983003: Implement AffiliationDatabase to cache affiliation information in a SQLite DB. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@aff_fetcher
Patch Set: Addressed comments from shess@. Created 5 years, 11 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 side-by-side diff with in-line comments
Download patch
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..6fc8a7e1fb2bafa647be2b939198b4d01b250818
--- /dev/null
+++ b/components/password_manager/core/browser/affiliation_database_unittest.cc
@@ -0,0 +1,293 @@
+// 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 an existing DB can be reopened, and data is retained.
+TEST_F(AffiliationDatabaseTest, DBRetainsDataAfterReopening) {
+ ASSERT_NO_FATAL_FAILURE(StoreInitialTestData());
+
+ CloseDatabase();
+ OpenDatabase();
+
+ // The test data should be returned in order.
+ std::vector<AffiliatedFacetsWithUpdateTime> affiliations;
+ db().GetAllAffiliations(&affiliations);
+ ASSERT_EQ(3u, affiliations.size());
+ ExpectEquivalenceClassesAreEqual(TestEquivalenceClass1(), affiliations[0]);
+ ExpectEquivalenceClassesAreEqual(TestEquivalenceClass2(), affiliations[1]);
+ ExpectEquivalenceClassesAreEqual(TestEquivalenceClass3(), affiliations[2]);
+}
+
+// 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) {
+ 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

Powered by Google App Engine
This is Rietveld 408576698