text: // Copyright (c) 2012 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/strings/string_split.h"
  #include "base/strings/string_util.h"
  #include "base/strings/stringprintf.h"
  #include "chrome/browser/chrome_notification_types.h"
  #include "chrome/browser/extensions/api/test/test_api.h"
  #include "chrome/browser/extensions/extension_service.h"
  #include "chrome/browser/extensions/extension_system.h"
  #include "chrome/browser/extensions/unpacked_installer.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/browser/ui/browser.h"
  #include "chrome/browser/ui/extensions/application_launch.h"
  #include "chrome/common/extensions/extension.h"
  #include "chrome/test/base/ui_test_utils.h"
  #include "content/public/browser/notification_registrar.h"
  #include "content/public/browser/notification_service.h"
  #include "net/base/escape.h"
  #include "net/base/net_util.h"
  #include "net/test/embedded_test_server/embedded_test_server.h"
  #include "net/test/embedded_test_server/http_response.h"
  #include "net/test/embedded_test_server/http_request.h"
  #include "net/test/spawned_test_server/spawned_test_server.h"
  
  namespace {
  
  const char kTestCustomArg[] = "customArg";
  const char kTestServerPort[] = "testServer.port";
  const char kTestDataDirectory[] = "testDataDirectory";
  const char kTestWebSocketPort[] = "testWebSocketPort";
  
  scoped_ptr<net::test_server::HttpResponse> HandleServerRedirectRequest(
      const net::test_server::HttpRequest& request) {
    if (!StartsWithASCII(request.relative_url, "/server-redirect?", true))
      return scoped_ptr<net::test_server::HttpResponse>();
  
    size_t query_string_pos = request.relative_url.find('?');
    std::string redirect_target =
        request.relative_url.substr(query_string_pos + 1);
  
    scoped_ptr<net::test_server::BasicHttpResponse> http_response(
        new net::test_server::BasicHttpResponse);
    http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
    http_response->AddCustomHeader("Location", redirect_target);
    return http_response.PassAs<net::test_server::HttpResponse>();
  }
  
  scoped_ptr<net::test_server::HttpResponse> HandleEchoHeaderRequest(
      const net::test_server::HttpRequest& request) {
    if (!StartsWithASCII(request.relative_url, "/echoheader?", true))
      return scoped_ptr<net::test_server::HttpResponse>();
  
    size_t query_string_pos = request.relative_url.find('?');
    std::string header_name =
        request.relative_url.substr(query_string_pos + 1);
  
    std::string header_value;
    std::map<std::string, std::string>::const_iterator it = request.headers.find(
        header_name);
    if (it != request.headers.end())
      header_value = it->second;
  
    scoped_ptr<net::test_server::BasicHttpResponse> http_response(
        new net::test_server::BasicHttpResponse);
    http_response->set_code(net::HTTP_OK);
    http_response->set_content(header_value);
    return http_response.PassAs<net::test_server::HttpResponse>();
  }
  
  scoped_ptr<net::test_server::HttpResponse> HandleSetCookieRequest(
      const net::test_server::HttpRequest& request) {
    if (!StartsWithASCII(request.relative_url, "/set-cookie?", true))
      return scoped_ptr<net::test_server::HttpResponse>();
  
    scoped_ptr<net::test_server::BasicHttpResponse> http_response(
        new net::test_server::BasicHttpResponse);
    http_response->set_code(net::HTTP_OK);
  
    size_t query_string_pos = request.relative_url.find('?');
    std::string cookie_value =
        request.relative_url.substr(query_string_pos + 1);
  
    std::vector<std::string> cookies;
    base::SplitString(cookie_value, '&', &cookies);
  
    for (size_t i = 0; i < cookies.size(); i++)
      http_response->AddCustomHeader("Set-Cookie", cookies[i]);
  
    return http_response.PassAs<net::test_server::HttpResponse>();
  }
  
  scoped_ptr<net::test_server::HttpResponse> HandleSetHeaderRequest(
      const net::test_server::HttpRequest& request) {
    if (!StartsWithASCII(request.relative_url, "/set-header?", true))
      return scoped_ptr<net::test_server::HttpResponse>();
  
    size_t query_string_pos = request.relative_url.find('?');
    std::string escaped_header =
        request.relative_url.substr(query_string_pos + 1);
  
    std::string header =
        net::UnescapeURLComponent(escaped_header,
                                  net::UnescapeRule::NORMAL |
                                  net::UnescapeRule::SPACES |
                                  net::UnescapeRule::URL_SPECIAL_CHARS);
  
    size_t colon_pos = header.find(':');
    if (colon_pos == std::string::npos)
      return scoped_ptr<net::test_server::HttpResponse>();
  
    std::string header_name = header.substr(0, colon_pos);
    // Skip space after colon.
    std::string header_value = header.substr(colon_pos + 2);
  
    scoped_ptr<net::test_server::BasicHttpResponse> http_response(
        new net::test_server::BasicHttpResponse);
    http_response->set_code(net::HTTP_OK);
    http_response->AddCustomHeader(header_name, header_value);
    return http_response.PassAs<net::test_server::HttpResponse>();
  }
  
  };  // namespace
  
  ExtensionApiTest::ExtensionApiTest() {
    embedded_test_server()->RegisterRequestHandler(
        base::Bind(&HandleServerRedirectRequest));
    embedded_test_server()->RegisterRequestHandler(
        base::Bind(&HandleEchoHeaderRequest));
    embedded_test_server()->RegisterRequestHandler(
        base::Bind(&HandleSetCookieRequest));
    embedded_test_server()->RegisterRequestHandler(
        base::Bind(&HandleSetHeaderRequest));
  }
  
  ExtensionApiTest::~ExtensionApiTest() {}
  
  ExtensionApiTest::ResultCatcher::ResultCatcher()
      : profile_restriction_(NULL),
        waiting_(false) {
    registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_TEST_PASSED,
                   content::NotificationService::AllSources());
    registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_TEST_FAILED,
                   content::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;
      content::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(
      int type, const content::NotificationSource& source,
      const content::NotificationDetails& details) {
    if (profile_restriction_ &&
        content::Source<Profile>(source).ptr() != profile_restriction_) {
      return;
    }
  
    switch (type) {
      case chrome::NOTIFICATION_EXTENSION_TEST_PASSED:
        VLOG(1) << "Got EXTENSION_TEST_PASSED notification.";
        results_.push_back(true);
        messages_.push_back(std::string());
        if (waiting_)
          base::MessageLoopForUI::current()->Quit();
        break;
  
      case chrome::NOTIFICATION_EXTENSION_TEST_FAILED:
        VLOG(1) << "Got EXTENSION_TEST_FAILED notification.";
        results_.push_back(false);
        messages_.push_back(*(content::Details<std::string>(details).ptr()));
        if (waiting_)
          base::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());
    test_config_->SetString(kTestDataDirectory,
                            net::FilePathToFileURL(test_data_dir_).spec());
    test_config_->SetInteger(kTestWebSocketPort, 0);
    extensions::TestGetConfigFunction::set_test_config_state(
        test_config_.get());
  }
  
  void ExtensionApiTest::TearDownInProcessBrowserTestFixture() {
    extensions::TestGetConfigFunction::set_test_config_state(NULL);
    test_config_.reset(NULL);
  }
  
  bool ExtensionApiTest::RunExtensionTest(const char* extension_name) {
    return RunExtensionTestImpl(
        extension_name, std::string(), NULL, kFlagEnableFileAccess);
  }
  
  bool ExtensionApiTest::RunExtensionTestIncognito(const char* extension_name) {
    return RunExtensionTestImpl(extension_name,
                                std::string(),
                                NULL,
                                kFlagEnableIncognito | kFlagEnableFileAccess);
  }
  
  bool ExtensionApiTest::RunExtensionTestIgnoreManifestWarnings(
      const char* extension_name) {
    return RunExtensionTestImpl(
        extension_name, std::string(), NULL, kFlagIgnoreManifestWarnings);
  }
  
  bool ExtensionApiTest::RunExtensionTestAllowOldManifestVersion(
      const char* extension_name) {
    return RunExtensionTestImpl(
        extension_name,
        std::string(),
        NULL,
        kFlagEnableFileAccess | kFlagAllowOldManifestVersions);
  }
  
  bool ExtensionApiTest::RunComponentExtensionTest(const char* extension_name) {
    return RunExtensionTestImpl(extension_name,
                                std::string(),
                                NULL,
                                kFlagEnableFileAccess | kFlagLoadAsComponent);
  }
  
  bool ExtensionApiTest::RunExtensionTestNoFileAccess(
      const char* extension_name) {
    return RunExtensionTestImpl(extension_name, std::string(), NULL, kFlagNone);
  }
  
  bool ExtensionApiTest::RunExtensionTestIncognitoNoFileAccess(
      const char* extension_name) {
    return RunExtensionTestImpl(
        extension_name, std::string(), NULL, kFlagEnableIncognito);
  }
  
  bool ExtensionApiTest::RunExtensionSubtest(const char* extension_name,
                                             const std::string& page_url) {
    return RunExtensionSubtest(extension_name, page_url, kFlagEnableFileAccess);
  }
  
  bool ExtensionApiTest::RunExtensionSubtest(const char* extension_name,
                                             const std::string& page_url,
                                             int flags) {
    DCHECK(!page_url.empty()) << "Argument page_url is required.";
    // See http://crbug.com/177163 for details.
  #if defined(OS_WIN) && !defined(NDEBUG)
    LOG(WARNING) << "Workaround for 177163, prematurely returning";
    return true;
  #endif
    return RunExtensionTestImpl(extension_name, page_url, NULL, flags);
  }
  
  
  bool ExtensionApiTest::RunPageTest(const std::string& page_url) {
    return RunExtensionSubtest("", page_url);
  }
  
  bool ExtensionApiTest::RunPageTest(const std::string& page_url,
                                     int flags) {
    return RunExtensionSubtest("", page_url, flags);
  }
  
  bool ExtensionApiTest::RunPlatformAppTest(const char* extension_name) {
    return RunExtensionTestImpl(
        extension_name, std::string(), NULL, kFlagLaunchPlatformApp);
  }
  
  bool ExtensionApiTest::RunPlatformAppTestWithArg(
      const char* extension_name, const char* custom_arg) {
    return RunExtensionTestImpl(
        extension_name, std::string(), custom_arg, kFlagLaunchPlatformApp);
  }
  
  // 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,
                                              const char* custom_arg,
                                              int flags) {
    bool load_as_component = (flags & kFlagLoadAsComponent) != 0;
    bool launch_platform_app = (flags & kFlagLaunchPlatformApp) != 0;
    bool use_incognito = (flags & kFlagUseIncognito) != 0;
  
    if (custom_arg)
      test_config_->SetString(kTestCustomArg, custom_arg);
  
    ResultCatcher catcher;
    DCHECK(!std::string(extension_name).empty() || !page_url.empty()) <<
        "extension_name and page_url cannot both be empty";
  
    const extensions::Extension* extension = NULL;
    if (!std::string(extension_name).empty()) {
      base::FilePath extension_path = test_data_dir_.AppendASCII(extension_name);
      if (load_as_component) {
        extension = LoadExtensionAsComponent(extension_path);
      } else {
        int browser_test_flags = ExtensionBrowserTest::kFlagNone;
        if (flags & kFlagEnableIncognito)
          browser_test_flags |= ExtensionBrowserTest::kFlagEnableIncognito;
        if (flags & kFlagEnableFileAccess)
          browser_test_flags |= ExtensionBrowserTest::kFlagEnableFileAccess;
        if (flags & kFlagIgnoreManifestWarnings)
          browser_test_flags |= ExtensionBrowserTest::kFlagIgnoreManifestWarnings;
        if (flags & kFlagAllowOldManifestVersions) {
          browser_test_flags |=
              ExtensionBrowserTest::kFlagAllowOldManifestVersions;
        }
        extension = LoadExtensionWithFlags(extension_path, browser_test_flags);
      }
      if (!extension) {
        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";
  
        url = extension->GetResourceURL(page_url);
      }
  
      if (use_incognito)
        ui_test_utils::OpenURLOffTheRecord(browser()->profile(), url);
      else
        ui_test_utils::NavigateToURL(browser(), url);
  
    } else if (launch_platform_app) {
      chrome::AppLaunchParams params(browser()->profile(), extension,
                                     extension_misc::LAUNCH_NONE,
                                     NEW_WINDOW);
      params.command_line = CommandLine::ForCurrentProcess();
      chrome::OpenApplication(params);
    }
  
    if (!catcher.GetNextResult()) {
      message_ = catcher.message();
      return false;
    } else {
      return true;
    }
  }
  
  // Test that exactly one extension is loaded, and return it.
  const extensions::Extension* ExtensionApiTest::GetSingleLoadedExtension() {
    ExtensionService* service = extensions::ExtensionSystem::Get(
        browser()->profile())->extension_service();
  
    const extensions::Extension* extension = NULL;
    for (ExtensionSet::const_iterator it = service->extensions()->begin();
         it != service->extensions()->end(); ++it) {
      // Ignore any component extensions. They are automatically loaded into all
      // profiles and aren't the extension we're looking for here.
      if ((*it)->location() == extensions::Manifest::COMPONENT)
        continue;
  
      if (extension != NULL) {
        // TODO(yoz): this is misleading; it counts component extensions.
        message_ = base::StringPrintf(
            "Expected only one extension to be present.  Found %u.",
            static_cast<unsigned>(service->extensions()->size()));
        return NULL;
      }
  
      extension = it->get();
    }
  
    if (!extension) {
      message_ = "extension pointer is NULL.";
      return NULL;
    }
    return extension;
  }
  
  bool ExtensionApiTest::StartEmbeddedTestServer() {
    if (!embedded_test_server()->InitializeAndWaitUntilReady())
      return false;
  
    // Build a dictionary of values that tests can use to build URLs that
    // access the test server and local file system.  Tests can see these values
    // using the extension API function chrome.test.getConfig().
    test_config_->SetInteger(kTestServerPort,
                             embedded_test_server()->port());
  
    return true;
  }
  
  bool ExtensionApiTest::StartWebSocketServer(
      const base::FilePath& root_directory) {
    websocket_server_.reset(new net::SpawnedTestServer(
        net::SpawnedTestServer::TYPE_WS,
        net::SpawnedTestServer::kLocalhost,
        root_directory));
  
    if (!websocket_server_->Start())
      return false;
  
    test_config_->SetInteger(kTestWebSocketPort,
                             websocket_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");
  }
  