summaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/components_tests.gyp1
-rw-r--r--components/password_manager.gypi2
-rw-r--r--components/password_manager/core/browser/BUILD.gn2
-rw-r--r--components/password_manager/core/browser/login_database.cc13
-rw-r--r--components/password_manager/core/browser/login_database.h4
-rw-r--r--components/password_manager/core/browser/mock_password_store.h6
-rw-r--r--components/password_manager/core/browser/password_store.cc28
-rw-r--r--components/password_manager/core/browser/password_store.h24
-rw-r--r--components/password_manager/core/browser/password_store_consumer.cc6
-rw-r--r--components/password_manager/core/browser/password_store_consumer.h7
-rw-r--r--components/password_manager/core/browser/password_store_default.cc19
-rw-r--r--components/password_manager/core/browser/password_store_default.h5
-rw-r--r--components/password_manager/core/browser/statistics_table.cc83
-rw-r--r--components/password_manager/core/browser/statistics_table.h61
-rw-r--r--components/password_manager/core/browser/statistics_table_unittest.cc91
-rw-r--r--components/password_manager/core/browser/test_password_store.cc11
-rw-r--r--components/password_manager/core/browser/test_password_store.h4
-rw-r--r--components/test/data/password_manager/login_db_v12.sql82
18 files changed, 445 insertions, 4 deletions
diff --git a/components/components_tests.gyp b/components/components_tests.gyp
index 05383f1..9c3ea50 100644
--- a/components/components_tests.gyp
+++ b/components/components_tests.gyp
@@ -346,6 +346,7 @@
'password_manager/core/browser/password_store_unittest.cc',
'password_manager/core/browser/password_syncable_service_unittest.cc',
'password_manager/core/browser/psl_matching_helper_unittest.cc',
+ 'password_manager/core/browser/statistics_table_unittest.cc',
'password_manager/core/common/credential_manager_types_unittest.cc',
],
'policy_unittest_sources': [
diff --git a/components/password_manager.gypi b/components/password_manager.gypi
index b560a66..62792ef 100644
--- a/components/password_manager.gypi
+++ b/components/password_manager.gypi
@@ -90,6 +90,8 @@
'password_manager/core/browser/password_syncable_service.h',
'password_manager/core/browser/psl_matching_helper.cc',
'password_manager/core/browser/psl_matching_helper.h',
+ 'password_manager/core/browser/statistics_table.cc',
+ 'password_manager/core/browser/statistics_table.h',
'password_manager/core/browser/test_affiliation_fetcher_factory.h',
'password_manager/core/browser/webdata/logins_table.cc',
'password_manager/core/browser/webdata/logins_table.h',
diff --git a/components/password_manager/core/browser/BUILD.gn b/components/password_manager/core/browser/BUILD.gn
index 9880bd3..f46488d 100644
--- a/components/password_manager/core/browser/BUILD.gn
+++ b/components/password_manager/core/browser/BUILD.gn
@@ -72,6 +72,8 @@ static_library("browser") {
"password_syncable_service.h",
"psl_matching_helper.cc",
"psl_matching_helper.h",
+ "statistics_table.cc",
+ "statistics_table.h",
"test_affiliation_fetcher_factory.h",
"webdata/logins_table.cc",
"webdata/logins_table.h",
diff --git a/components/password_manager/core/browser/login_database.cc b/components/password_manager/core/browser/login_database.cc
index f59f8c6..91c71d0 100644
--- a/components/password_manager/core/browser/login_database.cc
+++ b/components/password_manager/core/browser/login_database.cc
@@ -29,7 +29,7 @@ using autofill::PasswordForm;
namespace password_manager {
-const int kCurrentVersionNumber = 12;
+const int kCurrentVersionNumber = 13;
static const int kCompatibleVersionNumber = 1;
Pickle SerializeVector(const std::vector<base::string16>& vec) {
@@ -233,7 +233,13 @@ bool LoginDatabase::Init() {
// Initialize the tables.
if (!InitLoginsTable()) {
- LOG(WARNING) << "Unable to initialize the password store database.";
+ LOG(WARNING) << "Unable to initialize the logins table.";
+ db_.Close();
+ return false;
+ }
+
+ if (!stats_table_.Init(&db_)) {
+ LOG(WARNING) << "Unable to initialize the stats table.";
db_.Close();
return false;
}
@@ -374,6 +380,9 @@ bool LoginDatabase::MigrateOldVersionsAsNeeded() {
"generation_upload_status INTEGER"))
return false;
meta_table_.SetVersionNumber(12);
+ case 12:
+ // The stats table was added. Nothing to do really.
+ meta_table_.SetVersionNumber(13);
case kCurrentVersionNumber:
// Already up to date
return true;
diff --git a/components/password_manager/core/browser/login_database.h b/components/password_manager/core/browser/login_database.h
index 008afa3..ca3db84 100644
--- a/components/password_manager/core/browser/login_database.h
+++ b/components/password_manager/core/browser/login_database.h
@@ -16,6 +16,7 @@
#include "components/password_manager/core/browser/password_store.h"
#include "components/password_manager/core/browser/password_store_change.h"
#include "components/password_manager/core/browser/psl_matching_helper.h"
+#include "components/password_manager/core/browser/statistics_table.h"
#include "sql/connection.h"
#include "sql/meta_table.h"
@@ -108,6 +109,8 @@ class LoginDatabase {
// whether further use of this login database will succeed is unspecified.
bool DeleteAndRecreateDatabaseFile();
+ StatisticsTable& stats_table() { return stats_table_; }
+
private:
// Result values for encryption/decryption actions.
enum EncryptionResult {
@@ -165,6 +168,7 @@ class LoginDatabase {
base::FilePath db_path_;
mutable sql::Connection db_;
sql::MetaTable meta_table_;
+ StatisticsTable stats_table_;
DISALLOW_COPY_AND_ASSIGN(LoginDatabase);
};
diff --git a/components/password_manager/core/browser/mock_password_store.h b/components/password_manager/core/browser/mock_password_store.h
index 70f54e9..7b76b91 100644
--- a/components/password_manager/core/browser/mock_password_store.h
+++ b/components/password_manager/core/browser/mock_password_store.h
@@ -47,6 +47,12 @@ class MockPasswordStore : public PasswordStore {
MOCK_METHOD1(FillBlacklistLogins,
bool(ScopedVector<autofill::PasswordForm>*));
MOCK_METHOD1(NotifyLoginsChanged, void(const PasswordStoreChangeList&));
+ void AddSiteStatsImpl(const InteractionsStats& stats) override {}
+ void RemoveSiteStatsImpl(const GURL& origin_domain) override {}
+ scoped_ptr<InteractionsStats> GetSiteStatsImpl(
+ const GURL& origin_domain) override {
+ return scoped_ptr<InteractionsStats>();
+ }
PasswordStoreSync* GetSyncInterface() { return this; }
diff --git a/components/password_manager/core/browser/password_store.cc b/components/password_manager/core/browser/password_store.cc
index 5b13a3c..4a35791 100644
--- a/components/password_manager/core/browser/password_store.cc
+++ b/components/password_manager/core/browser/password_store.cc
@@ -59,6 +59,13 @@ void PasswordStore::GetLoginsRequest::NotifyConsumerWithResults(
consumer_weak_, base::Passed(&results)));
}
+void PasswordStore::GetLoginsRequest::NotifyWithSiteStatistics(
+ scoped_ptr<InteractionsStats> stats) {
+ origin_loop_->PostTask(FROM_HERE,
+ base::Bind(&PasswordStoreConsumer::OnGetSiteStatistics,
+ consumer_weak_, base::Passed(&stats)));
+}
+
PasswordStore::PasswordStore(
scoped_refptr<base::SingleThreadTaskRunner> main_thread_runner,
scoped_refptr<base::SingleThreadTaskRunner> db_thread_runner)
@@ -163,6 +170,22 @@ void PasswordStore::ReportMetrics(const std::string& sync_username,
}
}
+void PasswordStore::AddSiteStats(const InteractionsStats& stats) {
+ ScheduleTask(base::Bind(&PasswordStore::AddSiteStatsImpl, this, stats));
+}
+
+void PasswordStore::RemoveSiteStats(const GURL& origin_domain) {
+ ScheduleTask(
+ base::Bind(&PasswordStore::RemoveSiteStatsImpl, this, origin_domain));
+}
+
+void PasswordStore::GetSiteStats(const GURL& origin_domain,
+ PasswordStoreConsumer* consumer) {
+ scoped_ptr<GetLoginsRequest> request(new GetLoginsRequest(consumer));
+ ScheduleTask(base::Bind(&PasswordStore::NotifySiteStats, this, origin_domain,
+ base::Passed(&request)));
+}
+
void PasswordStore::AddObserver(Observer* observer) {
observers_->AddObserver(observer);
}
@@ -302,6 +325,11 @@ void PasswordStore::RemoveLoginsSyncedBetweenInternal(base::Time delete_begin,
NotifyLoginsChanged(changes);
}
+void PasswordStore::NotifySiteStats(const GURL& origin_domain,
+ scoped_ptr<GetLoginsRequest> request) {
+ request->NotifyWithSiteStatistics(GetSiteStatsImpl(origin_domain));
+}
+
void PasswordStore::GetLoginsWithAffiliationsImpl(
const PasswordForm& form,
AuthorizationPromptPolicy prompt_policy,
diff --git a/components/password_manager/core/browser/password_store.h b/components/password_manager/core/browser/password_store.h
index 74ff469..c1194cf 100644
--- a/components/password_manager/core/browser/password_store.h
+++ b/components/password_manager/core/browser/password_store.h
@@ -15,6 +15,7 @@
#include "base/time/time.h"
#include "components/password_manager/core/browser/password_store_change.h"
#include "components/password_manager/core/browser/password_store_sync.h"
+#include "components/password_manager/core/browser/statistics_table.h"
#include "sync/api/syncable_service.h"
namespace autofill {
@@ -128,6 +129,16 @@ class PasswordStore : protected PasswordStoreSync,
virtual void ReportMetrics(const std::string& sync_username,
bool custom_passphrase_sync_enabled);
+ // Adds or replaces the statistics for the domain |stats.origin_domain|.
+ void AddSiteStats(const InteractionsStats& stats);
+
+ // Removes the statistics for |origin_domain|.
+ void RemoveSiteStats(const GURL& origin_domain);
+
+ // Retrieves the statistics for |origin_domain| and notifies |consumer| on
+ // completion. The request will be cancelled if the consumer is destroyed.
+ void GetSiteStats(const GURL& origin_domain, PasswordStoreConsumer* consumer);
+
// Adds an observer to be notified when the password store data changes.
void AddObserver(Observer* observer);
@@ -162,6 +173,8 @@ class PasswordStore : protected PasswordStoreSync,
void NotifyConsumerWithResults(
ScopedVector<autofill::PasswordForm> results);
+ void NotifyWithSiteStatistics(scoped_ptr<InteractionsStats> stats);
+
void set_ignore_logins_cutoff(base::Time cutoff) {
ignore_logins_cutoff_ = cutoff;
}
@@ -235,6 +248,13 @@ class PasswordStore : protected PasswordStoreSync,
// Finds all blacklist PasswordForms, and notifies the consumer.
virtual void GetBlacklistLoginsImpl(scoped_ptr<GetLoginsRequest> request) = 0;
+ // Synchronous implementation for manipulating with statistics.
+ virtual void AddSiteStatsImpl(const InteractionsStats& stats) = 0;
+ virtual void RemoveSiteStatsImpl(const GURL& origin_domain) = 0;
+ // Returns a raw pointer so that InteractionsStats can be forward declared.
+ virtual scoped_ptr<InteractionsStats> GetSiteStatsImpl(
+ const GURL& origin_domain) WARN_UNUSED_RESULT = 0;
+
// Log UMA stats for number of bulk deletions.
void LogStatsForBulkDeletion(int num_deletions);
@@ -288,6 +308,10 @@ class PasswordStore : protected PasswordStoreSync,
void RemoveLoginsSyncedBetweenInternal(base::Time delete_begin,
base::Time delete_end);
+ // Notifies |request| about the stats for |origin_domain|.
+ void NotifySiteStats(const GURL& origin_domain,
+ scoped_ptr<GetLoginsRequest> request);
+
// Extended version of GetLoginsImpl that also returns credentials stored for
// the specified affiliated Android applications. That is, it finds all
// PasswordForms with a signon_realm that is either:
diff --git a/components/password_manager/core/browser/password_store_consumer.cc b/components/password_manager/core/browser/password_store_consumer.cc
index da62aed..8347f2b 100644
--- a/components/password_manager/core/browser/password_store_consumer.cc
+++ b/components/password_manager/core/browser/password_store_consumer.cc
@@ -4,6 +4,8 @@
#include "components/password_manager/core/browser/password_store_consumer.h"
+#include "components/password_manager/core/browser/statistics_table.h"
+
namespace password_manager {
PasswordStoreConsumer::PasswordStoreConsumer() : weak_ptr_factory_(this) {
@@ -12,4 +14,8 @@ PasswordStoreConsumer::PasswordStoreConsumer() : weak_ptr_factory_(this) {
PasswordStoreConsumer::~PasswordStoreConsumer() {
}
+void PasswordStoreConsumer::OnGetSiteStatistics(
+ scoped_ptr<InteractionsStats> stats) {
+}
+
} // namespace password_manager
diff --git a/components/password_manager/core/browser/password_store_consumer.h b/components/password_manager/core/browser/password_store_consumer.h
index 1ffdae3..b8934995 100644
--- a/components/password_manager/core/browser/password_store_consumer.h
+++ b/components/password_manager/core/browser/password_store_consumer.h
@@ -16,6 +16,8 @@ struct PasswordForm;
namespace password_manager {
+struct InteractionsStats;
+
// Reads from the PasswordStore are done asynchronously on a separate
// thread. PasswordStoreConsumer provides the virtual callback method, which is
// guaranteed to be executed on this (the UI) thread. It also provides the
@@ -25,10 +27,13 @@ class PasswordStoreConsumer {
public:
PasswordStoreConsumer();
- // Called when the request is finished, with the associated |results|.
+ // Called when the GetLogins() request is finished, with the associated
+ // |results|.
virtual void OnGetPasswordStoreResults(
ScopedVector<autofill::PasswordForm> results) = 0;
+ virtual void OnGetSiteStatistics(scoped_ptr<InteractionsStats> stats);
+
// The base::CancelableTaskTracker can be used for cancelling the
// tasks associated with the consumer.
base::CancelableTaskTracker* cancelable_task_tracker() {
diff --git a/components/password_manager/core/browser/password_store_default.cc b/components/password_manager/core/browser/password_store_default.cc
index 4e24ad1..54be8f8 100644
--- a/components/password_manager/core/browser/password_store_default.cc
+++ b/components/password_manager/core/browser/password_store_default.cc
@@ -148,4 +148,23 @@ bool PasswordStoreDefault::FillBlacklistLogins(
return login_db_ && login_db_->GetBlacklistLogins(forms);
}
+void PasswordStoreDefault::AddSiteStatsImpl(const InteractionsStats& stats) {
+ DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread());
+ if (login_db_)
+ login_db_->stats_table().AddRow(stats);
+}
+
+void PasswordStoreDefault::RemoveSiteStatsImpl(const GURL& origin_domain) {
+ DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread());
+ if (login_db_)
+ login_db_->stats_table().RemoveRow(origin_domain);
+}
+
+scoped_ptr<InteractionsStats> PasswordStoreDefault::GetSiteStatsImpl(
+ const GURL& origin_domain) {
+ DCHECK(GetBackgroundTaskRunner()->BelongsToCurrentThread());
+ return login_db_ ? login_db_->stats_table().GetRow(origin_domain)
+ : scoped_ptr<InteractionsStats>();
+}
+
} // namespace password_manager
diff --git a/components/password_manager/core/browser/password_store_default.h b/components/password_manager/core/browser/password_store_default.h
index ecfd38f..ab02004 100644
--- a/components/password_manager/core/browser/password_store_default.h
+++ b/components/password_manager/core/browser/password_store_default.h
@@ -59,8 +59,11 @@ class PasswordStoreDefault : public PasswordStore {
ScopedVector<autofill::PasswordForm>* forms) override;
bool FillBlacklistLogins(
ScopedVector<autofill::PasswordForm>* forms) override;
+ void AddSiteStatsImpl(const InteractionsStats& stats) override;
+ void RemoveSiteStatsImpl(const GURL& origin_domain) override;
+ scoped_ptr<InteractionsStats> GetSiteStatsImpl(
+ const GURL& origin_domain) override;
- protected:
inline bool DeleteAndRecreateDatabaseFile() {
return login_db_->DeleteAndRecreateDatabaseFile();
}
diff --git a/components/password_manager/core/browser/statistics_table.cc b/components/password_manager/core/browser/statistics_table.cc
new file mode 100644
index 0000000..1246ee9
--- /dev/null
+++ b/components/password_manager/core/browser/statistics_table.cc
@@ -0,0 +1,83 @@
+// Copyright 2015 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/statistics_table.h"
+
+#include "sql/connection.h"
+#include "sql/statement.h"
+
+namespace password_manager {
+namespace {
+
+// Convenience enum for interacting with SQL queries that use all the columns.
+enum LoginTableColumns {
+ COLUMN_ORIGIN_DOMAIN = 0,
+ COLUMN_NOPES,
+ COLUMN_DISMISSALS,
+ COLUMN_DATE,
+};
+
+} // namespace
+
+StatisticsTable::StatisticsTable() : db_(nullptr) {
+}
+
+StatisticsTable::~StatisticsTable() {
+}
+
+bool StatisticsTable::Init(sql::Connection* db) {
+ db_ = db;
+ if (!db_->DoesTableExist("stats")) {
+ const char query[] =
+ "CREATE TABLE stats ("
+ "origin_domain VARCHAR NOT NULL PRIMARY KEY, "
+ "nopes_count INTEGER, "
+ "dismissal_count INTEGER, "
+ "start_date INTEGER NOT NULL)";
+ if (!db_->Execute(query))
+ return false;
+ }
+ return true;
+}
+
+bool StatisticsTable::AddRow(const InteractionsStats& stats) {
+ sql::Statement s(db_->GetCachedStatement(
+ SQL_FROM_HERE,
+ "INSERT OR REPLACE INTO stats "
+ "(origin_domain, nopes_count, dismissal_count, start_date) "
+ "VALUES (?, ?, ?, ?)"));
+ s.BindString(COLUMN_ORIGIN_DOMAIN, stats.origin_domain.spec());
+ s.BindInt(COLUMN_NOPES, stats.nopes_count);
+ s.BindInt(COLUMN_DISMISSALS, stats.dismissal_count);
+ s.BindInt64(COLUMN_DATE, stats.start_date.ToInternalValue());
+ return s.Run();
+}
+
+bool StatisticsTable::RemoveRow(const GURL& domain) {
+ sql::Statement s(db_->GetCachedStatement(SQL_FROM_HERE,
+ "DELETE FROM stats WHERE "
+ "origin_domain = ? "));
+ s.BindString(0, domain.spec());
+ return s.Run();
+}
+
+scoped_ptr<InteractionsStats> StatisticsTable::GetRow(const GURL& domain) {
+ const char query[] =
+ "SELECT origin_domain, nopes_count, "
+ "dismissal_count, start_date FROM stats WHERE origin_domain == ?";
+ sql::Statement s(db_->GetCachedStatement(SQL_FROM_HERE, query));
+ s.BindString(0, domain.spec());
+ if (s.Step()) {
+ scoped_ptr<InteractionsStats> stats(new InteractionsStats);
+ stats->origin_domain = GURL(s.ColumnString(COLUMN_ORIGIN_DOMAIN));
+ stats->nopes_count = s.ColumnInt(COLUMN_NOPES);
+ stats->dismissal_count = s.ColumnInt(COLUMN_DISMISSALS);
+ stats->start_date =
+ base::Time::FromInternalValue(s.ColumnInt64(COLUMN_DATE));
+ return stats.Pass();
+ }
+ return scoped_ptr<InteractionsStats>();
+}
+
+} // namespace password_manager
diff --git a/components/password_manager/core/browser/statistics_table.h b/components/password_manager/core/browser/statistics_table.h
new file mode 100644
index 0000000..d135309
--- /dev/null
+++ b/components/password_manager/core/browser/statistics_table.h
@@ -0,0 +1,61 @@
+// Copyright 2015 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.
+
+#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_STATISTICS_TABLE_H_
+#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_STATISTICS_TABLE_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "url/gurl.h"
+
+namespace sql {
+class Connection;
+}
+
+namespace password_manager {
+
+// The statistics containing user interactions with a site.
+struct InteractionsStats {
+ // The domain of the site.
+ GURL origin_domain;
+
+ // Number of times the user clicked "Don't save the password".
+ int nopes_count;
+
+ // Number of times the user dismissed the bubble.
+ int dismissal_count;
+
+ // The beginning date of the measurements.
+ base::Time start_date;
+};
+
+// Represents 'stats' table in the Login Database.
+class StatisticsTable {
+ public:
+ StatisticsTable();
+ ~StatisticsTable();
+
+ // Initializes |db_| and creates the statistics table if it doesn't exist.
+ bool Init(sql::Connection* db);
+
+ // Adds or replaces the statistics about |stats.origin_domain|.
+ bool AddRow(const InteractionsStats& stats);
+
+ // Removes the statistics for |domain|. Returns true if the SQL completed
+ // successfully.
+ bool RemoveRow(const GURL& domain);
+
+ // Returns the statistics for |domain| if it exists.
+ scoped_ptr<InteractionsStats> GetRow(const GURL& domain);
+
+ private:
+ sql::Connection* db_;
+
+ DISALLOW_COPY_AND_ASSIGN(StatisticsTable);
+};
+
+} // namespace password_manager
+
+#endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_STATISTICS_TABLE_H_
diff --git a/components/password_manager/core/browser/statistics_table_unittest.cc b/components/password_manager/core/browser/statistics_table_unittest.cc
new file mode 100644
index 0000000..c3d28ff
--- /dev/null
+++ b/components/password_manager/core/browser/statistics_table_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright 2015 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/statistics_table.h"
+
+#include "base/files/scoped_temp_dir.h"
+#include "sql/connection.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace password_manager {
+namespace {
+
+const char kTestDomain[] = "http://google.com";
+
+void CheckStatsAreEqual(const InteractionsStats& left,
+ const InteractionsStats& right) {
+ EXPECT_EQ(left.origin_domain, right.origin_domain);
+ EXPECT_EQ(left.nopes_count, right.nopes_count);
+ EXPECT_EQ(left.dismissal_count, right.dismissal_count);
+ EXPECT_EQ(left.start_date, right.start_date);
+}
+
+class StatisticsTableTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ ReloadDatabase();
+
+ test_data_.origin_domain = GURL(kTestDomain);
+ test_data_.nopes_count = 5;
+ test_data_.dismissal_count = 10;
+ test_data_.start_date = base::Time::FromTimeT(1);
+ }
+
+ void ReloadDatabase() {
+ base::FilePath file = temp_dir_.path().AppendASCII("TestDatabase");
+ db_.reset(new StatisticsTable);
+ connection_.reset(new sql::Connection);
+ connection_->set_exclusive_locking();
+ ASSERT_TRUE(connection_->Open(file));
+ ASSERT_TRUE(db_->Init(connection_.get()));
+ }
+
+ InteractionsStats& test_data() { return test_data_; }
+ StatisticsTable* db() { return db_.get(); }
+
+ private:
+ base::ScopedTempDir temp_dir_;
+ scoped_ptr<sql::Connection> connection_;
+ scoped_ptr<StatisticsTable> db_;
+ InteractionsStats test_data_;
+};
+
+TEST_F(StatisticsTableTest, Sanity) {
+ EXPECT_TRUE(db()->AddRow(test_data()));
+ scoped_ptr<InteractionsStats> stats = db()->GetRow(test_data().origin_domain);
+ ASSERT_TRUE(stats);
+ CheckStatsAreEqual(test_data(), *stats);
+ EXPECT_TRUE(db()->RemoveRow(test_data().origin_domain));
+ EXPECT_FALSE(db()->GetRow(test_data().origin_domain));
+}
+
+TEST_F(StatisticsTableTest, Reload) {
+ EXPECT_TRUE(db()->AddRow(test_data()));
+ EXPECT_TRUE(db()->GetRow(test_data().origin_domain));
+
+ ReloadDatabase();
+
+ scoped_ptr<InteractionsStats> stats = db()->GetRow(test_data().origin_domain);
+ ASSERT_TRUE(stats);
+ CheckStatsAreEqual(test_data(), *stats);
+}
+
+TEST_F(StatisticsTableTest, DoubleOperation) {
+ EXPECT_TRUE(db()->AddRow(test_data()));
+ test_data().nopes_count++;
+ EXPECT_TRUE(db()->AddRow(test_data()));
+
+ scoped_ptr<InteractionsStats> stats = db()->GetRow(test_data().origin_domain);
+ ASSERT_TRUE(stats);
+ CheckStatsAreEqual(test_data(), *stats);
+
+ EXPECT_TRUE(db()->RemoveRow(test_data().origin_domain));
+ EXPECT_FALSE(db()->GetRow(test_data().origin_domain));
+ EXPECT_TRUE(db()->RemoveRow(test_data().origin_domain));
+ EXPECT_FALSE(db()->GetRow(test_data().origin_domain));
+}
+
+} // namespace
+} // namespace password_manager
diff --git a/components/password_manager/core/browser/test_password_store.cc b/components/password_manager/core/browser/test_password_store.cc
index a420e9e..60f2536 100644
--- a/components/password_manager/core/browser/test_password_store.cc
+++ b/components/password_manager/core/browser/test_password_store.cc
@@ -139,4 +139,15 @@ bool TestPasswordStore::FillBlacklistLogins(
return true;
}
+void TestPasswordStore::AddSiteStatsImpl(const InteractionsStats& stats) {
+}
+
+void TestPasswordStore::RemoveSiteStatsImpl(const GURL& origin_domain) {
+}
+
+scoped_ptr<InteractionsStats> TestPasswordStore::GetSiteStatsImpl(
+ const GURL& origin_domain) {
+ return scoped_ptr<InteractionsStats>();
+}
+
} // namespace password_manager
diff --git a/components/password_manager/core/browser/test_password_store.h b/components/password_manager/core/browser/test_password_store.h
index 96f6104..72addcd 100644
--- a/components/password_manager/core/browser/test_password_store.h
+++ b/components/password_manager/core/browser/test_password_store.h
@@ -66,6 +66,10 @@ class TestPasswordStore : public PasswordStore {
ScopedVector<autofill::PasswordForm>* forms) override;
bool FillBlacklistLogins(
ScopedVector<autofill::PasswordForm>* forms) override;
+ void AddSiteStatsImpl(const InteractionsStats& stats) override;
+ void RemoveSiteStatsImpl(const GURL& origin_domain) override;
+ scoped_ptr<InteractionsStats> GetSiteStatsImpl(
+ const GURL& origin_domain) override;
private:
PasswordMap stored_passwords_;
diff --git a/components/test/data/password_manager/login_db_v12.sql b/components/test/data/password_manager/login_db_v12.sql
new file mode 100644
index 0000000..6f746fe
--- /dev/null
+++ b/components/test/data/password_manager/login_db_v12.sql
@@ -0,0 +1,82 @@
+PRAGMA foreign_keys=OFF;
+BEGIN TRANSACTION;
+CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
+INSERT INTO "meta" VALUES('last_compatible_version','1');
+INSERT INTO "meta" VALUES('version','12');
+CREATE TABLE logins (
+origin_url VARCHAR NOT NULL,
+action_url VARCHAR,
+username_element VARCHAR,
+username_value VARCHAR,
+password_element VARCHAR,
+password_value BLOB,
+submit_element VARCHAR,
+signon_realm VARCHAR NOT NULL,
+ssl_valid INTEGER NOT NULL,
+preferred INTEGER NOT NULL,
+date_created INTEGER NOT NULL,
+blacklisted_by_user INTEGER NOT NULL,
+scheme INTEGER NOT NULL,
+password_type INTEGER,
+possible_usernames BLOB,
+times_used INTEGER,
+form_data BLOB,
+date_synced INTEGER,
+display_name VARCHAR,
+avatar_url VARCHAR,
+federation_url VARCHAR,
+skip_zero_click INTEGER,
+generation_upload_status INTEGER,
+UNIQUE (origin_url, username_element, username_value, password_element, signon_realm));
+INSERT INTO "logins" VALUES(
+'https://accounts.google.com/ServiceLogin', /* origin_url */
+'https://accounts.google.com/ServiceLoginAuth', /* action_url */
+'Email', /* username_element */
+'theerikchen', /* username_value */
+'Passwd', /* password_element */
+X'', /* password_value */
+'', /* submit_element */
+'https://accounts.google.com/', /* signon_realm */
+1, /* ssl_valid */
+1, /* preferred */
+13047429345000000, /* date_created */
+0, /* blacklisted_by_user */
+0, /* scheme */
+0, /* password_type */
+X'00000000', /* possible_usernames */
+1, /* times_used */
+X'18000000020000000000000000000000000000000000000000000000', /* form_data */
+0, /* date_synced */
+'', /* display_name */
+'', /* avatar_url */
+'', /* federation_url */
+0, /* skip_zero_click */
+0 /* generation_upload_status */
+);
+INSERT INTO "logins" VALUES(
+'https://accounts.google.com/ServiceLogin', /* origin_url */
+'https://accounts.google.com/ServiceLoginAuth', /* action_url */
+'Email', /* username_element */
+'theerikchen2', /* username_value */
+'Passwd', /* password_element */
+X'', /* password_value */
+'non-empty', /* submit_element */
+'https://accounts.google.com/', /* signon_realm */
+1, /* ssl_valid */
+1, /* preferred */
+13047423600000000, /* date_created */
+0, /* blacklisted_by_user */
+0, /* scheme */
+0, /* password_type */
+X'00000000', /* possible_usernames */
+1, /* times_used */
+X'18000000020000000000000000000000000000000000000000000000', /* form_data */
+0, /* date_synced */
+'', /* display_name */
+'', /* avatar_url */
+'', /* federation_url */
+0, /* skip_zero_click */
+0 /* generation_upload_status */
+);
+CREATE INDEX logins_signon ON logins (signon_realm);
+COMMIT;