// Copyright 2014 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/extensions/extension_action_manager.h"

#include "base/strings/string_number_conversions.h"
#include "chrome/browser/extensions/extension_action.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "extensions/common/value_builder.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace extensions {

namespace {

const char kBrowserAction[] = "browser_action";
const char kPageAction[] = "page_action";

}  // namespace

class ExtensionActionManagerTest : public testing::Test {
 public:
  ExtensionActionManagerTest();

 protected:
  // Build an extension, populating |action_type| key with |action|, and
  // "icons" key with |extension_icons|.
  scoped_refptr<Extension> BuildExtension(DictionaryBuilder extension_icons,
                                          DictionaryBuilder action,
                                          const char* action_type);

  // Returns true if |action|'s title matches |extension|'s name.
  bool TitlesMatch(const Extension& extension, const ExtensionAction& action);

  // Returns true if |action|'s icon for size |action_key| matches
  // |extension|'s icon for size |extension_key|;
  bool IconsMatch(const Extension& extension,
                  int extension_key,
                  const ExtensionAction& action,
                  int action_key);

  // Returns the appropriate action for |extension| according to |action_type|.
  ExtensionAction* GetAction(const char* action_type,
                             const Extension& extension);

  // Tests that values that are missing from the |action_type| key are properly
  // populated with values from the other keys in the manifest (e.g.
  // "default_icon" key of |action_type| is populated with "icons" key).
  void TestPopulateMissingValues(const char* action_type);

  ExtensionActionManager* manager() { return manager_; }

 private:
  content::TestBrowserThreadBundle thread_bundle_;
  ExtensionRegistry* registry_;
  int curr_id_;
  ExtensionActionManager* manager_;
  scoped_ptr<TestingProfile> profile_;
};

ExtensionActionManagerTest::ExtensionActionManagerTest()
    : curr_id_(0),
      profile_(new TestingProfile) {
  registry_ = ExtensionRegistry::Get(profile_.get());
  manager_ = ExtensionActionManager::Get(profile_.get());
}

scoped_refptr<Extension> ExtensionActionManagerTest::BuildExtension(
    DictionaryBuilder extension_icons,
    DictionaryBuilder action,
    const char* action_type) {
  std::string id = base::IntToString(curr_id_++);
  scoped_refptr<Extension> extension =
      ExtensionBuilder()
          .SetManifest(std::move(
              DictionaryBuilder()
                  .Set("version", "1")
                  .Set("manifest_version", 2)
                  .Set("icons", std::move(extension_icons))
                  .Set(action_type, std::move(action))
                  .Set("name", std::string("Test Extension").append(id))))
          .SetID(id)
          .Build();
  registry_->AddEnabled(extension);
  return extension;
}

bool ExtensionActionManagerTest::TitlesMatch(const Extension& extension,
                                             const ExtensionAction& action) {
  return action.GetTitle(ExtensionAction::kDefaultTabId) == extension.name();
}

bool ExtensionActionManagerTest::IconsMatch(const Extension& extension,
                                            int extension_key,
                                            const ExtensionAction& action,
                                            int action_key) {
  return action.default_icon()->Get(action_key,
                                    ExtensionIconSet::MATCH_BIGGER) ==
         IconsInfo::GetIcons(&extension)
             .Get(extension_key, ExtensionIconSet::MATCH_EXACTLY);
}

ExtensionAction* ExtensionActionManagerTest::GetAction(
    const char* action_type,
    const Extension& extension) {
  return (action_type == kBrowserAction) ?
      manager_->GetBrowserAction(extension) :
      manager_->GetPageAction(extension);
}

