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