// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/metrics/metrics_log_store.h"

#include "components/metrics/metrics_logs_event_manager.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/metrics/test/test_metrics_service_client.h"
#include "components/metrics/unsent_log_store_metrics_impl.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace metrics {
namespace {

const char kTestPrefName[] = "TestPref";

class TestUnsentLogStore : public UnsentLogStore {
 public:
  explicit TestUnsentLogStore(PrefService* service)
      : UnsentLogStore(std::make_unique<UnsentLogStoreMetricsImpl>(),
                       service,
                       kTestPrefName,
                       nullptr,
                       // Set to 3 so logs are not dropped in the test.
                       UnsentLogStore::UnsentLogStoreLimits{
                           .min_log_count = 3,
                       },
                       /*signing_key=*/std::string(),
                       /*logs_event_manager=*/nullptr) {}
  ~TestUnsentLogStore() override = default;

  TestUnsentLogStore(const TestUnsentLogStore&) = delete;
  TestUnsentLogStore& operator=(const TestUnsentLogStore&) = delete;

  static void RegisterPrefs(PrefRegistrySimple* registry) {
    registry->RegisterListPref(kTestPrefName);
  }
};

class MetricsLogStoreTest : public testing::Test {
 public:
  MetricsLogStoreTest() {
    MetricsLogStore::RegisterPrefs(pref_service_.registry());
    TestUnsentLogStore::RegisterPrefs(pref_service_.registry());
  }

  MetricsLogStoreTest(const MetricsLogStoreTest&) = delete;
  MetricsLogStoreTest& operator=(const MetricsLogStoreTest&) = delete;

  ~MetricsLogStoreTest() override {}

  MetricsLog* CreateLog(MetricsLog::LogType log_type) {
    return new MetricsLog("0a94430b-18e5-43c8-a657-580f7e855ce1", 0, log_type,
                          &client_);
  }

  // Returns the stored number of logs of the given type.
  size_t TypeCount(MetricsLog::LogType log_type) {
    const char* pref = log_type == MetricsLog::INITIAL_STABILITY_LOG
                           ? prefs::kMetricsInitialLogs
                           : prefs::kMetricsOngoingLogs;
    return pref_service_.GetList(pref).size();
  }

  TestMetricsServiceClient client_;
  TestingPrefServiceSimple pref_service_;
};

}  // namespace

TEST_F(MetricsLogStoreTest, StandardFlow) {
  MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                            /*signing_key=*/std::string(),
                            /*logs_event_manager=*/nullptr);
  log_store.LoadPersistedUnsentLogs();

  // Make sure a new manager has a clean slate.
  EXPECT_FALSE(log_store.has_staged_log());
  EXPECT_FALSE(log_store.has_unsent_logs());

  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);
  EXPECT_TRUE(log_store.has_unsent_logs());
  EXPECT_FALSE(log_store.has_staged_log());

  log_store.StageNextLog();
  EXPECT_TRUE(log_store.has_staged_log());
  EXPECT_FALSE(log_store.staged_log().empty());

  log_store.DiscardStagedLog();
  EXPECT_FALSE(log_store.has_staged_log());
  EXPECT_FALSE(log_store.has_unsent_logs());
}

