// Copyright 2014 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/sync/engine/get_updates_processor.h"

#include <stdint.h>

#include <memory>
#include <set>
#include <string>
#include <utility>

#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "components/sync/engine/cycle/debug_info_getter.h"
#include "components/sync/engine/cycle/nudge_tracker.h"
#include "components/sync/engine/cycle/status_controller.h"
#include "components/sync/engine/get_updates_delegate.h"
#include "components/sync/engine/update_handler.h"
#include "components/sync/protocol/data_type_progress_marker.pb.h"
#include "components/sync/test/mock_debug_info_getter.h"
#include "components/sync/test/mock_invalidation.h"
#include "components/sync/test/mock_update_handler.h"
#include "components/sync/test/model_type_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace syncer {

namespace {

}  // namespace

// A test fixture for tests exercising download updates functions.
class GetUpdatesProcessorTest : public ::testing::Test {
 public:
  GetUpdatesProcessorTest() = default;

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

  void SetUp() override {
    autofill_handler_ = AddUpdateHandler(AUTOFILL);
    bookmarks_handler_ = AddUpdateHandler(BOOKMARKS);
    preferences_handler_ = AddUpdateHandler(PREFERENCES);
  }

  ModelTypeSet enabled_types() { return enabled_types_; }

  std::unique_ptr<GetUpdatesProcessor> BuildGetUpdatesProcessor(
      const GetUpdatesDelegate& delegate) {
    return std::make_unique<GetUpdatesProcessor>(&update_handler_map_,
                                                 delegate);
  }

  void InitFakeUpdateResponse(sync_pb::GetUpdatesResponse* response) {
    ModelTypeSet types = enabled_types();

    for (ModelType type : types) {
      sync_pb::DataTypeProgressMarker* marker =
          response->add_new_progress_marker();
      marker->set_data_type_id(GetSpecificsFieldNumberFromModelType(type));
      marker->set_token("foobarbaz");
      sync_pb::DataTypeContext* context = response->add_context_mutations();
      context->set_data_type_id(GetSpecificsFieldNumberFromModelType(type));
      context->set_version(1);
      context->set_context("context");
    }

    response->set_changes_remaining(0);
  }

  MockUpdateHandler* AddUpdateHandler(ModelType type) {
    enabled_types_.Put(type);

    std::unique_ptr<MockUpdateHandler> handler =
        std::make_unique<MockUpdateHandler>(type);
    MockUpdateHandler* handler_ptr = handler.get();

    update_handler_map_.insert(std::make_pair(type, handler_ptr));
    update_handlers_.insert(std::move(handler));
    return handler_ptr;
  }

  MockUpdateHandler* GetBookmarksHandler() { return bookmarks_handler_; }

  MockUpdateHandler* GetAutofillHandler() { return autofill_handler_; }

  MockUpdateHandler* GetPreferencesHandler() { return preferences_handler_; }

  const base::TimeTicks kTestStartTime = base::TimeTicks::Now();

 private:
  ModelTypeSet enabled_types_;
  std::set<std::unique_ptr<MockUpdateHandler>> update_handlers_;
  UpdateHandlerMap update_handler_map_;
  std::unique_ptr<GetUpdatesProcessor> get_updates_processor_;

  raw_ptr<MockUpdateHandler> bookmarks_handler_;
  raw_ptr<MockUpdateHandler> autofill_handler_;
  raw_ptr<MockUpdateHandler> preferences_handler_;
};

// Basic test to make sure nudges are expressed properly in the request.
TEST_F(GetUpdatesProcessorTest, BookmarkNudge) {
  NudgeTracker nudge_tracker;
  nudge_tracker.RecordLocalChange(BOOKMARKS);

  sync_pb::ClientToServerMessage message;
  NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
  std::unique_ptr<GetUpdatesProcessor> processor(
      BuildGetUpdatesProcessor(normal_delegate));
  processor->PrepareGetUpdates(enabled_types(), &message);

  const sync_pb::GetUpdatesMessage& gu_msg = message.get_updates();
  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::UNKNOWN,
            gu_msg.caller_info().source());
  EXPECT_EQ(sync_pb::SyncEnums::GU_TRIGGER, gu_msg.get_updates_origin());
  for (int i = 0; i < gu_msg.from_progress_marker_size(); ++i) {
    ModelType type = GetModelTypeFromSpecificsFieldNumber(
        gu_msg.from_progress_marker(i).data_type_id());

    const sync_pb::DataTypeProgressMarker& progress_marker =
        gu_msg.from_progress_marker(i);
    const sync_pb::GetUpdateTriggers& gu_trigger =
        progress_marker.get_update_triggers();

    // We perform some basic tests of GU trigger and source fields here.  The
    // more complicated scenarios are tested by the NudgeTracker tests.
    if (type == BOOKMARKS) {
      EXPECT_EQ(1, gu_trigger.local_modification_nudges());
      EXPECT_EQ(0, gu_trigger.datatype_refresh_nudges());
    } else {
      EXPECT_EQ(0, gu_trigger.local_modification_nudges());
      EXPECT_EQ(0, gu_trigger.datatype_refresh_nudges());
    }
  }
}

