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