// Copyright (c) 2011 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 "chrome/browser/sync/js_sync_manager_observer.h"

#include <cstddef>

#include "base/basictypes.h"
#include "base/values.h"
#include "chrome/browser/sync/engine/syncapi.h"
#include "chrome/browser/sync/js_arg_list.h"
#include "chrome/browser/sync/js_test_util.h"
#include "chrome/browser/sync/sessions/session_state.h"
#include "chrome/browser/sync/syncable/model_type.h"
#include "chrome/test/sync/engine/test_user_share.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace browser_sync {
namespace {

using ::testing::InSequence;
using ::testing::StrictMock;

class JsSyncManagerObserverTest : public testing::Test {
 protected:
  JsSyncManagerObserverTest() : sync_manager_observer_(&mock_router_) {}

  StrictMock<MockJsEventRouter> mock_router_;
  JsSyncManagerObserver sync_manager_observer_;
};

TEST_F(JsSyncManagerObserverTest, NoArgNotifiations) {
  InSequence dummy;

  EXPECT_CALL(mock_router_,
              RouteJsEvent("onInitializationComplete",
                           HasArgs(JsArgList()), NULL));
  EXPECT_CALL(mock_router_,
              RouteJsEvent("onPassphraseFailed",
                           HasArgs(JsArgList()), NULL));

  EXPECT_CALL(mock_router_,
              RouteJsEvent("onStopSyncingPermanently",
                           HasArgs(JsArgList()), NULL));
  EXPECT_CALL(mock_router_,
              RouteJsEvent("onClearServerDataSucceeded",
                           HasArgs(JsArgList()), NULL));
  EXPECT_CALL(mock_router_,
              RouteJsEvent("onClearServerDataFailed",
                           HasArgs(JsArgList()), NULL));

  sync_manager_observer_.OnInitializationComplete();
  sync_manager_observer_.OnPassphraseFailed();
  sync_manager_observer_.OnStopSyncingPermanently();
  sync_manager_observer_.OnClearServerDataSucceeded();
  sync_manager_observer_.OnClearServerDataFailed();
}

TEST_F(JsSyncManagerObserverTest, OnChangesComplete) {
  InSequence dummy;

  for (int i = syncable::FIRST_REAL_MODEL_TYPE;
       i < syncable::MODEL_TYPE_COUNT; ++i) {
    const std::string& model_type_str =
        syncable::ModelTypeToString(syncable::ModelTypeFromInt(i));
    ListValue expected_args;
    expected_args.Append(Value::CreateStringValue(model_type_str));
    EXPECT_CALL(mock_router_,
                RouteJsEvent("onChangesComplete",
                             HasArgsAsList(expected_args), NULL));
  }

  for (int i = syncable::FIRST_REAL_MODEL_TYPE;
       i < syncable::MODEL_TYPE_COUNT; ++i) {
    sync_manager_observer_.OnChangesComplete(syncable::ModelTypeFromInt(i));
  }
}

TEST_F(JsSyncManagerObserverTest, OnSyncCycleCompleted) {
  std::string download_progress_markers[syncable::MODEL_TYPE_COUNT];
  sessions::SyncSessionSnapshot snapshot(sessions::SyncerStatus(),
                                         sessions::ErrorCounters(),
                                         100,
                                         false,
                                         syncable::ModelTypeBitSet(),
                                         download_progress_markers,
                                         false,
                                         true,
                                         100,
                                         5,
                                         false,
                                         sessions::SyncSourceInfo());
  ListValue expected_args;
  expected_args.Append(snapshot.ToValue());

  EXPECT_CALL(mock_router_,
              RouteJsEvent("onSyncCycleCompleted",
                           HasArgsAsList(expected_args), NULL));

  sync_manager_observer_.OnSyncCycleCompleted(&snapshot);
}

TEST_F(JsSyncManagerObserverTest, OnAuthError) {
  GoogleServiceAuthError error(GoogleServiceAuthError::TWO_FACTOR);
  ListValue expected_args;
  expected_args.Append(error.ToValue());

  EXPECT_CALL(mock_router_,
              RouteJsEvent("onAuthError",
                           HasArgsAsList(expected_args), NULL));

  sync_manager_observer_.OnAuthError(error);
}

TEST_F(JsSyncManagerObserverTest, OnPassphraseRequired) {
  InSequence dummy;

  ListValue true_args, false_args;
  true_args.Append(Value::CreateBooleanValue(true));
  false_args.Append(Value::CreateBooleanValue(false));

  EXPECT_CALL(mock_router_,
              RouteJsEvent("onPassphraseRequired",
                           HasArgsAsList(false_args), NULL));
  EXPECT_CALL(mock_router_,
              RouteJsEvent("onPassphraseRequired",
                           HasArgsAsList(true_args), NULL));

  sync_manager_observer_.OnPassphraseRequired(false);
  sync_manager_observer_.OnPassphraseRequired(true);
}

TEST_F(JsSyncManagerObserverTest, SensitiveNotifiations) {
  ListValue redacted_args;
  redacted_args.Append(Value::CreateStringValue("<redacted>"));

  EXPECT_CALL(mock_router_,
              RouteJsEvent("onUpdatedToken",
                           HasArgsAsList(redacted_args), NULL));
  EXPECT_CALL(mock_router_,
              RouteJsEvent("onPassphraseAccepted",
                           HasArgsAsList(redacted_args), NULL));

  sync_manager_observer_.OnUpdatedToken("sensitive_token");
  sync_manager_observer_.OnPassphraseAccepted("sensitive_token");
}

TEST_F(JsSyncManagerObserverTest, OnEncryptionComplete) {
  ListValue expected_args;
  ListValue* encrypted_type_values = new ListValue();
  syncable::ModelTypeSet encrypted_types;

  expected_args.Append(encrypted_type_values);
  for (int i = syncable::FIRST_REAL_MODEL_TYPE;
       i < syncable::MODEL_TYPE_COUNT; ++i) {
    syncable::ModelType type = syncable::ModelTypeFromInt(i);
    encrypted_types.insert(type);
    encrypted_type_values->Append(Value::CreateStringValue(
        syncable::ModelTypeToString(type)));
  }

  EXPECT_CALL(mock_router_,
              RouteJsEvent("onEncryptionComplete",
                           HasArgsAsList(expected_args), NULL));

  sync_manager_observer_.OnEncryptionComplete(encrypted_types);
}

TEST_F(JsSyncManagerObserverTest, OnMigrationNeededForTypes) {
  ListValue expected_args;
  ListValue* type_values = new ListValue();
  syncable::ModelTypeSet types;

  expected_args.Append(type_values);
  for (int i = syncable::FIRST_REAL_MODEL_TYPE;
       i < syncable::MODEL_TYPE_COUNT; ++i) {
    syncable::ModelType type = syncable::ModelTypeFromInt(i);
    types.insert(type);
    type_values->Append(Value::CreateStringValue(
        syncable::ModelTypeToString(type)));
  }

  EXPECT_CALL(mock_router_,
              RouteJsEvent("onMigrationNeededForTypes",
                           HasArgsAsList(expected_args), NULL));

  sync_manager_observer_.OnMigrationNeededForTypes(types);
}

namespace {

// Makes a node of the given model type.  Returns the id of the
// newly-created node.
int64 MakeNode(sync_api::UserShare* share, syncable::ModelType model_type) {
  sync_api::WriteTransaction trans(share);
  sync_api::ReadNode root_node(&trans);
  root_node.InitByRootLookup();
  sync_api::WriteNode node(&trans);
  EXPECT_TRUE(node.InitUniqueByCreation(
      model_type, root_node,
      syncable::ModelTypeToString(model_type)));
  node.SetIsFolder(false);
  return node.GetId();
}

}  // namespace

TEST_F(JsSyncManagerObserverTest, OnChangesApplied) {
  InSequence dummy;

  TestUserShare test_user_share;
  test_user_share.SetUp();

  // We don't test with passwords as that requires additional setup.

  // Build a list of example ChangeRecords.
  sync_api::SyncManager::ChangeRecord changes[syncable::MODEL_TYPE_COUNT];
  for (int i = syncable::AUTOFILL_PROFILE;
       i < syncable::MODEL_TYPE_COUNT; ++i) {
    changes[i].id =
        MakeNode(test_user_share.user_share(), syncable::ModelTypeFromInt(i));
    switch (i % 3) {
      case 0:
        changes[i].action =
            sync_api::SyncManager::ChangeRecord::ACTION_ADD;
        break;
      case 1:
        changes[i].action =
            sync_api::SyncManager::ChangeRecord::ACTION_UPDATE;
        break;
      default:
        changes[i].action =
            sync_api::SyncManager::ChangeRecord::ACTION_DELETE;
        break;
    }
    {
      sync_api::ReadTransaction trans(test_user_share.user_share());
      sync_api::ReadNode node(&trans);
      EXPECT_TRUE(node.InitByIdLookup(changes[i].id));
      changes[i].specifics = node.GetEntry()->Get(syncable::SPECIFICS);
    }
  }

  // For each i, we call OnChangesApplied() with the first arg equal
  // to i cast to ModelType and the second argument with the changes
  // starting from changes[i].

  // Set expectations for each data type.
  for (int i = syncable::AUTOFILL_PROFILE;
       i < syncable::MODEL_TYPE_COUNT; ++i) {
    const std::string& model_type_str =
        syncable::ModelTypeToString(syncable::ModelTypeFromInt(i));
    ListValue expected_args;
    expected_args.Append(Value::CreateStringValue(model_type_str));
    ListValue* expected_changes = new ListValue();
    expected_args.Append(expected_changes);
    for (int j = i; j < syncable::MODEL_TYPE_COUNT; ++j) {
      sync_api::ReadTransaction trans(test_user_share.user_share());
      expected_changes->Append(changes[j].ToValue(&trans));
    }
    EXPECT_CALL(mock_router_,
                RouteJsEvent("onChangesApplied",
                             HasArgsAsList(expected_args), NULL));
  }

  // Fire OnChangesApplied() for each data type.
  for (int i = syncable::AUTOFILL_PROFILE;
       i < syncable::MODEL_TYPE_COUNT; ++i) {
    sync_api::ReadTransaction trans(test_user_share.user_share());
    sync_manager_observer_.OnChangesApplied(syncable::ModelTypeFromInt(i),
                                            &trans, &changes[i],
                                            syncable::MODEL_TYPE_COUNT - i);
  }

  test_user_share.TearDown();
}

}  // namespace
}  // namespace browser_sync