// Basic test to ensure invalidation payloads are expressed in the
// NormalDelegate requests.
TEST_F(GetUpdatesProcessorTest, NotifyNormalDelegate) {
  MockUpdateHandler* autofill_handler = GetAutofillHandler();
  MockUpdateHandler* bookmarks_handler = GetBookmarksHandler();
  MockUpdateHandler* preferences_handler = GetPreferencesHandler();

  ModelTypeSet notified_types;
  notified_types.Put(AUTOFILL);
  notified_types.Put(BOOKMARKS);
  notified_types.Put(PREFERENCES);

  NudgeTracker nudge_tracker;

  sync_pb::ClientToServerMessage message;
  NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
  std::unique_ptr<GetUpdatesProcessor> processor(
      BuildGetUpdatesProcessor(normal_delegate));
  processor->PrepareGetUpdates(enabled_types(), &message);

  const sync_pb::GetUpdatesMessage& gu_msg = message.get_updates();
  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::UNKNOWN,
            gu_msg.caller_info().source());
  EXPECT_EQ(sync_pb::SyncEnums::GU_TRIGGER, gu_msg.get_updates_origin());

  EXPECT_EQ(1, autofill_handler->GetPrepareGetUpdatesCount());
  EXPECT_EQ(1, bookmarks_handler->GetPrepareGetUpdatesCount());
  EXPECT_EQ(1, preferences_handler->GetPrepareGetUpdatesCount());
}

// Basic test to ensure invalidation payloads are not expressed in
// ConfigureDelegate requests.
TEST_F(GetUpdatesProcessorTest, NotifyConfigureDelegate) {
  MockUpdateHandler* autofill_handler = GetAutofillHandler();
  MockUpdateHandler* bookmarks_handler = GetBookmarksHandler();
  MockUpdateHandler* preferences_handler = GetPreferencesHandler();

  ModelTypeSet notified_types;
  notified_types.Put(AUTOFILL);
  notified_types.Put(BOOKMARKS);
  notified_types.Put(PREFERENCES);

  sync_pb::ClientToServerMessage message;
  ConfigureGetUpdatesDelegate configure_delegate(
      sync_pb::SyncEnums::RECONFIGURATION);
  std::unique_ptr<GetUpdatesProcessor> processor(
      BuildGetUpdatesProcessor(configure_delegate));
  processor->PrepareGetUpdates(enabled_types(), &message);

  EXPECT_EQ(0, autofill_handler->GetPrepareGetUpdatesCount());
  EXPECT_EQ(0, bookmarks_handler->GetPrepareGetUpdatesCount());
  EXPECT_EQ(0, preferences_handler->GetPrepareGetUpdatesCount());
}

// Basic test to ensure invalidation payloads are not expressed in
// PollGetUpdatesDelegate requests.
TEST_F(GetUpdatesProcessorTest, NotifyPollGetUpdatesDelegate) {
  MockUpdateHandler* autofill_handler = GetAutofillHandler();
  MockUpdateHandler* bookmarks_handler = GetBookmarksHandler();
  MockUpdateHandler* preferences_handler = GetPreferencesHandler();

  ModelTypeSet notified_types;
  notified_types.Put(AUTOFILL);
  notified_types.Put(BOOKMARKS);
  notified_types.Put(PREFERENCES);

  sync_pb::ClientToServerMessage message;
  PollGetUpdatesDelegate poll_delegate;
  std::unique_ptr<GetUpdatesProcessor> processor(
      BuildGetUpdatesProcessor(poll_delegate));
  processor->PrepareGetUpdates(enabled_types(), &message);

  EXPECT_EQ(0, autofill_handler->GetPrepareGetUpdatesCount());
  EXPECT_EQ(0, bookmarks_handler->GetPrepareGetUpdatesCount());
  EXPECT_EQ(0, preferences_handler->GetPrepareGetUpdatesCount());
}

