blob: 2a7ac1c87efd351d07e0b45eae987b148d4cddb3 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/memory/scoped_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/app_list/search/webstore/webstore_provider.h"
#include "chrome/browser/ui/app_list/search/webstore/webstore_result.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/common/extension.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "ui/app_list/search_result.h"
using content::BrowserThread;
using extensions::Manifest;
using net::test_server::BasicHttpResponse;
using net::test_server::HttpRequest;
using net::test_server::HttpResponse;
using net::test_server::EmbeddedTestServer;
namespace app_list {
namespace test {
namespace {
// Mock results.
const char kOneResult[] = "{"
"\"search_url\": \"http://host/search\","
"\"results\":["
"{"
"\"id\": \"app1_id\","
"\"localized_name\": \"app1 name\","
"\"icon_url\": \"http://host/icon\","
"\"is_paid\": false"
"}"
"]}";
const char kThreeResults[] = "{"
"\"search_url\": \"http://host/search\","
"\"results\":["
"{"
"\"id\": \"app1_id\","
"\"localized_name\": \"one\","
"\"icon_url\": \"http://host/icon1\","
"\"is_paid\": true,"
"\"item_type\": \"PLATFORM_APP\""
"},"
"{"
"\"id\": \"app2_id\","
"\"localized_name\": \"two\","
"\"icon_url\": \"http://host/icon2\","
"\"is_paid\": false,"
"\"item_type\": \"HOSTED_APP\""
"},"
"{"
"\"id\": \"app3_id\","
"\"localized_name\": \"three\","
"\"icon_url\": \"http://host/icon3\","
"\"is_paid\": false,"
"\"item_type\": \"LEGACY_PACKAGED_APP\""
"}"
"]}";
struct ParsedSearchResult {
const char* id;
const char* title;
const char* icon_url;
bool is_paid;
Manifest::Type item_type;
size_t num_actions;
};
ParsedSearchResult kParsedOneResult[] = {{"app1_id", "app1 name",
"http://host/icon", false,
Manifest::TYPE_UNKNOWN, 1}};
ParsedSearchResult kParsedThreeResults[] = {
{"app1_id", "one", "http://host/icon1", true, Manifest::TYPE_PLATFORM_APP,
1},
{"app2_id", "two", "http://host/icon2", false, Manifest::TYPE_HOSTED_APP,
2},
{"app3_id", "three", "http://host/icon3", false,
Manifest::TYPE_LEGACY_PACKAGED_APP, 1}};
} // namespace
class WebstoreProviderTest : public InProcessBrowserTest {
public:
WebstoreProviderTest() {}
virtual ~WebstoreProviderTest() {}
// InProcessBrowserTest overrides:
void SetUpOnMainThread() override {
test_server_.reset(new EmbeddedTestServer);
ASSERT_TRUE(test_server_->InitializeAndWaitUntilReady());
test_server_->RegisterRequestHandler(
base::Bind(&WebstoreProviderTest::HandleRequest,
base::Unretained(this)));
CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kAppsGalleryURL, test_server_->base_url().spec());
CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kEnableEphemeralApps);
webstore_provider_.reset(new WebstoreProvider(
ProfileManager::GetActiveUserProfile(), NULL));
webstore_provider_->set_webstore_search_fetched_callback(
base::Bind(&WebstoreProviderTest::OnSearchResultsFetched,
base::Unretained(this)));
// TODO(mukai): add test cases for throttling.
webstore_provider_->set_use_throttling(false);
}
void TearDownOnMainThread() override {
EXPECT_TRUE(test_server_->ShutdownAndWaitUntilComplete());
test_server_.reset();
}
void RunQuery(const std::string& query,
const std::string& mock_server_response) {
webstore_provider_->Start(base::UTF8ToUTF16(query));
if (webstore_provider_->webstore_search_ && !mock_server_response.empty()) {
mock_server_response_ = mock_server_response;
DCHECK(!run_loop_);
run_loop_.reset(new base::RunLoop);
run_loop_->Run();
run_loop_.reset();
mock_server_response_.clear();
}
webstore_provider_->Stop();
}
std::string GetResultTitles() const {
std::string results;
for (SearchProvider::Results::const_iterator it =
webstore_provider_->results().begin();
it != webstore_provider_->results().end();
++it) {
if (!results.empty())
results += ',';
results += base::UTF16ToUTF8((*it)->title());
}
return results;
}
void VerifyResults(const ParsedSearchResult* expected_results,
size_t expected_result_size) {
ASSERT_EQ(expected_result_size, webstore_provider_->results().size());
for (size_t i = 0; i < expected_result_size; ++i) {
const SearchResult* result = webstore_provider_->results()[i];
ASSERT_EQ(extensions::Extension::GetBaseURLFromExtensionId(
expected_results[i].id).spec(),
result->id());
EXPECT_EQ(std::string(expected_results[i].title),
base::UTF16ToUTF8(result->title()));
// Ensure the number of action buttons is appropriate for the item type.
EXPECT_EQ(expected_results[i].num_actions, result->actions().size());
const WebstoreResult* webstore_result =
static_cast<const WebstoreResult*>(result);
EXPECT_EQ(expected_results[i].id, webstore_result->app_id());
EXPECT_EQ(expected_results[i].icon_url,
webstore_result->icon_url().spec());
EXPECT_EQ(expected_results[i].is_paid, webstore_result->is_paid());
EXPECT_EQ(expected_results[i].item_type, webstore_result->item_type());
}
}
void RunQueryAndVerify(const std::string& query,
const std::string& mock_server_response,
const ParsedSearchResult* expected_results,
size_t expected_result_size) {
RunQuery(query, mock_server_response);
VerifyResults(expected_results, expected_result_size);
}
WebstoreProvider* webstore_provider() { return webstore_provider_.get(); }
private:
scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request) {
scoped_ptr<BasicHttpResponse> response(new BasicHttpResponse);
if (request.relative_url.find("/jsonsearch?") != std::string::npos) {
if (mock_server_response_ == "404") {
response->set_code(net::HTTP_NOT_FOUND);
} else if (mock_server_response_ == "500") {
response->set_code(net::HTTP_INTERNAL_SERVER_ERROR);
} else {
response->set_code(net::HTTP_OK);
response->set_content(mock_server_response_);
}
}
return response.Pass();
}
void OnSearchResultsFetched() {
if (run_loop_)
run_loop_->Quit();
}
scoped_ptr<EmbeddedTestServer> test_server_;
scoped_ptr<base::RunLoop> run_loop_;
std::string mock_server_response_;
scoped_ptr<WebstoreProvider> webstore_provider_;
DISALLOW_COPY_AND_ASSIGN(WebstoreProviderTest);
};
// Flaky on CrOS and Windows: http://crbug.com/246136.
// TODO(erg): linux_aura bringup: http://crbug.com/163931
#if defined(OS_WIN) || defined(OS_LINUX)
#define MAYBE_Basic DISABLED_Basic
#else
#define MAYBE_Basic Basic
#endif
IN_PROC_BROWSER_TEST_F(WebstoreProviderTest, MAYBE_Basic) {
struct {
const char* query;
const char* mock_server_response;
const char* expected_result_titles;
const ParsedSearchResult* expected_results;
size_t expected_result_size;
} kTestCases[] = {
// "Search in web store" result with query text itself is used for
// synchronous placeholder, bad server response etc.
{"synchronous", "", "synchronous", NULL, 0 },
{"404", "404", "404", NULL, 0 },
{"500", "500", "500", NULL, 0 },
{"bad json", "invalid json", "bad json", NULL, 0 },
// Good results.
{"1 result", kOneResult, "app1 name", kParsedOneResult, 1 },
{"3 result", kThreeResults, "one,two,three", kParsedThreeResults, 3 },
};
for (size_t i = 0; i < arraysize(kTestCases); ++i) {
if (kTestCases[i].expected_result_titles) {
RunQuery(kTestCases[i].query, kTestCases[i].mock_server_response);
ASSERT_EQ(kTestCases[i].expected_result_titles, GetResultTitles())
<< "Case " << i << ": q=" << kTestCases[i].query;
if (kTestCases[i].expected_results) {
VerifyResults(kTestCases[i].expected_results,
kTestCases[i].expected_result_size);
}
}
}
}
IN_PROC_BROWSER_TEST_F(WebstoreProviderTest, NoSearchForSensitiveData) {
// None of the following input strings should be accepted because they may
// contain private data.
const char* inputs[] = {
// file: scheme is bad.
"file://filename",
"FILE://filename",
// URLs with usernames, ports, queries or refs are bad.
"http://username:password@hostname/",
"http://www.example.com:1000",
"http://foo:1000",
"http://hostname/?query=q",
"http://hostname/path#ref",
// A https URL with path is bad.
"https://hostname/path",
};
for (size_t i = 0; i < arraysize(inputs); ++i) {
RunQueryAndVerify(inputs[i], kOneResult, NULL, 0);
}
}
IN_PROC_BROWSER_TEST_F(WebstoreProviderTest, NoSearchForShortQueries) {
RunQueryAndVerify("a", kOneResult, NULL, 0);
RunQueryAndVerify("ab", kOneResult, NULL, 0);
RunQueryAndVerify("abc", kOneResult, kParsedOneResult, 1);
}
// Flaky on CrOS and Windows: http://crbug.com/246136.
#if defined(OS_WIN) || defined(OS_CHROMEOS)
#define MAYBE_SearchCache DISABLED_SearchCache
#else
#define MAYBE_SearchCache SearchCache
#endif
IN_PROC_BROWSER_TEST_F(WebstoreProviderTest, MAYBE_SearchCache) {
RunQueryAndVerify("foo", kOneResult, kParsedOneResult, 1);
// No result is provided but the provider gets the result from the cache.
RunQueryAndVerify("foo", "", kParsedOneResult, 1);
}
} // namespace test
} // namespace app_list