[Autofill] Added LocalCardMigrationStrikeDB

Intruduced LocalCardMigrationStrikeDatabase and added an AddStrikes
and RemoveStrikes functions, which allows multiple strikes to be added
at once.

Bug: 920385
Change-Id: Ifbfad49c9d67570098b0e71d7a3fda1800563413
Reviewed-on: https://chromium-review.googlesource.com/c/1422701
Reviewed-by: Sebastien Seguin-Gagnon <sebsg@chromium.org>
Reviewed-by: Jared Saul <jsaul@google.com>
Commit-Queue: Anne Lim <annelim@google.com>
Cr-Commit-Position: refs/heads/master@{#625369}
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index d6855a9..46afe629 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -131,6 +131,8 @@
     "legal_message_line.h",
     "local_card_migration_manager.cc",
     "local_card_migration_manager.h",
+    "local_card_migration_strike_database.cc",
+    "local_card_migration_strike_database.h",
     "name_field.cc",
     "name_field.h",
     "password_requirements_spec_fetcher.h",
@@ -381,6 +383,8 @@
     "test_legacy_strike_database.h",
     "test_local_card_migration_manager.cc",
     "test_local_card_migration_manager.h",
+    "test_local_card_migration_strike_database.cc",
+    "test_local_card_migration_strike_database.h",
     "test_personal_data_manager.cc",
     "test_personal_data_manager.h",
     "test_region_data_loader.cc",
@@ -516,6 +520,7 @@
     "legacy_strike_database_unittest.cc",
     "legal_message_line_unittest.cc",
     "local_card_migration_manager_unittest.cc",
+    "local_card_migration_strike_database_unittest.cc",
     "name_field_unittest.cc",
     "password_generator_fips181_unittest.cc",
     "password_generator_unittest.cc",
diff --git a/components/autofill/core/browser/credit_card_save_strike_database.cc b/components/autofill/core/browser/credit_card_save_strike_database.cc
index d2f0380..5df822f 100644
--- a/components/autofill/core/browser/credit_card_save_strike_database.cc
+++ b/components/autofill/core/browser/credit_card_save_strike_database.cc
@@ -29,4 +29,8 @@
   return (long long)1000000 * 60 * 60 * 24 * 180;
 }
 
