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