• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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