Home | History | Annotate | Download | only in spellchecker
      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