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/api/test/test_api.h"
     12 #include "chrome/browser/extensions/extension_service.h"
     13 #include "chrome/browser/extensions/extension_system.h"
     14 #include "chrome/browser/extensions/unpacked_installer.h"
     15 #include "chrome/browser/profiles/profile.h"
     16 #include "chrome/browser/ui/browser.h"
     17 #include "chrome/browser/ui/extensions/application_launch.h"
     18 #include "chrome/test/base/ui_test_utils.h"
     19 #include "content/public/browser/notification_registrar.h"
     20 #include "content/public/browser/notification_service.h"
     21 #include "extensions/common/extension.h"
     22 #include "net/base/escape.h"
     23 #include "net/base/net_util.h"
     24 #include "net/test/embedded_test_server/embedded_test_server.h"
     25 #include "net/test/embedded_test_server/http_request.h"
     26 #include "net/test/embedded_test_server/http_response.h"
     27 #include "net/test/spawned_test_server/spawned_test_server.h"
     28 
     29 namespace {
     30 
     31 const char kTestCustomArg[] = "customArg";
     32 const char kTestServerPort[] = "testServer.port";
     33 const char kTestDataDirectory[] = "testDataDirectory";
     34 const char kTestWebSocketPort[] = "testWebSocketPort";
     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 ExtensionApiTest::ResultCatcher::ResultCatcher()
    143     : profile_restriction_(NULL),
    144       waiting_(false) {
    145   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_TEST_PASSED,
    146                  content::NotificationService::AllSources());
    147   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_TEST_FAILED,
    148                  content::NotificationService::AllSources());
    149 }
    150 
    151 ExtensionApiTest::ResultCatcher::~ResultCatcher() {
    152 }
    153 
    154 bool ExtensionApiTest::ResultCatcher::GetNextResult() {
    155   // Depending on the tests, multiple results can come in from a single call
    156   // to RunMessageLoop(), so we maintain a queue of results and just pull them
    157   // off as the test calls this, going to the run loop only when the queue is
    158   // empty.
    159   if (results_.empty()) {
    160     waiting_ = true;
    161     content::RunMessageLoop();
    162     waiting_ = false;
    163   }
    164 
    165   if (!results_.empty()) {
    166     bool ret = results_.front();
    167     results_.pop_front();
    168     message_ = messages_.front();
    169     messages_.pop_front();
    170     return ret;
    171   }
    172 
    173   NOTREACHED();
    174   return false;
    175 }
    176 
    177 void ExtensionApiTest::ResultCatcher::Observe(
    178     int type, const content::NotificationSource& source,
    179     const content::NotificationDetails& details) {
    180   if (profile_restriction_ &&
    181       content::Source<Profile>(source).ptr() != profile_restriction_) {
    182     return;
    183   }
    184 
    185   switch (type) {
    186     case chrome::NOTIFICATION_EXTENSION_TEST_PASSED:
    187       VLOG(1) << "Got EXTENSION_TEST_PASSED notification.";
    188       results_.push_back(true);
    189       messages_.push_back(std::string());
    190       if (waiting_)
    191         base::MessageLoopForUI::current()->Quit();
    192       break;
    193 
    194     case chrome::NOTIFICATION_EXTENSION_TEST_FAILED:
    195       VLOG(1) << "Got EXTENSION_TEST_FAILED notification.";
    196       results_.push_back(false);
    197       messages_.push_back(*(content::Details<std::string>(details).ptr()));
    198       if (waiting_)
    199         base::MessageLoopForUI::current()->Quit();
    200       break;
    201 
    202     default:
    203       NOTREACHED();
    204   }
    205 }
    206 
    207 void ExtensionApiTest::SetUpInProcessBrowserTestFixture() {
    208   DCHECK(!test_config_.get()) << "Previous test did not clear config state.";
    209   test_config_.reset(new DictionaryValue());
    210   test_config_->SetString(kTestDataDirectory,
    211                           net::FilePathToFileURL(test_data_dir_).spec());
    212   test_config_->SetInteger(kTestWebSocketPort, 0);
    213   extensions::TestGetConfigFunction::set_test_config_state(
    214       test_config_.get());
    215 }
    216 
    217 void ExtensionApiTest::TearDownInProcessBrowserTestFixture() {
    218   extensions::TestGetConfigFunction::set_test_config_state(NULL);
    219   test_config_.reset(NULL);
    220 }
    221 
    222 bool ExtensionApiTest::RunExtensionTest(const std::string& extension_name) {
    223   return RunExtensionTestImpl(
    224       extension_name, std::string(), NULL, kFlagEnableFileAccess);
    225 }
    226 
    227 bool ExtensionApiTest::RunExtensionTestIncognito(
    228     const std::string& extension_name) {
    229   return RunExtensionTestImpl(extension_name,
    230                               std::string(),
    231                               NULL,
    232                               kFlagEnableIncognito | kFlagEnableFileAccess);
    233 }
    234 
    235 bool ExtensionApiTest::RunExtensionTestIgnoreManifestWarnings(
    236     const std::string& extension_name) {
    237   return RunExtensionTestImpl(
    238       extension_name, std::string(), NULL, kFlagIgnoreManifestWarnings);
    239 }
    240 
    241 bool ExtensionApiTest::RunExtensionTestAllowOldManifestVersion(
    242     const std::string& extension_name) {
    243   return RunExtensionTestImpl(
    244       extension_name,
    245       std::string(),
    246       NULL,
    247       kFlagEnableFileAccess | kFlagAllowOldManifestVersions);
    248 }
    249 
    250 bool ExtensionApiTest::RunComponentExtensionTest(
    251     const std::string& extension_name) {
    252   return RunExtensionTestImpl(extension_name,
    253                               std::string(),
    254                               NULL,
    255                               kFlagEnableFileAccess | kFlagLoadAsComponent);
    256 }
    257 
    258 bool ExtensionApiTest::RunExtensionTestNoFileAccess(
    259     const std::string& extension_name) {
    260   return RunExtensionTestImpl(extension_name, std::string(), NULL, kFlagNone);
    261 }
    262 
    263 bool ExtensionApiTest::RunExtensionTestIncognitoNoFileAccess(
    264     const std::string& extension_name) {
    265   return RunExtensionTestImpl(
    266       extension_name, std::string(), NULL, kFlagEnableIncognito);
    267 }
    268 
    269 bool ExtensionApiTest::RunExtensionSubtest(const std::string& extension_name,
    270                                            const std::string& page_url) {
    271   return RunExtensionSubtest(extension_name, page_url, kFlagEnableFileAccess);
    272 }
    273 
    274 bool ExtensionApiTest::RunExtensionSubtest(const std::string& extension_name,
    275                                            const std::string& page_url,
    276                                            int flags) {
    277   DCHECK(!page_url.empty()) << "Argument page_url is required.";
    278   // See http://crbug.com/177163 for details.
    279 #if defined(OS_WIN) && !defined(NDEBUG)
    280   LOG(WARNING) << "Workaround for 177163, prematurely returning";
    281   return true;
    282 #endif
    283   return RunExtensionTestImpl(extension_name, page_url, NULL, flags);
    284 }
    285 
    286 
    287 bool ExtensionApiTest::RunPageTest(const std::string& page_url) {
    288   return RunExtensionSubtest(std::string(), page_url);
    289 }
    290 
    291 bool ExtensionApiTest::RunPageTest(const std::string& page_url,
    292                                    int flags) {
    293   return RunExtensionSubtest(std::string(), page_url, flags);
    294 }
    295 
    296 bool ExtensionApiTest::RunPlatformAppTest(const std::string& extension_name) {
    297   return RunExtensionTestImpl(
    298       extension_name, std::string(), NULL, kFlagLaunchPlatformApp);
    299 }
    300 
    301 bool ExtensionApiTest::RunPlatformAppTestWithArg(
    302     const std::string& extension_name, const char* custom_arg) {
    303   return RunExtensionTestImpl(
    304       extension_name, std::string(), custom_arg, kFlagLaunchPlatformApp);
    305 }
    306 
    307 bool ExtensionApiTest::RunPlatformAppTestWithFlags(
    308     const std::string& extension_name, int flags) {
    309   return RunExtensionTestImpl(
    310       extension_name, std::string(), NULL, flags | kFlagLaunchPlatformApp);
    311 }
    312 
    313 // Load |extension_name| extension and/or |page_url| and wait for
    314 // PASSED or FAILED notification.
    315 bool ExtensionApiTest::RunExtensionTestImpl(const std::string& extension_name,
    316                                             const std::string& page_url,
    317                                             const char* custom_arg,
    318                                             int flags) {
    319   bool load_as_component = (flags & kFlagLoadAsComponent) != 0;
    320   bool launch_platform_app = (flags & kFlagLaunchPlatformApp) != 0;
    321   bool use_incognito = (flags & kFlagUseIncognito) != 0;
    322 
    323   if (custom_arg && custom_arg[0])
    324     test_config_->SetString(kTestCustomArg, custom_arg);
    325 
    326   ResultCatcher catcher;
    327   DCHECK(!extension_name.empty() || !page_url.empty()) <<
    328       "extension_name and page_url cannot both be empty";
    329 
    330   const extensions::Extension* extension = NULL;
    331   if (!extension_name.empty()) {
    332     base::FilePath extension_path = test_data_dir_.AppendASCII(extension_name);
    333     if (load_as_component) {
    334       extension = LoadExtensionAsComponent(extension_path);
    335     } else {
    336       int browser_test_flags = ExtensionBrowserTest::kFlagNone;
    337       if (flags & kFlagEnableIncognito)
    338         browser_test_flags |= ExtensionBrowserTest::kFlagEnableIncognito;
    339       if (flags & kFlagEnableFileAccess)
    340         browser_test_flags |= ExtensionBrowserTest::kFlagEnableFileAccess;
    341       if (flags & kFlagIgnoreManifestWarnings)
    342         browser_test_flags |= ExtensionBrowserTest::kFlagIgnoreManifestWarnings;
    343       if (flags & kFlagAllowOldManifestVersions) {
    344         browser_test_flags |=
    345             ExtensionBrowserTest::kFlagAllowOldManifestVersions;
    346       }
    347       extension = LoadExtensionWithFlags(extension_path, browser_test_flags);
    348     }
    349     if (!extension) {
    350       message_ = "Failed to load extension.";
    351       return false;
    352     }
    353   }
    354 
    355   // If there is a page_url to load, navigate it.
    356   if (!page_url.empty()) {
    357     GURL url = GURL(page_url);
    358 
    359     // Note: We use is_valid() here in the expectation that the provided url
    360     // may lack a scheme & host and thus be a relative url within the loaded
    361     // extension.
    362     if (!url.is_valid()) {
    363       DCHECK(!extension_name.empty()) <<
    364           "Relative page_url given with no extension_name";
    365 
    366       url = extension->GetResourceURL(page_url);
    367     }
    368 
    369     if (use_incognito)
    370       ui_test_utils::OpenURLOffTheRecord(browser()->profile(), url);
    371     else
    372       ui_test_utils::NavigateToURL(browser(), url);
    373   } else if (launch_platform_app) {
    374     AppLaunchParams params(browser()->profile(),
    375                            extension,
    376                            extensions::LAUNCH_CONTAINER_NONE,
    377                            NEW_WINDOW);
    378     params.command_line = CommandLine::ForCurrentProcess();
    379     OpenApplication(params);
    380   }
    381 
    382   if (!catcher.GetNextResult()) {
    383     message_ = catcher.message();
    384     return false;
    385   }
    386 
    387   return true;
    388 }
    389 
    390 // Test that exactly one extension is loaded, and return it.
    391 const extensions::Extension* ExtensionApiTest::GetSingleLoadedExtension() {
    392   ExtensionService* service = extensions::ExtensionSystem::Get(
    393       browser()->profile())->extension_service();
    394 
    395   const extensions::Extension* extension = NULL;
    396   for (ExtensionSet::const_iterator it = service->extensions()->begin();
    397        it != service->extensions()->end(); ++it) {
    398     // Ignore any component extensions. They are automatically loaded into all
    399     // profiles and aren't the extension we're looking for here.
    400     if ((*it)->location() == extensions::Manifest::COMPONENT)
    401       continue;
    402 
    403     if (extension != NULL) {
    404       // TODO(yoz): this is misleading; it counts component extensions.
    405       message_ = base::StringPrintf(
    406           "Expected only one extension to be present.  Found %u.",
    407           static_cast<unsigned>(service->extensions()->size()));
    408       return NULL;
    409     }
    410 
    411     extension = it->get();
    412   }
    413 
    414   if (!extension) {
    415     message_ = "extension pointer is NULL.";
    416     return NULL;
    417   }
    418   return extension;
    419 }
    420 
    421 bool ExtensionApiTest::StartEmbeddedTestServer() {
    422   if (!embedded_test_server()->InitializeAndWaitUntilReady())
    423     return false;
    424 
    425   // Build a dictionary of values that tests can use to build URLs that
    426   // access the test server and local file system.  Tests can see these values
    427   // using the extension API function chrome.test.getConfig().
    428   test_config_->SetInteger(kTestServerPort,
    429                            embedded_test_server()->port());
    430 
    431   return true;
    432 }
    433 
    434 bool ExtensionApiTest::StartWebSocketServer(
    435     const base::FilePath& root_directory) {
    436   websocket_server_.reset(new net::SpawnedTestServer(
    437       net::SpawnedTestServer::TYPE_WS,
    438       net::SpawnedTestServer::kLocalhost,
    439       root_directory));
    440 
    441   if (!websocket_server_->Start())
    442     return false;
    443 
    444   test_config_->SetInteger(kTestWebSocketPort,
    445                            websocket_server_->host_port_pair().port());
    446 
    447   return true;
    448 }
    449 
    450 bool ExtensionApiTest::StartSpawnedTestServer() {
    451   if (!test_server()->Start())
    452     return false;
    453 
    454   // Build a dictionary of values that tests can use to build URLs that
    455   // access the test server and local file system.  Tests can see these values
    456   // using the extension API function chrome.test.getConfig().
    457   test_config_->SetInteger(kSpawnedTestServerPort,
    458                            test_server()->host_port_pair().port());
    459 
    460   return true;
    461 }
    462 
    463 void ExtensionApiTest::SetUpCommandLine(CommandLine* command_line) {
    464   ExtensionBrowserTest::SetUpCommandLine(command_line);
    465   test_data_dir_ = test_data_dir_.AppendASCII("api_test");
    466 }
    467