1 // Copyright (c) 2012 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 #include <vector> 7 8 #include "base/bind.h" 9 #include "base/json/json_reader.h" 10 #include "base/memory/scoped_ptr.h" 11 #include "base/prefs/pref_service.h" 12 #include "base/strings/stringprintf.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "base/values.h" 15 #include "chrome/browser/spellchecker/spelling_service_client.h" 16 #include "chrome/common/pref_names.h" 17 #include "chrome/common/spellcheck_result.h" 18 #include "chrome/test/base/testing_profile.h" 19 #include "content/public/test/test_browser_thread_bundle.h" 20 #include "net/base/load_flags.h" 21 #include "net/url_request/test_url_fetcher_factory.h" 22 #include "testing/gtest/include/gtest/gtest.h" 23 24 namespace { 25 26 // A mock URL fetcher used in the TestingSpellingServiceClient class. This class 27 // verifies JSON-RPC requests when the SpellingServiceClient class sends them to 28 // the Spelling service. This class also verifies the SpellingServiceClient 29 // class does not either send cookies to the Spelling service or accept cookies 30 // from it. 31 class TestSpellingURLFetcher : public net::TestURLFetcher { 32 public: 33 TestSpellingURLFetcher(int id, 34 const GURL& url, 35 net::URLFetcherDelegate* d, 36 int version, 37 const std::string& text, 38 const std::string& language, 39 int status, 40 const std::string& response) 41 : net::TestURLFetcher(0, url, d), 42 version_(base::StringPrintf("v%d", version)), 43 language_(language.empty() ? std::string("en") : language), 44 text_(text) { 45 set_response_code(status); 46 SetResponseString(response); 47 } 48 virtual ~TestSpellingURLFetcher() { 49 } 50 51 virtual void SetUploadData(const std::string& upload_content_type, 52 const std::string& upload_content) OVERRIDE { 53 // Verify the given content type is JSON. (The Spelling service returns an 54 // internal server error when this content type is not JSON.) 55 EXPECT_EQ("application/json", upload_content_type); 56 57 // Parse the JSON to be sent to the service, and verify its parameters. 58 scoped_ptr<base::DictionaryValue> value(static_cast<base::DictionaryValue*>( 59 base::JSONReader::Read(upload_content, 60 base::JSON_ALLOW_TRAILING_COMMAS))); 61 ASSERT_TRUE(!!value.get()); 62 std::string method; 63 EXPECT_TRUE(value->GetString("method", &method)); 64 EXPECT_EQ("spelling.check", method); 65 std::string version; 66 EXPECT_TRUE(value->GetString("apiVersion", &version)); 67 EXPECT_EQ(version_, version); 68 std::string text; 69 EXPECT_TRUE(value->GetString("params.text", &text)); 70 EXPECT_EQ(text_, text); 71 std::string language; 72 EXPECT_TRUE(value->GetString("params.language", &language)); 73 EXPECT_EQ(language_, language); 74 ASSERT_TRUE(GetExpectedCountry(language, &country_)); 75 std::string country; 76 EXPECT_TRUE(value->GetString("params.originCountry", &country)); 77 EXPECT_EQ(country_, country); 78 79 net::TestURLFetcher::SetUploadData(upload_content_type, upload_content); 80 } 81 82 virtual void Start() OVERRIDE { 83 // Verify that this client does not either send cookies to the Spelling 84 // service or accept cookies from it. 85 EXPECT_EQ(net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES, 86 GetLoadFlags()); 87 } 88 89 private: 90 bool GetExpectedCountry(const std::string& language, std::string* country) { 91 static const struct { 92 const char* language; 93 const char* country; 94 } kCountries[] = { 95 {"af", "ZAF"}, 96 {"en", "USA"}, 97 }; 98 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kCountries); ++i) { 99 if (!language.compare(kCountries[i].language)) { 100 country->assign(kCountries[i].country); 101 return true; 102 } 103 } 104 return false; 105 } 106 107 std::string version_; 108 std::string language_; 109 std::string country_; 110 std::string text_; 111 }; 112 113 // A class derived from the SpellingServiceClient class used by the 114 // SpellingServiceClientTest class. This class overrides CreateURLFetcher so 115 // this test can use TestSpellingURLFetcher. This class also lets tests access 116 // the ParseResponse method. 117 class TestingSpellingServiceClient : public SpellingServiceClient { 118 public: 119 TestingSpellingServiceClient() 120 : request_type_(0), 121 response_status_(0), 122 success_(false), 123 fetcher_(NULL) { 124 } 125 virtual ~TestingSpellingServiceClient() { 126 } 127 128 void SetHTTPRequest(int type, 129 const std::string& text, 130 const std::string& language) { 131 request_type_ = type; 132 request_text_ = text; 133 request_language_ = language; 134 } 135 136 void SetHTTPResponse(int status, const char* data) { 137 response_status_ = status; 138 response_data_.assign(data); 139 } 140 141 void SetExpectedTextCheckResult(bool success, const char* text) { 142 success_ = success; 143 corrected_text_.assign(base::UTF8ToUTF16(text)); 144 } 145 146 void CallOnURLFetchComplete() { 147 ASSERT_TRUE(!!fetcher_); 148 fetcher_->delegate()->OnURLFetchComplete(fetcher_); 149 fetcher_ = NULL; 150 } 151 152 void VerifyResponse(bool success, 153 const base::string16& request_text, 154 const std::vector<SpellCheckResult>& results) { 155 EXPECT_EQ(success_, success); 156 base::string16 text(base::UTF8ToUTF16(request_text_)); 157 EXPECT_EQ(text, request_text); 158 for (std::vector<SpellCheckResult>::const_iterator it = results.begin(); 159 it != results.end(); ++it) { 160 text.replace(it->location, it->length, it->replacement); 161 } 162 EXPECT_EQ(corrected_text_, text); 163 } 164 165 bool ParseResponseSuccess(const std::string& data) { 166 std::vector<SpellCheckResult> results; 167 return ParseResponse(data, &results); 168 } 169 170 private: 171 virtual net::URLFetcher* CreateURLFetcher(const GURL& url) OVERRIDE { 172 EXPECT_EQ("https://www.googleapis.com/rpc", url.spec()); 173 fetcher_ = new TestSpellingURLFetcher(0, url, this, 174 request_type_, request_text_, 175 request_language_, 176 response_status_, response_data_); 177 return fetcher_; 178 } 179 180 int request_type_; 181 std::string request_text_; 182 std::string request_language_; 183 int response_status_; 184 std::string response_data_; 185 bool success_; 186 base::string16 corrected_text_; 187 TestSpellingURLFetcher* fetcher_; // weak 188 }; 189 190 // A test class used for testing the SpellingServiceClient class. This class 191 // implements a callback function used by the SpellingServiceClient class to 192 // monitor the class calls the callback with expected results. 193 class SpellingServiceClientTest : public testing::Test { 194 public: 195 void OnTextCheckComplete(int tag, 196 bool success, 197 const base::string16& text, 198 const std::vector<SpellCheckResult>& results) { 199 client_.VerifyResponse(success, text, results); 200 } 201 202 protected: 203 content::TestBrowserThreadBundle thread_bundle_; 204 TestingSpellingServiceClient client_; 205 TestingProfile profile_; 206 }; 207 208 } // namespace 209 210 // Verifies that SpellingServiceClient::RequestTextCheck() creates a JSON 211 // request sent to the Spelling service as we expect. This test also verifies 212 // that it parses a JSON response from the service and calls the callback 213 // function. To avoid sending JSON-RPC requests to the service, this test uses a 214 // custom TestURLFecher class (TestSpellingURLFetcher) which calls 215 // SpellingServiceClient::OnURLFetchComplete() with the parameters set by this 216 // test. This test also uses a custom callback function that replaces all 217 // misspelled words with ones suggested by the service so this test can compare 218 // the corrected text with the expected results. (If there are not any 219 // misspelled words, |corrected_text| should be equal to |request_text|.) 220 TEST_F(SpellingServiceClientTest, RequestTextCheck) { 221 static const struct { 222 const char* request_text; 223 SpellingServiceClient::ServiceType request_type; 224 int response_status; 225 const char* response_data; 226 bool success; 227 const char* corrected_text; 228 const char* language; 229 } kTests[] = { 230 { 231 "", 232 SpellingServiceClient::SUGGEST, 233 500, 234 "", 235 false, 236 "", 237 "af", 238 }, { 239 "chromebook", 240 SpellingServiceClient::SUGGEST, 241 200, 242 "{}", 243 true, 244 "chromebook", 245 "af", 246 }, { 247 "chrombook", 248 SpellingServiceClient::SUGGEST, 249 200, 250 "{\n" 251 " \"result\": {\n" 252 " \"spellingCheckResponse\": {\n" 253 " \"misspellings\": [{\n" 254 " \"charStart\": 0,\n" 255 " \"charLength\": 9,\n" 256 " \"suggestions\": [{ \"suggestion\": \"chromebook\" }],\n" 257 " \"canAutoCorrect\": false\n" 258 " }]\n" 259 " }\n" 260 " }\n" 261 "}", 262 true, 263 "chromebook", 264 "af", 265 }, { 266 "", 267 SpellingServiceClient::SPELLCHECK, 268 500, 269 "", 270 false, 271 "", 272 "en", 273 }, { 274 "I have been to USA.", 275 SpellingServiceClient::SPELLCHECK, 276 200, 277 "{}", 278 true, 279 "I have been to USA.", 280 "en", 281 }, { 282 "I have bean to USA.", 283 SpellingServiceClient::SPELLCHECK, 284 200, 285 "{\n" 286 " \"result\": {\n" 287 " \"spellingCheckResponse\": {\n" 288 " \"misspellings\": [{\n" 289 " \"charStart\": 7,\n" 290 " \"charLength\": 4,\n" 291 " \"suggestions\": [{ \"suggestion\": \"been\" }],\n" 292 " \"canAutoCorrect\": false\n" 293 " }]\n" 294 " }\n" 295 " }\n" 296 "}", 297 true, 298 "I have been to USA.", 299 "en", 300 }, 301 }; 302 303 PrefService* pref = profile_.GetPrefs(); 304 pref->SetBoolean(prefs::kEnableContinuousSpellcheck, true); 305 pref->SetBoolean(prefs::kSpellCheckUseSpellingService, true); 306 307 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) { 308 client_.SetHTTPRequest(kTests[i].request_type, kTests[i].request_text, 309 kTests[i].language); 310 client_.SetHTTPResponse(kTests[i].response_status, kTests[i].response_data); 311 client_.SetExpectedTextCheckResult(kTests[i].success, 312 kTests[i].corrected_text); 313 pref->SetString(prefs::kSpellCheckDictionary, kTests[i].language); 314 client_.RequestTextCheck( 315 &profile_, 316 kTests[i].request_type, 317 base::ASCIIToUTF16(kTests[i].request_text), 318 base::Bind(&SpellingServiceClientTest::OnTextCheckComplete, 319 base::Unretained(this), 0)); 320 client_.CallOnURLFetchComplete(); 321 } 322 } 323 324 // Verify that SpellingServiceClient::IsAvailable() returns true only when it 325 // can send suggest requests or spellcheck requests. 326 TEST_F(SpellingServiceClientTest, AvailableServices) { 327 const SpellingServiceClient::ServiceType kSuggest = 328 SpellingServiceClient::SUGGEST; 329 const SpellingServiceClient::ServiceType kSpellcheck = 330 SpellingServiceClient::SPELLCHECK; 331 332 // When a user disables spellchecking or prevent using the Spelling service, 333 // this function should return false both for suggestions and for spellcheck. 334 PrefService* pref = profile_.GetPrefs(); 335 pref->SetBoolean(prefs::kEnableContinuousSpellcheck, false); 336 pref->SetBoolean(prefs::kSpellCheckUseSpellingService, false); 337 EXPECT_FALSE(client_.IsAvailable(&profile_, kSuggest)); 338 EXPECT_FALSE(client_.IsAvailable(&profile_, kSpellcheck)); 339 340 pref->SetBoolean(prefs::kEnableContinuousSpellcheck, true); 341 pref->SetBoolean(prefs::kSpellCheckUseSpellingService, true); 342 343 // For locales supported by the SpellCheck service, this function returns 344 // false for suggestions and true for spellcheck. (The comment in 345 // SpellingServiceClient::IsAvailable() describes why this function returns 346 // false for suggestions.) If there is no language set, then we 347 // do not allow any remote. 348 pref->SetString(prefs::kSpellCheckDictionary, std::string()); 349 EXPECT_FALSE(client_.IsAvailable(&profile_, kSuggest)); 350 EXPECT_FALSE(client_.IsAvailable(&profile_, kSpellcheck)); 351 352 static const char* kSupported[] = { 353 #if !defined(OS_MACOSX) 354 "en-AU", "en-CA", "en-GB", "en-US", 355 #endif 356 }; 357 // If spellcheck is allowed, then suggest is not since spellcheck is a 358 // superset of suggest. 359 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSupported); ++i) { 360 pref->SetString(prefs::kSpellCheckDictionary, kSupported[i]); 361 EXPECT_FALSE(client_.IsAvailable(&profile_, kSuggest)); 362 EXPECT_TRUE(client_.IsAvailable(&profile_, kSpellcheck)); 363 } 364 365 // This function returns true for suggestions for all and false for 366 // spellcheck for unsupported locales. 367 static const char* kUnsupported[] = { 368 #if !defined(OS_MACOSX) 369 "af-ZA", "bg-BG", "ca-ES", "cs-CZ", "da-DK", "de-DE", "el-GR", "es-ES", 370 "et-EE", "fo-FO", "fr-FR", "he-IL", "hi-IN", "hr-HR", "hu-HU", "id-ID", 371 "it-IT", "lt-LT", "lv-LV", "nb-NO", "nl-NL", "pl-PL", "pt-BR", "pt-PT", 372 "ro-RO", "ru-RU", "sk-SK", "sl-SI", "sh", "sr", "sv-SE", "tr-TR", 373 "uk-UA", "vi-VN", 374 #endif 375 }; 376 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kUnsupported); ++i) { 377 pref->SetString(prefs::kSpellCheckDictionary, kUnsupported[i]); 378 EXPECT_TRUE(client_.IsAvailable(&profile_, kSuggest)); 379 EXPECT_FALSE(client_.IsAvailable(&profile_, kSpellcheck)); 380 } 381 } 382 383 // Verify that an error in JSON response from spelling service will result in 384 // ParseResponse returning false. 385 TEST_F(SpellingServiceClientTest, ResponseErrorTest) { 386 EXPECT_TRUE(client_.ParseResponseSuccess("{\"result\": {}}")); 387 EXPECT_FALSE(client_.ParseResponseSuccess("{\"error\": {}}")); 388 } 389