TEST_F(MetricsLogStoreTest, StoreAndLoad) {
  // Set up some in-progress logging in a scoped log manager simulating the
  // leadup to quitting, then persist as would be done on quit.
  {
    MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                              /*signing_key=*/std::string(),
                              /*logs_event_manager=*/nullptr);
    log_store.LoadPersistedUnsentLogs();
    EXPECT_FALSE(log_store.has_unsent_logs());
    log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata(),
                       MetricsLogsEventManager::CreateReason::kUnknown);
    log_store.TrimAndPersistUnsentLogs(/*overwrite_in_memory_store=*/true);
    EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
    EXPECT_EQ(1U, TypeCount(MetricsLog::ONGOING_LOG));
  }

  // Relaunch load and store more logs.
  {
    MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                              /*signing_key=*/std::string(),
                              /*logs_event_manager=*/nullptr);
    log_store.LoadPersistedUnsentLogs();
    EXPECT_TRUE(log_store.has_unsent_logs());
    EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
    EXPECT_EQ(1U, TypeCount(MetricsLog::ONGOING_LOG));
    log_store.StoreLog("x", MetricsLog::INITIAL_STABILITY_LOG, LogMetadata(),
                       MetricsLogsEventManager::CreateReason::kUnknown);
    log_store.StageNextLog();
    log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata(),
                       MetricsLogsEventManager::CreateReason::kUnknown);

    EXPECT_TRUE(log_store.has_unsent_logs());
    EXPECT_TRUE(log_store.has_staged_log());
    EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
    EXPECT_EQ(1U, TypeCount(MetricsLog::ONGOING_LOG));

    log_store.TrimAndPersistUnsentLogs(/*overwrite_in_memory_store=*/true);
    EXPECT_EQ(1U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
    EXPECT_EQ(2U, TypeCount(MetricsLog::ONGOING_LOG));
  }

  // Relaunch and verify that once logs are handled they are not re-persisted.
  {
    MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                              /*signing_key=*/std::string(),
                              /*logs_event_manager=*/nullptr);
    log_store.LoadPersistedUnsentLogs();
    EXPECT_TRUE(log_store.has_unsent_logs());

    log_store.StageNextLog();
    log_store.DiscardStagedLog();
    // The initial log should be sent first; update the persisted storage to
    // verify.
    log_store.TrimAndPersistUnsentLogs(/*overwrite_in_memory_store=*/true);
    EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
    EXPECT_EQ(2U, TypeCount(MetricsLog::ONGOING_LOG));

    // Handle the first ongoing log.
    log_store.StageNextLog();
    log_store.DiscardStagedLog();
    EXPECT_TRUE(log_store.has_unsent_logs());

    // Handle the last log.
    log_store.StageNextLog();
    log_store.DiscardStagedLog();
    EXPECT_FALSE(log_store.has_unsent_logs());

    // Nothing should have changed "on disk" since TrimAndPersistUnsentLogs
    // hasn't been called again.
    EXPECT_EQ(2U, TypeCount(MetricsLog::ONGOING_LOG));
    // Persist, and make sure nothing is left.
    log_store.TrimAndPersistUnsentLogs(/*overwrite_in_memory_store=*/true);
    EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
    EXPECT_EQ(0U, TypeCount(MetricsLog::ONGOING_LOG));
  }
}

TEST_F(MetricsLogStoreTest, StoreStagedOngoingLog) {
  // Ensure that types are preserved when storing staged logs.
  MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                            /*signing_key=*/std::string(),
                            /*logs_event_manager=*/nullptr);
  log_store.LoadPersistedUnsentLogs();
  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);
  log_store.StageNextLog();
  log_store.TrimAndPersistUnsentLogs(/*overwrite_in_memory_store=*/true);

  EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
  EXPECT_EQ(1U, TypeCount(MetricsLog::ONGOING_LOG));
}

TEST_F(MetricsLogStoreTest, StoreStagedInitialLog) {
  // Ensure that types are preserved when storing staged logs.
  MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                            /*signing_key=*/std::string(),
                            /*logs_event_manager=*/nullptr);
  log_store.LoadPersistedUnsentLogs();
  log_store.StoreLog("b", MetricsLog::INITIAL_STABILITY_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);
  log_store.StageNextLog();
  log_store.TrimAndPersistUnsentLogs(/*overwrite_in_memory_store=*/true);

  EXPECT_EQ(1U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
  EXPECT_EQ(0U, TypeCount(MetricsLog::ONGOING_LOG));
}

TEST_F(MetricsLogStoreTest, LargeLogDiscarding) {
  // Set the size threshold very low, to verify that it's honored.
  client_.set_max_ongoing_log_size_bytes(1);
  MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                            /*signing_key=*/std::string(),
                            /*logs_event_manager=*/nullptr);
  log_store.LoadPersistedUnsentLogs();

  log_store.StoreLog("persisted", MetricsLog::INITIAL_STABILITY_LOG,
                     LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);
  log_store.StoreLog("not_persisted", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);

  // Only the stability log should be written out, due to the threshold.
  log_store.TrimAndPersistUnsentLogs(/*overwrite_in_memory_store=*/true);
  EXPECT_EQ(1U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
  EXPECT_EQ(0U, TypeCount(MetricsLog::ONGOING_LOG));
}