// Basic test to ensure initial sync requests are expressed in the request.
TEST_F(GetUpdatesProcessorTest, InitialSyncRequest) {
  NudgeTracker nudge_tracker;
  nudge_tracker.RecordInitialSyncRequired(AUTOFILL);
  nudge_tracker.RecordInitialSyncRequired(PREFERENCES);

  ModelTypeSet initial_sync_types = ModelTypeSet(AUTOFILL, PREFERENCES);

  sync_pb::ClientToServerMessage message;
  NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
  std::unique_ptr<GetUpdatesProcessor> processor(
      BuildGetUpdatesProcessor(normal_delegate));
  processor->PrepareGetUpdates(enabled_types(), &message);

  const sync_pb::GetUpdatesMessage& gu_msg = message.get_updates();
  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::UNKNOWN,
            gu_msg.caller_info().source());
  EXPECT_EQ(sync_pb::SyncEnums::GU_TRIGGER, gu_msg.get_updates_origin());
  for (int i = 0; i < gu_msg.from_progress_marker_size(); ++i) {
    ModelType type = GetModelTypeFromSpecificsFieldNumber(
        gu_msg.from_progress_marker(i).data_type_id());

    const sync_pb::DataTypeProgressMarker& progress_marker =
        gu_msg.from_progress_marker(i);
    const sync_pb::GetUpdateTriggers& gu_trigger =
        progress_marker.get_update_triggers();

    // We perform some basic tests of GU trigger and source fields here.  The
    // more complicated scenarios are tested by the NudgeTracker tests.
    if (initial_sync_types.Has(type)) {
      EXPECT_TRUE(gu_trigger.initial_sync_in_progress());
    } else {
      EXPECT_TRUE(gu_trigger.has_initial_sync_in_progress());
      EXPECT_FALSE(gu_trigger.initial_sync_in_progress());
    }
  }
}

TEST_F(GetUpdatesProcessorTest, ConfigureTest) {
  sync_pb::ClientToServerMessage message;
  ConfigureGetUpdatesDelegate configure_delegate(
      sync_pb::SyncEnums::RECONFIGURATION);
  std::unique_ptr<GetUpdatesProcessor> processor(
      BuildGetUpdatesProcessor(configure_delegate));
  processor->PrepareGetUpdates(enabled_types(), &message);

  const sync_pb::GetUpdatesMessage& gu_msg = message.get_updates();
  EXPECT_EQ(sync_pb::SyncEnums::RECONFIGURATION, gu_msg.get_updates_origin());
  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::UNKNOWN,
            gu_msg.caller_info().source());

  ModelTypeSet progress_types;
  for (int i = 0; i < gu_msg.from_progress_marker_size(); ++i) {
    ModelType type = GetModelTypeFromSpecificsFieldNumber(
        gu_msg.from_progress_marker(i).data_type_id());
    progress_types.Put(type);
  }
  EXPECT_EQ(enabled_types(), progress_types);
}

TEST_F(GetUpdatesProcessorTest, PollTest) {
  sync_pb::ClientToServerMessage message;
  PollGetUpdatesDelegate poll_delegate;
  std::unique_ptr<GetUpdatesProcessor> processor(
      BuildGetUpdatesProcessor(poll_delegate));
  processor->PrepareGetUpdates(enabled_types(), &message);

  const sync_pb::GetUpdatesMessage& gu_msg = message.get_updates();
  EXPECT_EQ(sync_pb::SyncEnums::PERIODIC, gu_msg.get_updates_origin());
  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::UNKNOWN,
            gu_msg.caller_info().source());

  ModelTypeSet progress_types;
  for (int i = 0; i < gu_msg.from_progress_marker_size(); ++i) {
    ModelType type = GetModelTypeFromSpecificsFieldNumber(
        gu_msg.from_progress_marker(i).data_type_id());
    progress_types.Put(type);
  }
  EXPECT_EQ(enabled_types(), progress_types);
}