void ExtensionActionManagerTest::TestPopulateMissingValues(
    const char* action_type) {
  // Test that the largest icon from the extension's "icons" key is chosen as a
  // replacement for missing action default_icons keys. "19" should not be
  // replaced because "38" can always be used in its place.
  scoped_refptr<Extension> extension =
      BuildExtension(std::move(DictionaryBuilder()
                                   .Set("48", "icon48.png")
                                   .Set("128", "icon128.png")),
                     DictionaryBuilder(), action_type);

  ASSERT_TRUE(extension.get());
  const ExtensionAction* action = GetAction(action_type, *extension.get());
  ASSERT_TRUE(action);

  ASSERT_TRUE(TitlesMatch(*extension.get(), *action));
  ASSERT_TRUE(IconsMatch(*extension.get(), 48, *action, 38));

  // Test that the action's missing default_icons are not replaced with smaller
  // icons.
  extension =
      BuildExtension(std::move(DictionaryBuilder().Set("24", "icon24.png")),
                     DictionaryBuilder(), action_type);

  ASSERT_TRUE(extension.get());
  action = GetAction(action_type, *extension.get());
  ASSERT_TRUE(action);

  ASSERT_TRUE(IconsMatch(*extension.get(), 24, *action, 19));
  ASSERT_FALSE(IconsMatch(*extension.get(), 24, *action, 38));

  // Test that an action's 19px icon is not replaced if a 38px action icon
  // exists.
  extension = BuildExtension(
      std::move(DictionaryBuilder().Set("128", "icon128.png")),
      std::move(DictionaryBuilder().Set(
          "default_icon",
          std::move(DictionaryBuilder().Set("38", "action38.png")))),
      action_type);

  ASSERT_TRUE(extension.get());
  action = GetAction(action_type, *extension.get());
  ASSERT_TRUE(action);

  ASSERT_FALSE(IconsMatch(*extension.get(), 128, *action, 19));

  // Test that existing default_icons and default_title are not replaced.
  extension = BuildExtension(
      std::move(DictionaryBuilder().Set("128", "icon128.png")),
      std::move(
          DictionaryBuilder()
              .Set("default_title", "Action!")
              .Set("default_icon", std::move(DictionaryBuilder()
                                                 .Set("19", "action19.png")
                                                 .Set("38", "action38.png")))),
      action_type);

  ASSERT_TRUE(extension.get());
  action = GetAction(action_type, *extension.get());
  ASSERT_TRUE(action);

  ASSERT_FALSE(TitlesMatch(*extension.get(), *action));
  ASSERT_FALSE(IconsMatch(*extension.get(), 128, *action, 19));
  ASSERT_FALSE(IconsMatch(*extension.get(), 128, *action, 38));
}

namespace {

TEST_F(ExtensionActionManagerTest, PopulateBrowserAction) {
  TestPopulateMissingValues(kBrowserAction);
}

TEST_F(ExtensionActionManagerTest, PopulatePageAction) {
  TestPopulateMissingValues(kPageAction);
}

TEST_F(ExtensionActionManagerTest, GetBestFitActionTest) {
  // Create an extension with page action defaults.
  scoped_refptr<Extension> extension = BuildExtension(
      std::move(DictionaryBuilder().Set("48", "icon48.png")),
      std::move(DictionaryBuilder()
                    .Set("default_title", "Action!")
                    .Set("default_icon", std::move(DictionaryBuilder().Set(
                                             "38", "action38.png")))),
      kPageAction);
  ASSERT_TRUE(extension.get());

  // Get a "best fit" browser action for |extension|.
  scoped_ptr<ExtensionAction> action =
      manager()->GetBestFitAction(*extension.get(), ActionInfo::TYPE_BROWSER);
  ASSERT_TRUE(action.get());
  ASSERT_EQ(action->action_type(), ActionInfo::TYPE_BROWSER);

  // |action|'s title and default icon should match |extension|'s page action's.
  ASSERT_EQ(action->GetTitle(ExtensionAction::kDefaultTabId), "Action!");
  ASSERT_EQ(action->default_icon()->Get(38, ExtensionIconSet::MATCH_EXACTLY),
            "action38.png");

  // Create a new extension without page action defaults.
  extension =
      BuildExtension(std::move(DictionaryBuilder().Set("48", "icon48.png")),
                     DictionaryBuilder(), kPageAction);
  ASSERT_TRUE(extension.get());

  action =
      manager()->GetBestFitAction(*extension.get(), ActionInfo::TYPE_BROWSER);

  // Now these values match because |extension| does not have page action
  // defaults.
  ASSERT_TRUE(TitlesMatch(*extension.get(), *action));
  ASSERT_TRUE(IconsMatch(*extension.get(), 48, *action, 38));
}

}  // namespace
}  // namespace extensions