// Copyright 2016 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 <string>
#include <vector>

#include "base/memory/ref_counted.h"
#include "base/values.h"
#include "chrome/browser/extensions/component_migration_helper.h"
#include "chrome/browser/extensions/extension_action_test_util.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/feature_switch.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::Return;
using ::testing::StrictMock;

namespace extensions {
namespace {

const char kTestActionId[] = "toolbar-action";

class MockComponentActionDelegate
    : public ComponentMigrationHelper::ComponentActionDelegate {
 public:
  MOCK_METHOD1(AddComponentAction, void(const std::string&));
  MOCK_METHOD1(RemoveComponentAction, void(const std::string&));
  MOCK_CONST_METHOD1(HasComponentAction, bool(const std::string&));
};

class MockComponentMigrationHelper : public ComponentMigrationHelper {
 public:
  MockComponentMigrationHelper(Profile* profile,
                               ComponentActionDelegate* delegate)
      : ComponentMigrationHelper(profile, delegate) {}

  ~MockComponentMigrationHelper() override{};

  void SetTestComponentActionPref(bool enabled) {
    SetComponentActionPref(kTestActionId, enabled);
  }

  void EnableTestFeature() { enabled_actions_.insert(kTestActionId); }

  void DisableTestFeature() { enabled_actions_.erase(kTestActionId); }
};

}  // namespace

class ComponentMigrationHelperTest : public ExtensionServiceTestBase {
 protected:
  ComponentMigrationHelperTest() {}
  ~ComponentMigrationHelperTest() override {}

  void SetUp() override {
    extension_action_redesign_.reset(new FeatureSwitch::ScopedOverride(
        FeatureSwitch::extension_action_redesign(),
        FeatureSwitch::OVERRIDE_ENABLED));

    ExtensionServiceTestBase::SetUp();
    InitializeEmptyExtensionService();

    migrated_extension_a_ = extension_action_test_util::CreateActionExtension(
        "migrated_browser_action_a",
        extension_action_test_util::BROWSER_ACTION);

    migrated_extension_b_ = extension_action_test_util::CreateActionExtension(
        "migrated_browser_action_b",
        extension_action_test_util::BROWSER_ACTION);

    unregistered_extension_ = extension_action_test_util::CreateActionExtension(
        "unregistered_extension", extension_action_test_util::BROWSER_ACTION);

    mock_helper_.reset(new StrictMock<MockComponentMigrationHelper>(
        profile(), &mock_delegate_));
    mock_helper_->Register(kTestActionId, migrated_extension_a_->id());
    mock_helper_->Register(kTestActionId, migrated_extension_b_->id());
  }

  bool IsTestComponentActionEnabled() {
    const base::DictionaryValue* migration_pref =
        profile()->GetPrefs()->GetDictionary(
            ::prefs::kToolbarMigratedComponentActionStatus);
    if (!migration_pref->HasKey(kTestActionId))
      return false;
    bool enable_value = false;
    CHECK(migration_pref->GetBoolean(kTestActionId, &enable_value));
    return enable_value;
  }

  StrictMock<MockComponentActionDelegate> mock_delegate_;
  scoped_ptr<StrictMock<MockComponentMigrationHelper>> mock_helper_;
  scoped_ptr<FeatureSwitch::ScopedOverride> extension_action_redesign_;

  // Migrated extensions with browser actions.
  scoped_refptr<const Extension> migrated_extension_a_;
  scoped_refptr<const Extension> migrated_extension_b_;
  // An extension that is not migrated.
  scoped_refptr<const Extension> unregistered_extension_;