+bool CreditCardSaveStrikeDatabase::UniqueIdsRequired() {
+  return true;
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/credit_card_save_strike_database.h b/components/autofill/core/browser/credit_card_save_strike_database.h
index 7ce7dbb..5376822 100644
--- a/components/autofill/core/browser/credit_card_save_strike_database.h
+++ b/components/autofill/core/browser/credit_card_save_strike_database.h
@@ -22,6 +22,7 @@
   std::string GetProjectPrefix() override;
   int GetMaxStrikesLimit() override;
   long long GetExpiryTimeMicros() override;
+  bool UniqueIdsRequired() override;
 };
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/local_card_migration_strike_database.cc b/components/autofill/core/browser/local_card_migration_strike_database.cc
new file mode 100644
index 0000000..6125d44
--- /dev/null
+++ b/components/autofill/core/browser/local_card_migration_strike_database.cc
@@ -0,0 +1,36 @@
+// Copyright 2019 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/autofill/core/browser/local_card_migration_strike_database.h"
+
+#include "components/autofill/core/browser/proto/strike_data.pb.h"
+
+namespace autofill {
+
+LocalCardMigrationStrikeDatabase::LocalCardMigrationStrikeDatabase(
+    StrikeDatabase* strike_database)
+    : StrikeDatabaseIntegratorBase(strike_database) {
+  RemoveExpiredStrikes();
+}
+
+LocalCardMigrationStrikeDatabase::~LocalCardMigrationStrikeDatabase() {}
+
+std::string LocalCardMigrationStrikeDatabase::GetProjectPrefix() {
+  return "LocalCardMigration";
+}
+
+int LocalCardMigrationStrikeDatabase::GetMaxStrikesLimit() {
+  return 6;
+}
+
+long long LocalCardMigrationStrikeDatabase::GetExpiryTimeMicros() {
+  // Expiry time is 1 year.
+  return (long long)1000000 * 60 * 60 * 24 * 365;
+}
+
+bool LocalCardMigrationStrikeDatabase::UniqueIdsRequired() {
+  return false;
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/local_card_migration_strike_database.h b/components/autofill/core/browser/local_card_migration_strike_database.h
new file mode 100644
index 0000000..ab25114
--- /dev/null
+++ b/components/autofill/core/browser/local_card_migration_strike_database.h
@@ -0,0 +1,29 @@
+// Copyright 2019 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_AUTOFILL_CORE_BROWSER_LOCAL_CARD_MIGRATION_STRIKE_DATABASE_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_LOCAL_CARD_MIGRATION_STRIKE_DATABASE_H_
+
+#include <string>
+
+#include "components/autofill/core/browser/strike_database.h"
+#include "components/autofill/core/browser/strike_database_integrator_base.h"
+
+namespace autofill {
+
+// Implementation of StrikeDatabaseIntegratorBase for local card migrations.
+class LocalCardMigrationStrikeDatabase : public StrikeDatabaseIntegratorBase {
+ public:
+  LocalCardMigrationStrikeDatabase(StrikeDatabase* strike_database);
+  ~LocalCardMigrationStrikeDatabase() override;
+
+  std::string GetProjectPrefix() override;
+  int GetMaxStrikesLimit() override;
+  long long GetExpiryTimeMicros() override;
+  bool UniqueIdsRequired() override;
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_LOCAL_CARD_MIGRATION_STRIKE_DATABASE_H_
diff --git a/components/autofill/core/browser/local_card_migration_strike_database_unittest.cc b/components/autofill/core/browser/local_card_migration_strike_database_unittest.cc
new file mode 100644
index 0000000..c7d3ad0
--- /dev/null
+++ b/components/autofill/core/browser/local_card_migration_strike_database_unittest.cc
@@ -0,0 +1,111 @@
+// Copyright 2019 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/autofill/core/browser/local_card_migration_strike_database.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/autofill/core/browser/proto/strike_data.pb.h"
+#include "components/autofill/core/browser/test_autofill_clock.h"
+#include "components/autofill/core/browser/test_local_card_migration_strike_database.h"
+#include "components/autofill/core/common/autofill_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace autofill {
+
+class LocalCardMigrationStrikeDatabaseTest : public ::testing::Test {
+ public:
+  LocalCardMigrationStrikeDatabaseTest()
+      : strike_database_(new StrikeDatabase(InitFilePath())) {}
+
+ protected:
+  base::HistogramTester* GetHistogramTester() { return &histogram_tester_; }
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  TestLocalCardMigrationStrikeDatabase strike_database_;
+
+ private:
+  static const base::FilePath InitFilePath() {
+    base::ScopedTempDir temp_dir_;
+    EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+    const base::FilePath file_path =
+        temp_dir_.GetPath().AppendASCII("StrikeDatabaseTest");
+    return file_path;
+  }
+
+  base::HistogramTester histogram_tester_;
+};
+
+TEST_F(LocalCardMigrationStrikeDatabaseTest, MaxStrikesLimitReachedTest) {
+  EXPECT_EQ(false, strike_database_.IsMaxStrikesLimitReached());
+  // 3 strikes added.
+  strike_database_.AddStrikes(3);
+  EXPECT_EQ(false, strike_database_.IsMaxStrikesLimitReached());
+  // 4 strike added, total strike count is 7.
+  strike_database_.AddStrikes(4);
+  EXPECT_EQ(true, strike_database_.IsMaxStrikesLimitReached());
+}
+
+TEST_F(LocalCardMigrationStrikeDatabaseTest,
+       LocalCardMigrationNthStrikeAddedHistogram) {
+  // 2 strikes logged.
+  strike_database_.AddStrikes(2);
+  strike_database_.RemoveStrikes(2);
+  // 1 strike logged.
+  strike_database_.AddStrike();
+  // 2 strikes logged.
+  strike_database_.AddStrike();
+  std::vector<base::Bucket> buckets = GetHistogramTester()->GetAllSamples(
+      "Autofill.StrikeDatabase.NthStrikeAdded.LocalCardMigration");
+  // There should be two buckets, for strike counts of 1 and 2.
+  ASSERT_EQ(2U, buckets.size());
+  // Bucket for 1 strike should have count of 1.
+  EXPECT_EQ(1, buckets[0].count);
+  // Bucket for 2 strikes should have count of 2.
+  EXPECT_EQ(2, buckets[1].count);
+}
+
+TEST_F(LocalCardMigrationStrikeDatabaseTest,
+       AddStrikeForZeroAndNonZeroStrikesTest) {
+  EXPECT_EQ(0, strike_database_.GetStrikes());
+  strike_database_.AddStrike();
+  EXPECT_EQ(1, strike_database_.GetStrikes());
+  strike_database_.AddStrikes(2);
+  EXPECT_EQ(3, strike_database_.GetStrikes());
+}
+
+TEST_F(LocalCardMigrationStrikeDatabaseTest,
+       ClearStrikesForNonZeroStrikesTest) {
+  strike_database_.AddStrikes(3);
+  EXPECT_EQ(3, strike_database_.GetStrikes());
+  strike_database_.ClearStrikes();
+  EXPECT_EQ(0, strike_database_.GetStrikes());
+}
+
+TEST_F(LocalCardMigrationStrikeDatabaseTest, ClearStrikesForZeroStrikesTest) {
+  strike_database_.ClearStrikes();
+  EXPECT_EQ(0, strike_database_.GetStrikes());
+}
+
+TEST_F(LocalCardMigrationStrikeDatabaseTest, RemoveExpiredStrikesTest) {
+  autofill::TestAutofillClock test_clock;
+  test_clock.SetNow(AutofillClock::Now());
+  strike_database_.AddStrikes(2);
+  EXPECT_EQ(2, strike_database_.GetStrikes());
+
+  // Advance clock to past expiry time.
+  test_clock.Advance(base::TimeDelta::FromMicroseconds(
+      strike_database_.GetExpiryTimeMicros() + 1));
+
+  // One strike should be removed.
+  strike_database_.RemoveExpiredStrikes();
+  EXPECT_EQ(1, strike_database_.GetStrikes());
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/strike_database.cc b/components/autofill/core/browser/strike_database.cc
index a3b85f5b..0e247e7 100644
--- a/components/autofill/core/browser/strike_database.cc
+++ b/components/autofill/core/browser/strike_database.cc
@@ -39,17 +39,20 @@
 
 StrikeDatabase::~StrikeDatabase() {}
 
-int StrikeDatabase::AddStrike(const std::string key) {
-  int num_strikes = strike_map_cache_.count(key)  // Cache has entry for |key|.
-                        ? strike_map_cache_[key].num_strikes() + 1
-                        : 1;
+int StrikeDatabase::AddStrikes(int strikes_increase, const std::string key) {
+  DCHECK(strikes_increase > 0);
+  int num_strikes =
+      strike_map_cache_.count(key)  // Cache has entry for |key|.
+          ? strike_map_cache_[key].num_strikes() + strikes_increase
+          : strikes_increase;
   SetStrikeData(key, num_strikes);
   return num_strikes;
 }
 
-int StrikeDatabase::RemoveStrike(const std::string key) {
+int StrikeDatabase::RemoveStrikes(int strikes_decrease, const std::string key) {
+  DCHECK(strikes_decrease > 0);
   DCHECK(strike_map_cache_.count(key));
-  int num_strikes = strike_map_cache_[key].num_strikes() - 1;
+  int num_strikes = strike_map_cache_[key].num_strikes() - strikes_decrease;
   if (num_strikes < 1) {
     ClearStrikes(key);
     return 0;
diff --git a/components/autofill/core/browser/strike_database.h b/components/autofill/core/browser/strike_database.h
index 3049e0d..bea6da0 100644
--- a/components/autofill/core/browser/strike_database.h
+++ b/components/autofill/core/browser/strike_database.h
@@ -47,12 +47,13 @@
   explicit StrikeDatabase(const base::FilePath& database_dir);
   ~StrikeDatabase() override;
 
-  // Increments in-memory cache and updates underlying ProtoDatabase.
-  int AddStrike(const std::string key);
+  // Increases in-memory cache by |strikes_increase| and updates underlying
+  // ProtoDatabase.
+  int AddStrikes(int strikes_increase, const std::string key);
 
-  // Removes an in-memory cache strike, updates last_update_timestamp, and
-  // updates underlying ProtoDatabase.
-  int RemoveStrike(const std::string key);
+  // Removes |strikes_decrease| in-memory cache strikes, updates
+  // last_update_timestamp, and updates underlying ProtoDatabase.
+  int RemoveStrikes(int strikes_decrease, const std::string key);
 
   // Returns strike count from in-memory cache.
   int GetStrikes(const std::string key);
diff --git a/components/autofill/core/browser/strike_database_integrator_base.cc b/components/autofill/core/browser/strike_database_integrator_base.cc
index 04b9c90..f643819 100644
--- a/components/autofill/core/browser/strike_database_integrator_base.cc
+++ b/components/autofill/core/browser/strike_database_integrator_base.cc
@@ -31,11 +31,19 @@
 
 bool StrikeDatabaseIntegratorBase::IsMaxStrikesLimitReached(
     const std::string id) {
+  CheckIdUniqueness(id);
   return GetStrikes(id) >= GetMaxStrikesLimit();
 }
 
 int StrikeDatabaseIntegratorBase::AddStrike(const std::string id) {
-  int num_strikes = strike_database_->AddStrike(GetKey(id));
+  CheckIdUniqueness(id);
+  return AddStrikes(1, id);
+}
+
+int StrikeDatabaseIntegratorBase::AddStrikes(int strikes_increase,
+                                             const std::string id) {
+  CheckIdUniqueness(id);
+  int num_strikes = strike_database_->AddStrikes(strikes_increase, GetKey(id));
   base::UmaHistogramCounts1000(
       "Autofill.StrikeDatabase.NthStrikeAdded." + GetProjectPrefix(),
       num_strikes);
@@ -43,14 +51,23 @@
 }
 
 int StrikeDatabaseIntegratorBase::RemoveStrike(const std::string id) {
-  return strike_database_->RemoveStrike(GetKey(id));
+  CheckIdUniqueness(id);
+  return strike_database_->RemoveStrikes(1, GetKey(id));
+}
+
+int StrikeDatabaseIntegratorBase::RemoveStrikes(int strikes_decrease,
+                                                const std::string id) {
+  CheckIdUniqueness(id);
+  return strike_database_->RemoveStrikes(strikes_decrease, GetKey(id));
 }
 
 int StrikeDatabaseIntegratorBase::GetStrikes(const std::string id) {
+  CheckIdUniqueness(id);
   return strike_database_->GetStrikes(GetKey(id));
 }
 
 void StrikeDatabaseIntegratorBase::ClearStrikes(const std::string id) {
+  CheckIdUniqueness(id);
   strike_database_->ClearStrikes(GetKey(id));
 }
 
@@ -65,7 +82,7 @@
     }
   }
   for (std::string key : expired_keys)
-    strike_database_->RemoveStrike(key);
+    strike_database_->RemoveStrikes(1, key);
 }
 
 std::string StrikeDatabaseIntegratorBase::GetKey(const std::string id) {
diff --git a/components/autofill/core/browser/strike_database_integrator_base.h b/components/autofill/core/browser/strike_database_integrator_base.h
index 9a7d4261..9f207da 100644
--- a/components/autofill/core/browser/strike_database_integrator_base.h
+++ b/components/autofill/core/browser/strike_database_integrator_base.h
@@ -9,6 +9,10 @@
 
 namespace autofill {
 
+namespace {
+static const char kSharedId[] = "shared_id";
+}  // namespace
+
 // Contains virtual functions for per-project implementations of StrikeDatabase
 // to interface from, as well as a pointer to StrikeDatabase. This class is
 // seperated from StrikeDatabase since we only want StrikeDatabase's cache to
@@ -20,21 +24,29 @@
 
   // Returns whether or not strike count for |id| has reached the strike limit
   // set by GetMaxStrikesLimit().
-  bool IsMaxStrikesLimitReached(const std::string id);
+  bool IsMaxStrikesLimitReached(const std::string id = kSharedId);
 
   // Increments in-memory cache and updates underlying ProtoDatabase.
-  int AddStrike(const std::string id);
+  int AddStrike(const std::string id = kSharedId);
+
+  // Increases in-memory cache by |strikes_increase| and updates underlying
+  // ProtoDatabase.
+  int AddStrikes(int strikes_increase, const std::string id = kSharedId);
 
   // Removes an in-memory cache strike, updates last_update_timestamp, and
   // updates underlying ProtoDatabase.
-  int RemoveStrike(const std::string id);
+  int RemoveStrike(const std::string id = kSharedId);
+
+  // Removes |strikes_decrease| in-memory cache strikes, updates
+  // |last_update_timestamp|, and updates underlying ProtoDatabase.
+  int RemoveStrikes(int strikes_decrease, const std::string id = kSharedId);
 
   // Returns strike count from in-memory cache.
-  int GetStrikes(const std::string id);
+  int GetStrikes(const std::string id = kSharedId);
 
   // Removes all database entries from in-memory cache and underlying
   // ProtoDatabase.
-  void ClearStrikes(const std::string id);
+  void ClearStrikes(const std::string id = kSharedId);
 
  protected:
   // Removes all strikes in which it has been longer than GetExpiryTimeMicros()
@@ -50,11 +62,22 @@
                            GetIdForCreditCardSaveTest);
   FRIEND_TEST_ALL_PREFIXES(CreditCardSaveStrikeDatabaseTest,
                            RemoveExpiredStrikesTest);
+  FRIEND_TEST_ALL_PREFIXES(LocalCardMigrationStrikeDatabaseTest,
+                           RemoveExpiredStrikesTest);
   friend class StrikeDatabaseTest;
   friend class StrikeDatabaseTester;
 
   StrikeDatabase* strike_database_;
 
+  // For projects in which strikes don't have unique identifiers, the
+  // id suffix is set to |kSharedId|. This makes sure that projects requiring
+  // unique IDs always specify |id| instead of relying on the default shared
+  // value, while projects where unique IDs are unnecessary always fall back to
+  // the default shared value.
+  void CheckIdUniqueness(std::string id) {
+    DCHECK(UniqueIdsRequired() == (id != kSharedId));
+  }
+
   // Generates key based on project-specific string identifier.
   std::string GetKey(const std::string id);
 
@@ -68,6 +91,10 @@
 
   // Returns the time after which the most recent strike should expire.
   virtual long long GetExpiryTimeMicros() = 0;
+
+  // Returns whether or not a unique string identifier is required for every
+  // strike in this project.
+  virtual bool UniqueIdsRequired() = 0;
 };
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/test_local_card_migration_strike_database.cc b/components/autofill/core/browser/test_local_card_migration_strike_database.cc
new file mode 100644
index 0000000..cc21271
--- /dev/null
+++ b/components/autofill/core/browser/test_local_card_migration_strike_database.cc
@@ -0,0 +1,13 @@
+// Copyright 2019 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/autofill/core/browser/test_local_card_migration_strike_database.h"
+
+namespace autofill {
+
+TestLocalCardMigrationStrikeDatabase::TestLocalCardMigrationStrikeDatabase(
+    StrikeDatabase* strike_database)
+    : LocalCardMigrationStrikeDatabase(strike_database) {}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/test_local_card_migration_strike_database.h b/components/autofill/core/browser/test_local_card_migration_strike_database.h
new file mode 100644
index 0000000..c1cac18
--- /dev/null
+++ b/components/autofill/core/browser/test_local_card_migration_strike_database.h
@@ -0,0 +1,20 @@
+// Copyright 2019 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_AUTOFILL_CORE_BROWSER_TEST_LOCAL_CARD_MIGRATION_STRIKE_DATABASE_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_LOCAL_CARD_MIGRATION_STRIKE_DATABASE_H_
+
+#include "components/autofill/core/browser/local_card_migration_strike_database.h"
+
+namespace autofill {
+
+class TestLocalCardMigrationStrikeDatabase
+    : public LocalCardMigrationStrikeDatabase {
+ public:
+  TestLocalCardMigrationStrikeDatabase(StrikeDatabase* strike_database);
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_LOCAL_CARD_MIGRATION_STRIKE_DATABASE_H_