TEST_F(GetUpdatesProcessorTest, RetryTest) {
  NudgeTracker nudge_tracker;

  // Schedule a retry.
  base::TimeTicks t1 = kTestStartTime;
  nudge_tracker.SetNextRetryTime(t1);

  // Get the nudge tracker to think the retry is due.
  nudge_tracker.SetSyncCycleStartTime(t1 + base::Seconds(1));

  sync_pb::ClientToServerMessage message;
  NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
  std::unique_ptr<GetUpdatesProcessor> processor(
      BuildGetUpdatesProcessor(normal_delegate));
  processor->PrepareGetUpdates(enabled_types(), &message);

  const sync_pb::GetUpdatesMessage& gu_msg = message.get_updates();
  EXPECT_EQ(sync_pb::SyncEnums::RETRY, gu_msg.get_updates_origin());
  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::UNKNOWN,
            gu_msg.caller_info().source());
  EXPECT_TRUE(gu_msg.is_retry());

  ModelTypeSet progress_types;
  for (int i = 0; i < gu_msg.from_progress_marker_size(); ++i) {
    ModelType type = GetModelTypeFromSpecificsFieldNumber(
        gu_msg.from_progress_marker(i).data_type_id());
    progress_types.Put(type);
  }
  EXPECT_EQ(enabled_types(), progress_types);
}

TEST_F(GetUpdatesProcessorTest, NudgeWithRetryTest) {
  NudgeTracker nudge_tracker;

  // Schedule a retry.
  base::TimeTicks t1 = kTestStartTime;
  nudge_tracker.SetNextRetryTime(t1);

  // Get the nudge tracker to think the retry is due.
  nudge_tracker.SetSyncCycleStartTime(t1 + base::Seconds(1));

  // Record a local change, too.
  nudge_tracker.RecordLocalChange(BOOKMARKS);

  sync_pb::ClientToServerMessage message;
  NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
  std::unique_ptr<GetUpdatesProcessor> processor(
      BuildGetUpdatesProcessor(normal_delegate));
  processor->PrepareGetUpdates(enabled_types(), &message);

  const sync_pb::GetUpdatesMessage& gu_msg = message.get_updates();
  EXPECT_NE(sync_pb::SyncEnums::RETRY, gu_msg.get_updates_origin());
  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::UNKNOWN,
            gu_msg.caller_info().source());

  EXPECT_TRUE(gu_msg.is_retry());
}

// Verify that a bogus response message is detected.
TEST_F(GetUpdatesProcessorTest, InvalidResponse) {
  sync_pb::GetUpdatesResponse gu_response;
  InitFakeUpdateResponse(&gu_response);

  // This field is essential for making the client stop looping.  If it's unset
  // then something is very wrong.  The client should detect this.
  gu_response.clear_changes_remaining();

  NudgeTracker nudge_tracker;
  NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
  StatusController status;
  std::unique_ptr<GetUpdatesProcessor> processor(
      BuildGetUpdatesProcessor(normal_delegate));
  SyncerError error =
      processor->ProcessResponse(gu_response, enabled_types(), &status);
  EXPECT_EQ(error.value(), SyncerError::SERVER_RESPONSE_VALIDATION_FAILED);
}

// Verify that we correctly detect when there's more work to be done.
TEST_F(GetUpdatesProcessorTest, MoreToDownloadResponse) {
  sync_pb::GetUpdatesResponse gu_response;
  InitFakeUpdateResponse(&gu_response);
  gu_response.set_changes_remaining(1);

  NudgeTracker nudge_tracker;
  NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
  StatusController status;
  std::unique_ptr<GetUpdatesProcessor> processor(
      BuildGetUpdatesProcessor(normal_delegate));
  SyncerError error =
      processor->ProcessResponse(gu_response, enabled_types(), &status);
  EXPECT_EQ(error.value(), SyncerError::SERVER_MORE_TO_DOWNLOAD);
}

// A simple scenario: No updates returned and nothing more to download.
TEST_F(GetUpdatesProcessorTest, NormalResponseTest) {
  sync_pb::GetUpdatesResponse gu_response;
  InitFakeUpdateResponse(&gu_response);
  gu_response.set_changes_remaining(0);

  NudgeTracker nudge_tracker;
  NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
  StatusController status;
  std::unique_ptr<GetUpdatesProcessor> processor(
      BuildGetUpdatesProcessor(normal_delegate));
  SyncerError error =
      processor->ProcessResponse(gu_response, enabled_types(), &status);
  EXPECT_EQ(error.value(), SyncerError::SYNCER_OK);
}

