// Copyright (c) 2012 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 #include #include "base/bind.h" #include "base/json/json_reader.h" #include "base/memory/scoped_ptr.h" #include "base/prefs/pref_service.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/spellchecker/spelling_service_client.h" #include "chrome/common/pref_names.h" #include "chrome/common/spellcheck_result.h" #include "chrome/test/base/testing_profile.h" #include "content/public/test/test_browser_thread_bundle.h" #include "net/base/load_flags.h" #include "net/url_request/test_url_fetcher_factory.h" #include "testing/gtest/include/gtest/gtest.h" namespace { // A mock URL fetcher used in the TestingSpellingServiceClient class. This class // verifies JSON-RPC requests when the SpellingServiceClient class sends them to // the Spelling service. This class also verifies the SpellingServiceClient // class does not either send cookies to the Spelling service or accept cookies // from it. class TestSpellingURLFetcher : public net::TestURLFetcher { public: TestSpellingURLFetcher(int id, const GURL& url, net::URLFetcherDelegate* d, int version, const std::string& text, const std::string& language, int status, const std::string& response) : net::TestURLFetcher(0, url, d), version_(base::StringPrintf("v%d", version)), language_(language.empty() ? std::string("en") : language), text_(text) { set_response_code(status); SetResponseString(response); } virtual ~TestSpellingURLFetcher() { } virtual void SetUploadData(const std::string& upload_content_type, const std::string& upload_content) OVERRIDE { // Verify the given content type is JSON. (The Spelling service returns an // internal server error when this content type is not JSON.) EXPECT_EQ("application/json", upload_content_type); // Parse the JSON to be sent to the service, and verify its parameters. scoped_ptr value(static_cast( base::JSONReader::Read(upload_content, base::JSON_ALLOW_TRAILING_COMMAS))); ASSERT_TRUE(!!value.get()); std::string method; EXPECT_TRUE(value->GetString("method", &method)); EXPECT_EQ("spelling.check", method); std::string version; EXPECT_TRUE(value->GetString("apiVersion", &version)); EXPECT_EQ(version_, version); std::string text; EXPECT_TRUE(value->GetString("params.text", &text)); EXPECT_EQ(text_, text); std::string language; EXPECT_TRUE(value->GetString("params.language", &language)); EXPECT_EQ(language_, language); ASSERT_TRUE(GetExpectedCountry(language, &country_)); std::string country; EXPECT_TRUE(value->GetString("params.originCountry", &country)); EXPECT_EQ(country_, country); net::TestURLFetcher::SetUploadData(upload_content_type, upload_content); } virtual void Start() OVERRIDE { // Verify that this client does not either send cookies to the Spelling // service or accept cookies from it. EXPECT_EQ(net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES, GetLoadFlags()); } private: bool GetExpectedCountry(const std::string& language, std::string* country) { static const struct { const char* language; const char* country; } kCountries[] = { {"af", "ZAF"}, {"en", "USA"}, }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kCountries); ++i) { if (!language.compare(kCountries[i].language)) { country->assign(kCountries[i].country); return true; } } return false; } std::string version_; std::string language_; std::string country_; std::string text_; }; // A class derived from the SpellingServiceClient class used by the // SpellingServiceClientTest class. This class overrides CreateURLFetcher so // this test can use TestSpellingURLFetcher. This class also lets tests access // the ParseResponse method. class TestingSpellingServiceClient : public SpellingServiceClient { public: TestingSpellingServiceClient() : request_type_(0), response_status_(0), success_(false), fetcher_(NULL) { } virtual ~TestingSpellingServiceClient() { } void SetHTTPRequest(int type, const std::string& text, const std::string& language) { request_type_ = type; request_text_ = text; request_language_ = language; } void SetHTTPResponse(int status, const char* data) { response_status_ = status; response_data_.assign(data); } void SetExpectedTextCheckResult(bool success, const char* text) { success_ = success; corrected_text_.assign(base::UTF8ToUTF16(text)); } void CallOnURLFetchComplete() { ASSERT_TRUE(!!fetcher_); fetcher_->delegate()->OnURLFetchComplete(fetcher_); fetcher_ = NULL; } void VerifyResponse(bool success, const base::string16& request_text, const std::vector& results) { EXPECT_EQ(success_, success); base::string16 text(base::UTF8ToUTF16(request_text_)); EXPECT_EQ(text, request_text); for (std::vector::const_iterator it = results.begin(); it != results.end(); ++it) { text.replace(it->location, it->length, it->replacement); } EXPECT_EQ(corrected_text_, text); } bool ParseResponseSuccess(const std::string& data) { std::vector results; return ParseResponse(data, &results); } private: virtual net::URLFetcher* CreateURLFetcher(const GURL& url) OVERRIDE { EXPECT_EQ("https://www.googleapis.com/rpc", url.spec()); fetcher_ = new TestSpellingURLFetcher(0, url, this, request_type_, request_text_, request_language_, response_status_, response_data_); return fetcher_; } int request_type_; std::string request_text_; std::string request_language_; int response_status_; std::string response_data_; bool success_; base::string16 corrected_text_; TestSpellingURLFetcher* fetcher_; // weak }; // A test class used for testing the SpellingServiceClient class. This class // implements a callback function used by the SpellingServiceClient class to // monitor the class calls the callback with expected results. class SpellingServiceClientTest : public testing::Test { public: void OnTextCheckComplete(int tag, bool success, const base::string16& text, const std::vector& results) { client_.VerifyResponse(success, text, results); } protected: content::TestBrowserThreadBundle thread_bundle_; TestingSpellingServiceClient client_; TestingProfile profile_; }; } // namespace // Verifies that SpellingServiceClient::RequestTextCheck() creates a JSON // request sent to the Spelling service as we expect. This test also verifies // that it parses a JSON response from the service and calls the callback // function. To avoid sending JSON-RPC requests to the service, this test uses a // custom TestURLFecher class (TestSpellingURLFetcher) which calls // SpellingServiceClient::OnURLFetchComplete() with the parameters set by this // test. This test also uses a custom callback function that replaces all // misspelled words with ones suggested by the service so this test can compare // the corrected text with the expected results. (If there are not any // misspelled words, |corrected_text| should be equal to |request_text|.) TEST_F(SpellingServiceClientTest, RequestTextCheck) { static const struct { const char* request_text; SpellingServiceClient::ServiceType request_type; int response_status; const char* response_data; bool success; const char* corrected_text; const char* language; } kTests[] = { { "", SpellingServiceClient::SUGGEST, 500, "", false, "", "af", }, { "chromebook", SpellingServiceClient::SUGGEST, 200, "{}", true, "chromebook", "af", }, { "chrombook", SpellingServiceClient::SUGGEST, 200, "{\n" " \"result\": {\n" " \"spellingCheckResponse\": {\n" " \"misspellings\": [{\n" " \"charStart\": 0,\n" " \"charLength\": 9,\n" " \"suggestions\": [{ \"suggestion\": \"chromebook\" }],\n" " \"canAutoCorrect\": false\n" " }]\n" " }\n" " }\n" "}", true, "chromebook", "af", }, { "", SpellingServiceClient::SPELLCHECK, 500, "", false, "", "en", }, { "I have been to USA.", SpellingServiceClient::SPELLCHECK, 200, "{}", true, "I have been to USA.", "en", }, { "I have bean to USA.", SpellingServiceClient::SPELLCHECK, 200, "{\n" " \"result\": {\n" " \"spellingCheckResponse\": {\n" " \"misspellings\": [{\n" " \"charStart\": 7,\n" " \"charLength\": 4,\n" " \"suggestions\": [{ \"suggestion\": \"been\" }],\n" " \"canAutoCorrect\": false\n" " }]\n" " }\n" " }\n" "}", true, "I have been to USA.", "en", }, }; PrefService* pref = profile_.GetPrefs(); pref->SetBoolean(prefs::kEnableContinuousSpellcheck, true); pref->SetBoolean(prefs::kSpellCheckUseSpellingService, true); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) { client_.SetHTTPRequest(kTests[i].request_type, kTests[i].request_text, kTests[i].language); client_.SetHTTPResponse(kTests[i].response_status, kTests[i].response_data); client_.SetExpectedTextCheckResult(kTests[i].success, kTests[i].corrected_text); pref->SetString(prefs::kSpellCheckDictionary, kTests[i].language); client_.RequestTextCheck( &profile_, kTests[i].request_type, base::ASCIIToUTF16(kTests[i].request_text), base::Bind(&SpellingServiceClientTest::OnTextCheckComplete, base::Unretained(this), 0)); client_.CallOnURLFetchComplete(); } } // Verify that SpellingServiceClient::IsAvailable() returns true only when it // can send suggest requests or spellcheck requests. TEST_F(SpellingServiceClientTest, AvailableServices) { const SpellingServiceClient::ServiceType kSuggest = SpellingServiceClient::SUGGEST; const SpellingServiceClient::ServiceType kSpellcheck = SpellingServiceClient::SPELLCHECK; // When a user disables spellchecking or prevent using the Spelling service, // this function should return false both for suggestions and for spellcheck. PrefService* pref = profile_.GetPrefs(); pref->SetBoolean(prefs::kEnableContinuousSpellcheck, false); pref->SetBoolean(prefs::kSpellCheckUseSpellingService, false); EXPECT_FALSE(client_.IsAvailable(&profile_, kSuggest)); EXPECT_FALSE(client_.IsAvailable(&profile_, kSpellcheck)); pref->SetBoolean(prefs::kEnableContinuousSpellcheck, true); pref->SetBoolean(prefs::kSpellCheckUseSpellingService, true); // For locales supported by the SpellCheck service, this function returns // false for suggestions and true for spellcheck. (The comment in // SpellingServiceClient::IsAvailable() describes why this function returns // false for suggestions.) If there is no language set, then we // do not allow any remote. pref->SetString(prefs::kSpellCheckDictionary, std::string()); EXPECT_FALSE(client_.IsAvailable(&profile_, kSuggest)); EXPECT_FALSE(client_.IsAvailable(&profile_, kSpellcheck)); static const char* kSupported[] = { #if !defined(OS_MACOSX) "en-AU", "en-CA", "en-GB", "en-US", #endif }; // If spellcheck is allowed, then suggest is not since spellcheck is a // superset of suggest. for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSupported); ++i) { pref->SetString(prefs::kSpellCheckDictionary, kSupported[i]); EXPECT_FALSE(client_.IsAvailable(&profile_, kSuggest)); EXPECT_TRUE(client_.IsAvailable(&profile_, kSpellcheck)); } // This function returns true for suggestions for all and false for // spellcheck for unsupported locales. static const char* kUnsupported[] = { #if !defined(OS_MACOSX) "af-ZA", "bg-BG", "ca-ES", "cs-CZ", "da-DK", "de-DE", "el-GR", "es-ES", "et-EE", "fo-FO", "fr-FR", "he-IL", "hi-IN", "hr-HR", "hu-HU", "id-ID", "it-IT", "lt-LT", "lv-LV", "nb-NO", "nl-NL", "pl-PL", "pt-BR", "pt-PT", "ro-RO", "ru-RU", "sk-SK", "sl-SI", "sh", "sr", "sv-SE", "tr-TR", "uk-UA", "vi-VN", #endif }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kUnsupported); ++i) { pref->SetString(prefs::kSpellCheckDictionary, kUnsupported[i]); EXPECT_TRUE(client_.IsAvailable(&profile_, kSuggest)); EXPECT_FALSE(client_.IsAvailable(&profile_, kSpellcheck)); } } // Verify that an error in JSON response from spelling service will result in // ParseResponse returning false. TEST_F(SpellingServiceClientTest, ResponseErrorTest) { EXPECT_TRUE(client_.ParseResponseSuccess("{\"result\": {}}")); EXPECT_FALSE(client_.ParseResponseSuccess("{\"error\": {}}")); }