Home | History | Annotate | Download | only in webstore
      1 // Copyright 2013 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 <string>
      6 
      7 #include "base/basictypes.h"
      8 #include "base/bind.h"
      9 #include "base/command_line.h"
     10 #include "base/memory/scoped_ptr.h"
     11 #include "base/run_loop.h"
     12 #include "base/strings/utf_string_conversions.h"
     13 #include "chrome/browser/profiles/profile_manager.h"
     14 #include "chrome/browser/ui/app_list/search/chrome_search_result.h"
     15 #include "chrome/browser/ui/app_list/search/webstore/webstore_provider.h"
     16 #include "chrome/browser/ui/app_list/search/webstore/webstore_result.h"
     17 #include "chrome/common/chrome_switches.h"
     18 #include "chrome/test/base/in_process_browser_test.h"
     19 #include "content/public/browser/browser_thread.h"
     20 #include "extensions/common/extension.h"
     21 #include "net/test/embedded_test_server/embedded_test_server.h"
     22 #include "net/test/embedded_test_server/http_request.h"
     23 #include "net/test/embedded_test_server/http_response.h"
     24 
     25 using content::BrowserThread;
     26 using extensions::Manifest;
     27 using net::test_server::BasicHttpResponse;
     28 using net::test_server::HttpRequest;
     29 using net::test_server::HttpResponse;
     30 using net::test_server::EmbeddedTestServer;
     31 
     32 namespace app_list {
     33 namespace test {
     34 namespace {
     35 
     36 // Mock results.
     37 const char kOneResult[] = "{"
     38     "\"search_url\": \"http://host/search\","
     39     "\"results\":["
     40       "{"
     41         "\"id\": \"app1_id\","
     42         "\"localized_name\": \"app1 name\","
     43         "\"icon_url\": \"http://host/icon\","
     44         "\"is_paid\": false"
     45       "}"
     46     "]}";
     47 
     48 const char kThreeResults[] = "{"
     49     "\"search_url\": \"http://host/search\","
     50     "\"results\":["
     51       "{"
     52         "\"id\": \"app1_id\","
     53         "\"localized_name\": \"one\","
     54         "\"icon_url\": \"http://host/icon1\","
     55         "\"is_paid\": true,"
     56         "\"item_type\": \"PLATFORM_APP\""
     57       "},"
     58       "{"
     59         "\"id\": \"app2_id\","
     60         "\"localized_name\": \"two\","
     61         "\"icon_url\": \"http://host/icon2\","
     62         "\"is_paid\": false,"
     63         "\"item_type\": \"HOSTED_APP\""
     64       "},"
     65       "{"
     66         "\"id\": \"app3_id\","
     67         "\"localized_name\": \"three\","
     68         "\"icon_url\": \"http://host/icon3\","
     69         "\"is_paid\": false,"
     70         "\"item_type\": \"LEGACY_PACKAGED_APP\""
     71       "}"
     72     "]}";
     73 
     74 struct ParsedSearchResult {
     75   const char* id;
     76   const char* title;
     77   const char* icon_url;
     78   bool is_paid;
     79   Manifest::Type item_type;
     80   size_t num_actions;
     81 };
     82 
     83 ParsedSearchResult kParsedOneResult[] = {{"app1_id", "app1 name",
     84                                           "http://host/icon", false,
     85                                           Manifest::TYPE_UNKNOWN, 1}};
     86 
     87 ParsedSearchResult kParsedThreeResults[] = {
     88     {"app1_id", "one", "http://host/icon1", true, Manifest::TYPE_PLATFORM_APP,
     89      1},
     90     {"app2_id", "two", "http://host/icon2", false, Manifest::TYPE_HOSTED_APP,
     91      2},
     92     {"app3_id", "three", "http://host/icon3", false,
     93      Manifest::TYPE_LEGACY_PACKAGED_APP, 1}};
     94 
     95 }  // namespace
     96 
     97 class WebstoreProviderTest : public InProcessBrowserTest {
     98  public:
     99   WebstoreProviderTest() {}
    100   virtual ~WebstoreProviderTest() {}
    101 
    102   // InProcessBrowserTest overrides:
    103   virtual void SetUpOnMainThread() OVERRIDE {
    104     test_server_.reset(new EmbeddedTestServer);
    105 
    106     ASSERT_TRUE(test_server_->InitializeAndWaitUntilReady());
    107     test_server_->RegisterRequestHandler(
    108         base::Bind(&WebstoreProviderTest::HandleRequest,
    109                    base::Unretained(this)));
    110     CommandLine::ForCurrentProcess()->AppendSwitchASCII(
    111         switches::kAppsGalleryURL, test_server_->base_url().spec());
    112     CommandLine::ForCurrentProcess()->AppendSwitch(
    113         switches::kEnableEphemeralApps);
    114 
    115     webstore_provider_.reset(new WebstoreProvider(
    116         ProfileManager::GetActiveUserProfile(), NULL));
    117     webstore_provider_->set_webstore_search_fetched_callback(
    118         base::Bind(&WebstoreProviderTest::OnSearchResultsFetched,
    119                    base::Unretained(this)));
    120     // TODO(mukai): add test cases for throttling.
    121     webstore_provider_->set_use_throttling(false);
    122   }
    123 
    124   virtual void TearDownOnMainThread() OVERRIDE {
    125     EXPECT_TRUE(test_server_->ShutdownAndWaitUntilComplete());
    126     test_server_.reset();
    127   }
    128 
    129   void RunQuery(const std::string& query,
    130                 const std::string& mock_server_response) {
    131     webstore_provider_->Start(base::UTF8ToUTF16(query));
    132 
    133     if (webstore_provider_->webstore_search_ && !mock_server_response.empty()) {
    134       mock_server_response_ = mock_server_response;
    135 
    136       DCHECK(!run_loop_);
    137       run_loop_.reset(new base::RunLoop);
    138       run_loop_->Run();
    139       run_loop_.reset();
    140 
    141       mock_server_response_.clear();
    142     }
    143 
    144     webstore_provider_->Stop();
    145   }
    146 
    147   std::string GetResultTitles() const {
    148     std::string results;
    149     for (SearchProvider::Results::const_iterator it =
    150              webstore_provider_->results().begin();
    151          it != webstore_provider_->results().end();
    152          ++it) {
    153       if (!results.empty())
    154         results += ',';
    155       results += base::UTF16ToUTF8((*it)->title());
    156     }
    157     return results;
    158   }
    159 
    160   void VerifyResults(const ParsedSearchResult* expected_results,
    161                      size_t expected_result_size) {
    162     ASSERT_EQ(expected_result_size, webstore_provider_->results().size());
    163     for (size_t i = 0; i < expected_result_size; ++i) {
    164       const SearchResult* result = webstore_provider_->results()[i];
    165       ASSERT_EQ(extensions::Extension::GetBaseURLFromExtensionId(
    166                     expected_results[i].id).spec(),
    167                 result->id());
    168       EXPECT_EQ(std::string(expected_results[i].title),
    169                 base::UTF16ToUTF8(result->title()));
    170 
    171       // Ensure the number of action buttons is appropriate for the item type.
    172       EXPECT_EQ(expected_results[i].num_actions, result->actions().size());
    173 
    174       const WebstoreResult* webstore_result =
    175           static_cast<const WebstoreResult*>(result);
    176       EXPECT_EQ(expected_results[i].id, webstore_result->app_id());
    177       EXPECT_EQ(expected_results[i].icon_url,
    178                 webstore_result->icon_url().spec());
    179       EXPECT_EQ(expected_results[i].is_paid, webstore_result->is_paid());
    180       EXPECT_EQ(expected_results[i].item_type, webstore_result->item_type());
    181     }
    182   }
    183 
    184   void RunQueryAndVerify(const std::string& query,
    185                          const std::string& mock_server_response,
    186                          const ParsedSearchResult* expected_results,
    187                          size_t expected_result_size) {
    188     RunQuery(query, mock_server_response);
    189     VerifyResults(expected_results, expected_result_size);
    190   }
    191 
    192   WebstoreProvider* webstore_provider() { return webstore_provider_.get(); }
    193 
    194  private:
    195   scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request) {
    196     scoped_ptr<BasicHttpResponse> response(new BasicHttpResponse);
    197 
    198     if (request.relative_url.find("/jsonsearch?") != std::string::npos) {
    199       if (mock_server_response_ == "404") {
    200         response->set_code(net::HTTP_NOT_FOUND);
    201       } else if (mock_server_response_ == "500") {
    202         response->set_code(net::HTTP_INTERNAL_SERVER_ERROR);
    203       } else {
    204         response->set_code(net::HTTP_OK);
    205         response->set_content(mock_server_response_);
    206       }
    207     }
    208 
    209     return response.PassAs<HttpResponse>();
    210   }
    211 
    212   void OnSearchResultsFetched() {
    213     if (run_loop_)
    214       run_loop_->Quit();
    215   }
    216 
    217   scoped_ptr<EmbeddedTestServer> test_server_;
    218   scoped_ptr<base::RunLoop> run_loop_;
    219 
    220   std::string mock_server_response_;
    221 
    222   scoped_ptr<WebstoreProvider> webstore_provider_;
    223 
    224   DISALLOW_COPY_AND_ASSIGN(WebstoreProviderTest);
    225 };
    226 
    227 // Flaky on CrOS and Windows: http://crbug.com/246136.
    228 // TODO(erg): linux_aura bringup: http://crbug.com/163931
    229 #if defined(OS_WIN) || defined(OS_LINUX)
    230 #define MAYBE_Basic DISABLED_Basic
    231 #else
    232 #define MAYBE_Basic Basic
    233 #endif
    234 IN_PROC_BROWSER_TEST_F(WebstoreProviderTest, MAYBE_Basic) {
    235   struct {
    236     const char* query;
    237     const char* mock_server_response;
    238     const char* expected_result_titles;
    239     const ParsedSearchResult* expected_results;
    240     size_t expected_result_size;
    241   } kTestCases[] = {
    242     // "Search in web store" result with query text itself is used for
    243     // synchronous placeholder, bad server response etc.
    244     {"synchronous", "", "synchronous", NULL, 0 },
    245     {"404", "404", "404", NULL, 0 },
    246     {"500", "500", "500", NULL, 0 },
    247     {"bad json", "invalid json", "bad json", NULL, 0 },
    248     // Good results.
    249     {"1 result", kOneResult, "app1 name", kParsedOneResult, 1 },
    250     {"3 result", kThreeResults, "one,two,three", kParsedThreeResults, 3 },
    251   };
    252 
    253   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
    254     if (kTestCases[i].expected_result_titles) {
    255       RunQuery(kTestCases[i].query, kTestCases[i].mock_server_response);
    256       ASSERT_EQ(kTestCases[i].expected_result_titles, GetResultTitles())
    257           << "Case " << i << ": q=" << kTestCases[i].query;
    258 
    259       if (kTestCases[i].expected_results) {
    260         VerifyResults(kTestCases[i].expected_results,
    261                       kTestCases[i].expected_result_size);
    262       }
    263     }
    264   }
    265 }
    266 
    267 IN_PROC_BROWSER_TEST_F(WebstoreProviderTest, NoSearchForSensitiveData) {
    268   // None of the following input strings should be accepted because they may
    269   // contain private data.
    270   const char* inputs[] = {
    271     // file: scheme is bad.
    272     "file://filename",
    273     "FILE://filename",
    274     // URLs with usernames, ports, queries or refs are bad.
    275     "http://username:password@hostname/",
    276     "http://www.example.com:1000",
    277     "http://foo:1000",
    278     "http://hostname/?query=q",
    279     "http://hostname/path#ref",
    280     // A https URL with path is bad.
    281     "https://hostname/path",
    282   };
    283 
    284   for (size_t i = 0; i < arraysize(inputs); ++i) {
    285     RunQueryAndVerify(inputs[i], kOneResult, NULL, 0);
    286   }
    287 }
    288 
    289 IN_PROC_BROWSER_TEST_F(WebstoreProviderTest, NoSearchForShortQueries) {
    290   RunQueryAndVerify("a", kOneResult, NULL, 0);
    291   RunQueryAndVerify("ab", kOneResult, NULL, 0);
    292   RunQueryAndVerify("abc", kOneResult, kParsedOneResult, 1);
    293 }
    294 
    295 // Flaky on CrOS and Windows: http://crbug.com/246136.
    296 #if defined(OS_WIN) || defined(OS_CHROMEOS)
    297 #define MAYBE_SearchCache DISABLED_SearchCache
    298 #else
    299 #define MAYBE_SearchCache SearchCache
    300 #endif
    301 IN_PROC_BROWSER_TEST_F(WebstoreProviderTest, MAYBE_SearchCache) {
    302   RunQueryAndVerify("foo", kOneResult, kParsedOneResult, 1);
    303 
    304   // No result is provided but the provider gets the result from the cache.
    305   RunQueryAndVerify("foo", "", kParsedOneResult, 1);
    306 }
    307 
    308 }  // namespace test
    309 }  // namespace app_list
    310