// Variant of GetUpdatesProcessor test designed to test update application.
//
// Maintains two enabled types, but requests that updates be applied for only
// one of them.
class GetUpdatesProcessorApplyUpdatesTest : public GetUpdatesProcessorTest {
 public:
  GetUpdatesProcessorApplyUpdatesTest() = default;
  ~GetUpdatesProcessorApplyUpdatesTest() override = default;

  void SetUp() override {
    bookmarks_handler_ = AddUpdateHandler(BOOKMARKS);
    autofill_handler_ = AddUpdateHandler(AUTOFILL);
  }

  ModelTypeSet GetGuTypes() { return ModelTypeSet(AUTOFILL); }

  MockUpdateHandler* GetNonAppliedHandler() { return bookmarks_handler_; }

  MockUpdateHandler* GetAppliedHandler() { return autofill_handler_; }

 private:
  raw_ptr<MockUpdateHandler> bookmarks_handler_;
  raw_ptr<MockUpdateHandler> autofill_handler_;
};

// Verify that a normal cycle applies updates to the specified types.
TEST_F(GetUpdatesProcessorApplyUpdatesTest, Normal) {
  NudgeTracker nudge_tracker;
  NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
  std::unique_ptr<GetUpdatesProcessor> processor(
      BuildGetUpdatesProcessor(normal_delegate));

  EXPECT_EQ(0, GetNonAppliedHandler()->GetApplyUpdatesCount());
  EXPECT_EQ(0, GetAppliedHandler()->GetApplyUpdatesCount());

  StatusController status;
  processor->ApplyUpdates(GetGuTypes(), &status);

  EXPECT_EQ(0, GetNonAppliedHandler()->GetApplyUpdatesCount());
  EXPECT_EQ(1, GetAppliedHandler()->GetApplyUpdatesCount());
}

// Verify that a configure cycle applies updates to the specified types.
TEST_F(GetUpdatesProcessorApplyUpdatesTest, Configure) {
  ConfigureGetUpdatesDelegate configure_delegate(
      sync_pb::SyncEnums::RECONFIGURATION);
  std::unique_ptr<GetUpdatesProcessor> processor(
      BuildGetUpdatesProcessor(configure_delegate));

  EXPECT_EQ(0, GetNonAppliedHandler()->GetApplyUpdatesCount());
  EXPECT_EQ(0, GetAppliedHandler()->GetApplyUpdatesCount());

  StatusController status;
  processor->ApplyUpdates(GetGuTypes(), &status);

  EXPECT_EQ(0, GetNonAppliedHandler()->GetApplyUpdatesCount());
  EXPECT_EQ(1, GetAppliedHandler()->GetApplyUpdatesCount());
}

// Verify that a poll cycle applies updates to the specified types.
TEST_F(GetUpdatesProcessorApplyUpdatesTest, Poll) {
  PollGetUpdatesDelegate poll_delegate;
  std::unique_ptr<GetUpdatesProcessor> processor(
      BuildGetUpdatesProcessor(poll_delegate));

  EXPECT_EQ(0, GetNonAppliedHandler()->GetApplyUpdatesCount());
  EXPECT_EQ(0, GetAppliedHandler()->GetApplyUpdatesCount());

  StatusController status;
  processor->ApplyUpdates(GetGuTypes(), &status);

  EXPECT_EQ(0, GetNonAppliedHandler()->GetApplyUpdatesCount());
  EXPECT_EQ(1, GetAppliedHandler()->GetApplyUpdatesCount());
}

class DownloadUpdatesDebugInfoTest : public ::testing::Test {
 public:
  DownloadUpdatesDebugInfoTest() = default;
  ~DownloadUpdatesDebugInfoTest() override = default;

  StatusController* status() { return &status_; }

  DebugInfoGetter* debug_info_getter() { return &debug_info_getter_; }

  void AddDebugEvent() { debug_info_getter_.AddDebugEvent(); }

 private:
  StatusController status_;
  MockDebugInfoGetter debug_info_getter_;
};

}  // namespace syncer