TEST_F(MetricsLogStoreTest, DiscardOrder) {
  // Ensure that the correct log is discarded if new logs are pushed while
  // a log is staged.
  MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                            /*signing_key=*/std::string(),
                            /*logs_event_manager=*/nullptr);
  log_store.LoadPersistedUnsentLogs();

  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);
  log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);
  log_store.StageNextLog();
  log_store.StoreLog("c", MetricsLog::INITIAL_STABILITY_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);
  EXPECT_EQ(2U, log_store.ongoing_log_count());
  EXPECT_EQ(1U, log_store.initial_log_count());
  // Should discard the ongoing log staged earlier.
  log_store.DiscardStagedLog();
  EXPECT_EQ(1U, log_store.ongoing_log_count());
  EXPECT_EQ(1U, log_store.initial_log_count());
  // Initial log should be staged next.
  log_store.StageNextLog();
  log_store.DiscardStagedLog();
  EXPECT_EQ(1U, log_store.ongoing_log_count());
  EXPECT_EQ(0U, log_store.initial_log_count());
}

TEST_F(MetricsLogStoreTest, WritesToAlternateOngoingLogStore) {
  MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                            /*signing_key=*/std::string(),
                            /*logs_event_manager=*/nullptr);
  std::unique_ptr<TestUnsentLogStore> alternate_ongoing_log_store =
      std::make_unique<TestUnsentLogStore>(&pref_service_);
  TestUnsentLogStore* alternate_ongoing_log_store_ptr =
      alternate_ongoing_log_store.get();

  // Needs to be called before writing logs to alternate ongoing store since
  // SetAlternateOngoingLogStore loads persisted unsent logs and assumes that
  // the native initial and ongoing unsent logs have already been loaded.
  log_store.LoadPersistedUnsentLogs();

  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);
  log_store.SetAlternateOngoingLogStore(std::move(alternate_ongoing_log_store));
  log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);
  log_store.StoreLog("c", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);

  EXPECT_EQ(1U, log_store.ongoing_log_count());
  EXPECT_EQ(2U, alternate_ongoing_log_store_ptr->size());
}

TEST_F(MetricsLogStoreTest, AlternateOngoingLogStoreGetsEventsLogsManager) {
  // Create a MetricsLogStore with a MetricsLogsEventManager.
  MetricsLogsEventManager logs_event_manager;
  MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                            /*signing_key=*/std::string(), &logs_event_manager);

  // Create an UnsentLogStore that will be used as an alternate ongoing log
  // store.
  std::unique_ptr<TestUnsentLogStore> alternate_ongoing_log_store =
      std::make_unique<TestUnsentLogStore>(&pref_service_);
  TestUnsentLogStore* alternate_ongoing_log_store_ptr =
      alternate_ongoing_log_store.get();

  // Verify that |alternate_ongoing_log_store| has no logs event manager.
  EXPECT_FALSE(
      alternate_ongoing_log_store_ptr->GetLogsEventManagerForTesting());

  // Needs to be called before we can set |log_store|'s alternate ongoing log
  // store.
  log_store.LoadPersistedUnsentLogs();

  // Verify that after setting |log_store|'s alternate ongoing log store to
  // |alternate_ongoing_log_store|, the latter should have the former's logs
  // event manager.
  log_store.SetAlternateOngoingLogStore(std::move(alternate_ongoing_log_store));
  EXPECT_EQ(alternate_ongoing_log_store_ptr->GetLogsEventManagerForTesting(),
            &logs_event_manager);
}

