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