 private:
  DISALLOW_COPY_AND_ASSIGN(ComponentMigrationHelperTest);
};

TEST_F(ComponentMigrationHelperTest, FeatureEnabledWhenExtensionInstalled) {
  service()->AddExtension(migrated_extension_a_.get());
  service()->AddExtension(migrated_extension_b_.get());

  EXPECT_CALL(mock_delegate_, HasComponentAction(kTestActionId))
      .WillOnce(Return(false));
  EXPECT_CALL(mock_delegate_, AddComponentAction(kTestActionId));

  mock_helper_->OnFeatureEnabled(kTestActionId);
  EXPECT_TRUE(IsTestComponentActionEnabled());
  EXPECT_FALSE(
      registry()->enabled_extensions().Contains(migrated_extension_a_->id()));
  EXPECT_FALSE(
      registry()->enabled_extensions().Contains(migrated_extension_b_->id()));
}

TEST_F(ComponentMigrationHelperTest, FeatureEnabledWithNoPref) {
  mock_helper_->OnFeatureEnabled(kTestActionId);
  EXPECT_FALSE(IsTestComponentActionEnabled());
}

TEST_F(ComponentMigrationHelperTest, FeatureEnabledWithPrefFalse) {
  mock_helper_->SetTestComponentActionPref(false);

  mock_helper_->OnFeatureEnabled(kTestActionId);
  EXPECT_FALSE(IsTestComponentActionEnabled());
}

TEST_F(ComponentMigrationHelperTest, FeatureEnabledWithPrefTrue) {
  mock_helper_->SetTestComponentActionPref(true);

  EXPECT_CALL(mock_delegate_, HasComponentAction(kTestActionId))
      .WillOnce(Return(false));
  EXPECT_CALL(mock_delegate_, AddComponentAction(kTestActionId));

  mock_helper_->OnFeatureEnabled(kTestActionId);
  EXPECT_TRUE(IsTestComponentActionEnabled());
}

TEST_F(ComponentMigrationHelperTest, FeatureDisabledWithAction) {
  mock_helper_->EnableTestFeature();

  EXPECT_CALL(mock_delegate_, HasComponentAction(kTestActionId))
      .WillOnce(Return(true));
  EXPECT_CALL(mock_delegate_, RemoveComponentAction(kTestActionId));

  mock_helper_->OnFeatureDisabled(kTestActionId);
  EXPECT_FALSE(IsTestComponentActionEnabled());
}

TEST_F(ComponentMigrationHelperTest, InstallWithFeatureEnabled) {
  mock_helper_->EnableTestFeature();

  EXPECT_CALL(mock_delegate_, HasComponentAction(kTestActionId))
      .WillOnce(Return(false));
  EXPECT_CALL(mock_delegate_, AddComponentAction(kTestActionId));

  service()->AddExtension(migrated_extension_a_.get());
  // The test framework does not call OnExtensionReady :-/
  mock_helper_->OnExtensionReady(browser_context(),
                                 migrated_extension_a_.get());

  EXPECT_TRUE(IsTestComponentActionEnabled());
  EXPECT_FALSE(
      registry()->enabled_extensions().Contains(migrated_extension_a_->id()));
}

TEST_F(ComponentMigrationHelperTest, InstallWithFeatureDisabled) {
  mock_helper_->DisableTestFeature();
  service()->AddExtension(migrated_extension_a_.get());
  EXPECT_FALSE(IsTestComponentActionEnabled());
  EXPECT_TRUE(
      registry()->enabled_extensions().Contains(migrated_extension_a_->id()));
}

TEST_F(ComponentMigrationHelperTest, InstallUnregisteredExtension) {
  service()->AddExtension(unregistered_extension_.get());
  EXPECT_FALSE(IsTestComponentActionEnabled());
  EXPECT_TRUE(
      registry()->enabled_extensions().Contains(unregistered_extension_->id()));
}

TEST_F(ComponentMigrationHelperTest, RemoveComponentAction) {
  mock_helper_->SetTestComponentActionPref(true);

  EXPECT_CALL(mock_delegate_, HasComponentAction(kTestActionId))
      .WillOnce(Return(false));
  EXPECT_CALL(mock_delegate_, AddComponentAction(kTestActionId));

  mock_helper_->OnFeatureEnabled(kTestActionId);
  EXPECT_TRUE(IsTestComponentActionEnabled());

  EXPECT_CALL(mock_delegate_, HasComponentAction(kTestActionId))
      .WillOnce(Return(true));
  EXPECT_CALL(mock_delegate_, RemoveComponentAction(kTestActionId));

  mock_helper_->OnActionRemoved(kTestActionId);
  EXPECT_FALSE(IsTestComponentActionEnabled());
}

}  // namespace extensions