TEST_F(MetricsLogStoreTest, StagesInitialOverBothOngoing) {
  MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                            /*signing_key=*/std::string(),
                            /*logs_event_manager=*/nullptr);
  std::unique_ptr<TestUnsentLogStore> alternate_ongoing_log_store =
      std::make_unique<TestUnsentLogStore>(&pref_service_);
  TestUnsentLogStore* alternate_ongoing_log_store_ptr =
      alternate_ongoing_log_store.get();

  // Needs to be called before writing logs to alternate ongoing store since
  // SetAlternateOngoingLogStore loads persisted unsent logs and assumes that
  // the native initial and ongoing unsent logs have already been loaded.
  log_store.LoadPersistedUnsentLogs();

  log_store.StoreLog("a", MetricsLog::INITIAL_STABILITY_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);
  log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);
  log_store.SetAlternateOngoingLogStore(std::move(alternate_ongoing_log_store));
  log_store.StoreLog("c", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);
  log_store.StageNextLog();
  log_store.DiscardStagedLog();

  // Discarded log should be from initial_log_store.
  EXPECT_EQ(0U, log_store.initial_log_count());
  EXPECT_EQ(1U, log_store.ongoing_log_count());
  EXPECT_EQ(1U, alternate_ongoing_log_store_ptr->size());
}

TEST_F(MetricsLogStoreTest, StagesAlternateOverOngoing) {
  MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                            /*signing_key=*/std::string(),
                            /*logs_event_manager=*/nullptr);
  std::unique_ptr<TestUnsentLogStore> alternate_ongoing_log_store =
      std::make_unique<TestUnsentLogStore>(&pref_service_);
  TestUnsentLogStore* alternate_ongoing_log_store_ptr =
      alternate_ongoing_log_store.get();

  // Needs to be called before writing logs to alternate ongoing store since
  // SetAlternateOngoingLogStore loads persisted unsent logs and assumes that
  // the native initial and ongoing unsent logs have already been loaded.
  log_store.LoadPersistedUnsentLogs();

  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);
  log_store.SetAlternateOngoingLogStore(std::move(alternate_ongoing_log_store));
  log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);
  log_store.StageNextLog();
  log_store.DiscardStagedLog();

  // Discarded log should be from alternate_ongoing_log_store.
  EXPECT_EQ(1U, log_store.ongoing_log_count());
  EXPECT_EQ(0U, alternate_ongoing_log_store_ptr->size());
}

TEST_F(MetricsLogStoreTest,
       UnboundAlternateOngoingLogStoreWritesToNativeOngoing) {
  MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                            /*signing_key=*/std::string(),
                            /*logs_event_manager=*/nullptr);
  std::unique_ptr<TestUnsentLogStore> alternate_ongoing_log_store =
      std::make_unique<TestUnsentLogStore>(&pref_service_);

  // Needs to be called before writing logs to alternate ongoing store since
  // SetAlternateOngoingLogStore loads persisted unsent logs and assumes that
  // the native initial and ongoing unsent logs have already been loaded.
  log_store.LoadPersistedUnsentLogs();

  log_store.SetAlternateOngoingLogStore(std::move(alternate_ongoing_log_store));
  // Should be written to alternate ongoing log store.
  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);

  log_store.UnsetAlternateOngoingLogStore();

  // Should be in native ongoing log store.
  log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);
  log_store.StoreLog("c", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);

  EXPECT_EQ(2U, log_store.ongoing_log_count());
}

TEST_F(MetricsLogStoreTest,
       StageOngoingLogWhenAlternateOngoingLogStoreIsEmpty) {
  MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                            /*signing_key=*/std::string(),
                            /*logs_event_manager=*/nullptr);
  std::unique_ptr<TestUnsentLogStore> alternate_ongoing_log_store =
      std::make_unique<TestUnsentLogStore>(&pref_service_);

  // Needs to be called before writing logs to alternate ongoing store since
  // SetAlternateOngoingLogStore loads persisted unsent logs and assumes that
  // the native initial and ongoing unsent logs have already been loaded.
  log_store.LoadPersistedUnsentLogs();

  // Should be written to ongoing log store.
  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata(),
                     MetricsLogsEventManager::CreateReason::kUnknown);

  // Ensure that the log was stored in ongoing log.
  EXPECT_EQ(1U, log_store.ongoing_log_count());

  log_store.SetAlternateOngoingLogStore(std::move(alternate_ongoing_log_store));

  log_store.StageNextLog();
  log_store.DiscardStagedLog();

  // Discarded log should be from ongoing.
  EXPECT_EQ(0U, log_store.ongoing_log_count());
}

}  // namespace metrics
