// 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/extensions/extension_apitest.h"

#include "base/string_util.h"
#include "base/stringprintf.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_test_api.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/ui_test_utils.h"
#include "content/common/notification_registrar.h"

namespace {

const char kTestServerPort[] = "testServer.port";

};  // namespace

ExtensionApiTest::ExtensionApiTest() {}

ExtensionApiTest::~ExtensionApiTest() {}

ExtensionApiTest::ResultCatcher::ResultCatcher()
    : profile_restriction_(NULL),
      waiting_(false) {
  registrar_.Add(this, NotificationType::EXTENSION_TEST_PASSED,
                 NotificationService::AllSources());
  registrar_.Add(this, NotificationType::EXTENSION_TEST_FAILED,
                 NotificationService::AllSources());
}

ExtensionApiTest::ResultCatcher::~ResultCatcher() {
}

bool ExtensionApiTest::ResultCatcher::GetNextResult() {
  // Depending on the tests, multiple results can come in from a single call
  // to RunMessageLoop(), so we maintain a queue of results and just pull them
  // off as the test calls this, going to the run loop only when the queue is
  // empty.
  if (results_.empty()) {
    waiting_ = true;
    ui_test_utils::RunMessageLoop();
    waiting_ = false;
  }

  if (!results_.empty()) {
    bool ret = results_.front();
    results_.pop_front();
    message_ = messages_.front();
    messages_.pop_front();
    return ret;
  }

  NOTREACHED();
  return false;
}

void ExtensionApiTest::ResultCatcher::Observe(
    NotificationType type, const NotificationSource& source,
    const NotificationDetails& details) {
  if (profile_restriction_ &&
      Source<Profile>(source).ptr() != profile_restriction_) {
    return;
  }

  switch (type.value) {
    case NotificationType::EXTENSION_TEST_PASSED:
      VLOG(1) << "Got EXTENSION_TEST_PASSED notification.";
      results_.push_back(true);
      messages_.push_back("");
      if (waiting_)
        MessageLoopForUI::current()->Quit();
      break;

    case NotificationType::EXTENSION_TEST_FAILED:
      VLOG(1) << "Got EXTENSION_TEST_FAILED notification.";
      results_.push_back(false);
      messages_.push_back(*(Details<std::string>(details).ptr()));
      if (waiting_)
        MessageLoopForUI::current()->Quit();
      break;

    default:
      NOTREACHED();
  }
}

void ExtensionApiTest::SetUpInProcessBrowserTestFixture() {
  DCHECK(!test_config_.get()) << "Previous test did not clear config state.";
  test_config_.reset(new DictionaryValue());
  ExtensionTestGetConfigFunction::set_test_config_state(test_config_.get());
}

void ExtensionApiTest::TearDownInProcessBrowserTestFixture() {
  ExtensionTestGetConfigFunction::set_test_config_state(NULL);
  test_config_.reset(NULL);
}

bool ExtensionApiTest::RunExtensionTest(const char* extension_name) {
  return RunExtensionTestImpl(extension_name, "", false, true, false);
}

bool ExtensionApiTest::RunExtensionTestIncognito(const char* extension_name) {
  return RunExtensionTestImpl(extension_name, "", true, true, false);
}

bool ExtensionApiTest::RunComponentExtensionTest(const char* extension_name) {
  return RunExtensionTestImpl(extension_name, "", false, true, true);
}

bool ExtensionApiTest::RunExtensionTestNoFileAccess(
    const char* extension_name) {
  return RunExtensionTestImpl(extension_name, "", false, false, false);
}

bool ExtensionApiTest::RunExtensionTestIncognitoNoFileAccess(
    const char* extension_name) {
  return RunExtensionTestImpl(extension_name, "", true, false, false);
}
bool ExtensionApiTest::RunExtensionSubtest(const char* extension_name,
                                           const std::string& page_url) {
  DCHECK(!page_url.empty()) << "Argument page_url is required.";
  return RunExtensionTestImpl(extension_name, page_url, false, true, false);
}

