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:
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)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 }
~TestSpellingURLFetcher()48 virtual ~TestSpellingURLFetcher() {
49 }
50
SetUploadData(const std::string & upload_content_type,const std::string & upload_content)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
Start()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:
GetExpectedCountry(const std::string & language,std::string * country)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:
TestingSpellingServiceClient()119 TestingSpellingServiceClient()
120 : request_type_(0),
121 response_status_(0),
122 success_(false),
123 fetcher_(NULL) {
124 }
~TestingSpellingServiceClient()125 virtual ~TestingSpellingServiceClient() {
126 }
127
SetHTTPRequest(int type,const std::string & text,const std::string & language)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
SetHTTPResponse(int status,const char * data)136 void SetHTTPResponse(int status, const char* data) {
137 response_status_ = status;
138 response_data_.assign(data);
139 }
140
SetExpectedTextCheckResult(bool success,const char * text)141 void SetExpectedTextCheckResult(bool success, const char* text) {
142 success_ = success;
143 corrected_text_.assign(base::UTF8ToUTF16(text));
144 }
145
CallOnURLFetchComplete()146 void CallOnURLFetchComplete() {
147 ASSERT_TRUE(!!fetcher_);
148 fetcher_->delegate()->OnURLFetchComplete(fetcher_);
149 fetcher_ = NULL;
150 }
151
VerifyResponse(bool success,const base::string16 & request_text,const std::vector<SpellCheckResult> & results)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
ParseResponseSuccess(const std::string & data)165 bool ParseResponseSuccess(const std::string& data) {
166 std::vector<SpellCheckResult> results;
167 return ParseResponse(data, &results);
168 }
169
170 private:
CreateURLFetcher(const GURL & url)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:
OnTextCheckComplete(int tag,bool success,const base::string16 & text,const std::vector<SpellCheckResult> & results)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|.)
TEST_F(SpellingServiceClientTest,RequestTextCheck)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.
TEST_F(SpellingServiceClientTest,AvailableServices)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.
TEST_F(SpellingServiceClientTest,ResponseErrorTest)385 TEST_F(SpellingServiceClientTest, ResponseErrorTest) {
386 EXPECT_TRUE(client_.ParseResponseSuccess("{\"result\": {}}"));
387 EXPECT_FALSE(client_.ParseResponseSuccess("{\"error\": {}}"));
388 }
389