Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/extensions/extension_apitest.h"
      6 
      7 #include "base/strings/string_split.h"
      8 #include "base/strings/string_util.h"
      9 #include "base/strings/stringprintf.h"
     10 #include "chrome/browser/extensions/extension_service.h"
     11 #include "chrome/browser/extensions/unpacked_installer.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/ui/browser.h"
     14 #include "chrome/browser/ui/extensions/application_launch.h"
     15 #include "chrome/test/base/ui_test_utils.h"
     16 #include "extensions/browser/api/test/test_api.h"
     17 #include "extensions/browser/extension_system.h"
     18 #include "extensions/common/extension.h"
     19 #include "extensions/common/extension_set.h"
     20 #include "extensions/test/result_catcher.h"
     21 #include "net/base/escape.h"
     22 #include "net/base/filename_util.h"
     23 #include "net/test/embedded_test_server/embedded_test_server.h"
     24 #include "net/test/embedded_test_server/http_request.h"
     25 #include "net/test/embedded_test_server/http_response.h"
     26 #include "net/test/spawned_test_server/spawned_test_server.h"
     27 
     28 namespace {
     29 
     30 const char kTestCustomArg[] = "customArg";
     31 const char kTestServerPort[] = "testServer.port";
     32 const char kTestDataDirectory[] = "testDataDirectory";
     33 const char kTestWebSocketPort[] = "testWebSocketPort";
     34 const char kFtpServerPort[] = "ftpServer.port";
     35 const char kSpawnedTestServerPort[] = "spawnedTestServer.port";
     36 
     37 scoped_ptr<net::test_server::HttpResponse> HandleServerRedirectRequest(
     38     const net::test_server::HttpRequest& request) {
     39   if (!StartsWithASCII(request.relative_url, "/server-redirect?", true))
     40     return scoped_ptr<net::test_server::HttpResponse>();
     41 
     42   size_t query_string_pos = request.relative_url.find('?');
     43   std::string redirect_target =
     44       request.relative_url.substr(query_string_pos + 1);
     45 
     46   scoped_ptr<net::test_server::BasicHttpResponse> http_response(
     47       new net::test_server::BasicHttpResponse);
     48   http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
     49   http_response->AddCustomHeader("Location", redirect_target);
     50   return http_response.PassAs<net::test_server::HttpResponse>();
     51 }
     52 
     53 scoped_ptr<net::test_server::HttpResponse> HandleEchoHeaderRequest(
     54     const net::test_server::HttpRequest& request) {
     55   if (!StartsWithASCII(request.relative_url, "/echoheader?", true))
     56     return scoped_ptr<net::test_server::HttpResponse>();
     57 
     58   size_t query_string_pos = request.relative_url.find('?');
     59   std::string header_name =
     60       request.relative_url.substr(query_string_pos + 1);
     61 
     62   std::string header_value;
     63   std::map<std::string, std::string>::const_iterator it = request.headers.find(
     64       header_name);
     65   if (it != request.headers.end())
     66     header_value = it->second;
     67 
     68   scoped_ptr<net::test_server::BasicHttpResponse> http_response(
     69       new net::test_server::BasicHttpResponse);
     70   http_response->set_code(net::HTTP_OK);
     71   http_response->set_content(header_value);
     72   return http_response.PassAs<net::test_server::HttpResponse>();
     73 }
     74 
     75 scoped_ptr<net::test_server::HttpResponse> HandleSetCookieRequest(
     76     const net::test_server::HttpRequest& request) {
     77   if (!StartsWithASCII(request.relative_url, "/set-cookie?", true))
     78     return scoped_ptr<net::test_server::HttpResponse>();
     79 
     80   scoped_ptr<net::test_server::BasicHttpResponse> http_response(
     81       new net::test_server::BasicHttpResponse);
     82   http_response->set_code(net::HTTP_OK);
     83 
     84   size_t query_string_pos = request.relative_url.find('?');
     85   std::string cookie_value =
     86       request.relative_url.substr(query_string_pos + 1);
     87 
     88   std::vector<std::string> cookies;
     89   base::SplitString(cookie_value, '&', &cookies);
     90 
     91   for (size_t i = 0; i < cookies.size(); i++)
     92     http_response->AddCustomHeader("Set-Cookie", cookies[i]);
     93 
     94   return http_response.PassAs<net::test_server::HttpResponse>();
     95 }
     96 
     97 scoped_ptr<net::test_server::HttpResponse> HandleSetHeaderRequest(
     98     const net::test_server::HttpRequest& request) {
     99   if (!StartsWithASCII(request.relative_url, "/set-header?", true))
    100     return scoped_ptr<net::test_server::HttpResponse>();
    101 
    102   size_t query_string_pos = request.relative_url.find('?');
    103   std::string escaped_header =
    104       request.relative_url.substr(query_string_pos + 1);
    105 
    106   std::string header =
    107       net::UnescapeURLComponent(escaped_header,
    108                                 net::UnescapeRule::NORMAL |
    109                                 net::UnescapeRule::SPACES |
    110                                 net::UnescapeRule::URL_SPECIAL_CHARS);
    111 
    112   size_t colon_pos = header.find(':');
    113   if (colon_pos == std::string::npos)
    114     return scoped_ptr<net::test_server::HttpResponse>();
    115 
    116   std::string header_name = header.substr(0, colon_pos);
    117   // Skip space after colon.
    118   std::string header_value = header.substr(colon_pos + 2);
    119 
    120   scoped_ptr<net::test_server::BasicHttpResponse> http_response(
    121       new net::test_server::BasicHttpResponse);
    122   http_response->set_code(net::HTTP_OK);
    123   http_response->AddCustomHeader(header_name, header_value);
    124   return http_response.PassAs<net::test_server::HttpResponse>();
    125 }
    126 
    127 };  // namespace
    128 
    129 ExtensionApiTest::ExtensionApiTest() {
    130   embedded_test_server()->RegisterRequestHandler(
    131       base::Bind(&HandleServerRedirectRequest));
    132   embedded_test_server()->RegisterRequestHandler(
    133       base::Bind(&HandleEchoHeaderRequest));
    134   embedded_test_server()->RegisterRequestHandler(
    135       base::Bind(&HandleSetCookieRequest));
    136   embedded_test_server()->RegisterRequestHandler(
    137       base::Bind(&HandleSetHeaderRequest));
    138 }
    139 
    140 ExtensionApiTest::~ExtensionApiTest() {}
    141 
    142 void ExtensionApiTest::SetUpInProcessBrowserTestFixture() {
    143   DCHECK(!test_config_.get()) << "Previous test did not clear config state.";
    144   test_config_.reset(new base::DictionaryValue());
    145   test_config_->SetString(kTestDataDirectory,
    146                           net::FilePathToFileURL(test_data_dir_).spec());
    147   test_config_->SetInteger(kTestWebSocketPort, 0);
    148   extensions::TestGetConfigFunction::set_test_config_state(
    149       test_config_.get());
    150 }
    151 
    152 void ExtensionApiTest::TearDownInProcessBrowserTestFixture() {
    153   extensions::TestGetConfigFunction::set_test_config_state(NULL);
    154   test_config_.reset(NULL);
    155 }
    156 
    157 bool ExtensionApiTest::RunExtensionTest(const std::string& extension_name) {
    158   return RunExtensionTestImpl(
    159       extension_name, std::string(), NULL, kFlagEnableFileAccess);
    160 }
    161 
    162 bool ExtensionApiTest::RunExtensionTestIncognito(
    163     const std::string& extension_name) {
    164   return RunExtensionTestImpl(extension_name,
    165                               std::string(),
    166                               NULL,
    167                               kFlagEnableIncognito | kFlagEnableFileAccess);
    168 }
    169 
    170 bool ExtensionApiTest::RunExtensionTestIgnoreManifestWarnings(
    171     const std::string& extension_name) {
    172   return RunExtensionTestImpl(
    173       extension_name, std::string(), NULL, kFlagIgnoreManifestWarnings);
    174 }
    175 
    176 bool ExtensionApiTest::RunExtensionTestAllowOldManifestVersion(
    177     const std::string& extension_name) {
    178   return RunExtensionTestImpl(
    179       extension_name,
    180       std::string(),
    181       NULL,
    182       kFlagEnableFileAccess | kFlagAllowOldManifestVersions);
    183 }
    184 
    185 bool ExtensionApiTest::RunComponentExtensionTest(
    186     const std::string& extension_name) {
    187   return RunExtensionTestImpl(extension_name,
    188                               std::string(),
    189                               NULL,
    190                               kFlagEnableFileAccess | kFlagLoadAsComponent);
    191 }
    192 
    193 bool ExtensionApiTest::RunExtensionTestNoFileAccess(
    194     const std::string& extension_name) {
    195   return RunExtensionTestImpl(extension_name, std::string(), NULL, kFlagNone);
    196 }
    197 
    198 bool ExtensionApiTest::RunExtensionTestIncognitoNoFileAccess(
    199     const std::string& extension_name) {
    200   return RunExtensionTestImpl(
    201       extension_name, std::string(), NULL, kFlagEnableIncognito);
    202 }
    203 
    204 bool ExtensionApiTest::RunExtensionSubtest(const std::string& extension_name,
    205                                            const std::string& page_url) {
    206   return RunExtensionSubtest(extension_name, page_url, kFlagEnableFileAccess);
    207 }
    208 
    209 bool ExtensionApiTest::RunExtensionSubtest(const std::string& extension_name,
    210                                            const std::string& page_url,
    211                                            int flags) {
    212   DCHECK(!page_url.empty()) << "Argument page_url is required.";
    213   // See http://crbug.com/177163 for details.
    214 #if defined(OS_WIN) && !defined(NDEBUG)
    215   LOG(WARNING) << "Workaround for 177163, prematurely returning";
    216   return true;
    217 #else
    218   return RunExtensionTestImpl(extension_name, page_url, NULL, flags);
    219 #endif
    220 }
    221 
    222 
    223 bool ExtensionApiTest::RunPageTest(const std::string& page_url) {
    224   return RunExtensionSubtest(std::string(), page_url);
    225 }
    226 
    227 bool ExtensionApiTest::RunPageTest(const std::string& page_url,
    228                                    int flags) {
    229   return RunExtensionSubtest(std::string(), page_url, flags);
    230 }
    231 
    232 bool ExtensionApiTest::RunPlatformAppTest(const std::string& extension_name) {
    233   return RunExtensionTestImpl(
    234       extension_name, std::string(), NULL, kFlagLaunchPlatformApp);
    235 }
    236 
    237 bool ExtensionApiTest::RunPlatformAppTestWithArg(
    238     const std::string& extension_name, const char* custom_arg) {
    239   return RunExtensionTestImpl(
    240       extension_name, std::string(), custom_arg, kFlagLaunchPlatformApp);
    241 }
    242 
    243 bool ExtensionApiTest::RunPlatformAppTestWithFlags(
    244     const std::string& extension_name, int flags) {
    245   return RunExtensionTestImpl(
    246       extension_name, std::string(), NULL, flags | kFlagLaunchPlatformApp);
    247 }
    248 
    249 // Load |extension_name| extension and/or |page_url| and wait for
    250 // PASSED or FAILED notification.
    251 bool ExtensionApiTest::RunExtensionTestImpl(const std::string& extension_name,
    252                                             const std::string& page_url,
    253                                             const char* custom_arg,
    254                                             int flags) {
    255   bool load_as_component = (flags & kFlagLoadAsComponent) != 0;
    256   bool launch_platform_app = (flags & kFlagLaunchPlatformApp) != 0;
    257   bool use_incognito = (flags & kFlagUseIncognito) != 0;
    258 
    259   if (custom_arg && custom_arg[0])
    260     test_config_->SetString(kTestCustomArg, custom_arg);
    261 
    262   extensions::ResultCatcher catcher;
    263   DCHECK(!extension_name.empty() || !page_url.empty()) <<
    264       "extension_name and page_url cannot both be empty";
    265 
    266   const extensions::Extension* extension = NULL;
    267   if (!extension_name.empty()) {
    268     base::FilePath extension_path = test_data_dir_.AppendASCII(extension_name);
    269     if (load_as_component) {
    270       extension = LoadExtensionAsComponent(extension_path);
    271     } else {
    272       int browser_test_flags = ExtensionBrowserTest::kFlagNone;
    273       if (flags & kFlagEnableIncognito)
    274         browser_test_flags |= ExtensionBrowserTest::kFlagEnableIncognito;
    275       if (flags & kFlagEnableFileAccess)
    276         browser_test_flags |= ExtensionBrowserTest::kFlagEnableFileAccess;
    277       if (flags & kFlagIgnoreManifestWarnings)
    278         browser_test_flags |= ExtensionBrowserTest::kFlagIgnoreManifestWarnings;
    279       if (flags & kFlagAllowOldManifestVersions) {
    280         browser_test_flags |=
    281             ExtensionBrowserTest::kFlagAllowOldManifestVersions;
    282       }
    283       extension = LoadExtensionWithFlags(extension_path, browser_test_flags);
    284     }
    285     if (!extension) {
    286       message_ = "Failed to load extension.";
    287       return false;
    288     }
    289   }
    290 
    291   // If there is a page_url to load, navigate it.
    292   if (!page_url.empty()) {
    293     GURL url = GURL(page_url);
    294 
    295     // Note: We use is_valid() here in the expectation that the provided url
    296     // may lack a scheme & host and thus be a relative url within the loaded
    297     // extension.
    298     if (!url.is_valid()) {
    299       DCHECK(!extension_name.empty()) <<
    300           "Relative page_url given with no extension_name";
    301 
    302       url = extension->GetResourceURL(page_url);
    303     }
    304 
    305     if (use_incognito)
    306       ui_test_utils::OpenURLOffTheRecord(browser()->profile(), url);
    307     else
    308       ui_test_utils::NavigateToURL(browser(), url);
    309   } else if (launch_platform_app) {
    310     AppLaunchParams params(browser()->profile(),
    311                            extension,
    312                            extensions::LAUNCH_CONTAINER_NONE,
    313                            NEW_WINDOW);
    314     params.command_line = *CommandLine::ForCurrentProcess();
    315     OpenApplication(params);
    316   }
    317 
    318   if (!catcher.GetNextResult()) {
    319     message_ = catcher.message();
    320     return false;
    321   }
    322 
    323   return true;
    324 }
    325 
    326 // Test that exactly one extension is loaded, and return it.
    327 const extensions::Extension* ExtensionApiTest::GetSingleLoadedExtension() {
    328   ExtensionService* service = extensions::ExtensionSystem::Get(
    329       browser()->profile())->extension_service();
    330 
    331   const extensions::Extension* extension = NULL;
    332   for (extensions::ExtensionSet::const_iterator it =
    333            service->extensions()->begin();
    334        it != service->extensions()->end(); ++it) {
    335     // Ignore any component extensions. They are automatically loaded into all
    336     // profiles and aren't the extension we're looking for here.
    337     if ((*it)->location() == extensions::Manifest::COMPONENT)
    338       continue;
    339 
    340     if (extension != NULL) {
    341       // TODO(yoz): this is misleading; it counts component extensions.
    342       message_ = base::StringPrintf(
    343           "Expected only one extension to be present.  Found %u.",
    344           static_cast<unsigned>(service->extensions()->size()));
    345       return NULL;
    346     }
    347 
    348     extension = it->get();
    349   }
    350 
    351   if (!extension) {
    352     message_ = "extension pointer is NULL.";
    353     return NULL;
    354   }
    355   return extension;
    356 }
    357 
    358 bool ExtensionApiTest::StartEmbeddedTestServer() {
    359   if (!embedded_test_server()->InitializeAndWaitUntilReady())
    360     return false;
    361 
    362   // Build a dictionary of values that tests can use to build URLs that
    363   // access the test server and local file system.  Tests can see these values
    364   // using the extension API function chrome.test.getConfig().
    365   test_config_->SetInteger(kTestServerPort,
    366                            embedded_test_server()->port());
    367 
    368   return true;
    369 }
    370 
    371 bool ExtensionApiTest::StartWebSocketServer(
    372     const base::FilePath& root_directory) {
    373   websocket_server_.reset(new net::SpawnedTestServer(
    374       net::SpawnedTestServer::TYPE_WS,
    375       net::SpawnedTestServer::kLocalhost,
    376       root_directory));
    377 
    378   if (!websocket_server_->Start())
    379     return false;
    380 
    381   test_config_->SetInteger(kTestWebSocketPort,
    382                            websocket_server_->host_port_pair().port());
    383 
    384   return true;
    385 }
    386 
    387 bool ExtensionApiTest::StartFTPServer(const base::FilePath& root_directory) {
    388   ftp_server_.reset(new net::SpawnedTestServer(
    389       net::SpawnedTestServer::TYPE_FTP,
    390       net::SpawnedTestServer::kLocalhost,
    391       root_directory));
    392 
    393   if (!ftp_server_->Start())
    394     return false;
    395 
    396   test_config_->SetInteger(kFtpServerPort,
    397                            ftp_server_->host_port_pair().port());
    398 
    399   return true;
    400 }
    401 
    402 bool ExtensionApiTest::StartSpawnedTestServer() {
    403   if (!test_server()->Start())
    404     return false;
    405 
    406   // Build a dictionary of values that tests can use to build URLs that
    407   // access the test server and local file system.  Tests can see these values
    408   // using the extension API function chrome.test.getConfig().
    409   test_config_->SetInteger(kSpawnedTestServerPort,
    410                            test_server()->host_port_pair().port());
    411 
    412   return true;
    413 }
    414 
    415 void ExtensionApiTest::SetUpCommandLine(CommandLine* command_line) {
    416   ExtensionBrowserTest::SetUpCommandLine(command_line);
    417   test_data_dir_ = test_data_dir_.AppendASCII("api_test");
    418 }
    419