bool ExtensionApiTest::RunPageTest(const std::string& page_url) {
  return RunExtensionSubtest("", page_url);
}

// Load |extension_name| extension and/or |page_url| and wait for
// PASSED or FAILED notification.
bool ExtensionApiTest::RunExtensionTestImpl(const char* extension_name,
                                            const std::string& page_url,
                                            bool enable_incognito,
                                            bool enable_fileaccess,
                                            bool load_as_component) {
  ResultCatcher catcher;
  DCHECK(!std::string(extension_name).empty() || !page_url.empty()) <<
      "extension_name and page_url cannot both be empty";

  if (!std::string(extension_name).empty()) {
    bool loaded = false;
    if (load_as_component) {
      loaded =
          LoadExtensionAsComponent(test_data_dir_.AppendASCII(extension_name));
    } else {
      if (enable_incognito) {
        loaded = enable_fileaccess ?
          LoadExtensionIncognito(test_data_dir_.AppendASCII(extension_name)) :
          LoadExtensionIncognitoNoFileAccess(
              test_data_dir_.AppendASCII(extension_name));
      } else {
        loaded = enable_fileaccess ?
          LoadExtension(test_data_dir_.AppendASCII(extension_name)) :
          LoadExtensionNoFileAccess(test_data_dir_.AppendASCII(extension_name));
      }
    }
    if (!loaded) {
      message_ = "Failed to load extension.";
      return false;
    }
  }

  // If there is a page_url to load, navigate it.
  if (!page_url.empty()) {
    GURL url = GURL(page_url);

    // Note: We use is_valid() here in the expectation that the provided url
    // may lack a scheme & host and thus be a relative url within the loaded
    // extension.
    if (!url.is_valid()) {
      DCHECK(!std::string(extension_name).empty()) <<
          "Relative page_url given with no extension_name";

      ExtensionService* service = browser()->profile()->GetExtensionService();
      const Extension* extension =
          service->GetExtensionById(last_loaded_extension_id_, false);
      if (!extension)
        return false;

      url = extension->GetResourceURL(page_url);
    }

    ui_test_utils::NavigateToURL(browser(), url);
  }

  if (!catcher.GetNextResult()) {
    message_ = catcher.message();
    return false;
  } else {
    return true;
  }
}

// Test that exactly one extension loaded.
const Extension* ExtensionApiTest::GetSingleLoadedExtension() {
  ExtensionService* service = browser()->profile()->GetExtensionService();

  int found_extension_index = -1;
  for (size_t i = 0; i < service->extensions()->size(); ++i) {
    // Ignore any component extensions. They are automatically loaded into all
    // profiles and aren't the extension we're looking for here.
    if (service->extensions()->at(i)->location() == Extension::COMPONENT)
      continue;

    if (found_extension_index != -1) {
      message_ = base::StringPrintf(
          "Expected only one extension to be present.  Found %u.",
          static_cast<unsigned>(service->extensions()->size()));
      return NULL;
    }

    found_extension_index = static_cast<int>(i);
  }

  const Extension* extension = service->extensions()->at(found_extension_index);
  if (!extension) {
    message_ = "extension pointer is NULL.";
    return NULL;
  }
  return extension;
}

bool ExtensionApiTest::StartTestServer() {
  if (!test_server()->Start())
    return false;

  // Build a dictionary of values that tests can use to build URLs that
  // access the test server.  Tests can see these values using the extension
  // API function chrome.test.getConfig().
  test_config_->SetInteger(kTestServerPort,
                           test_server()->host_port_pair().port());

  return true;
}

void ExtensionApiTest::SetUpCommandLine(CommandLine* command_line) {
  ExtensionBrowserTest::SetUpCommandLine(command_line);
  test_data_dir_ = test_data_dir_.AppendASCII("api_test");
}