OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013 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 // Unit tests for |FeedbackSender| object. | |
6 | |
7 #include "chrome/browser/spellchecker/feedback_sender.h" | |
8 | |
9 #include <stddef.h> | |
10 #include <stdint.h> | |
11 | |
12 #include "base/bind.h" | |
13 #include "base/command_line.h" | |
14 #include "base/json/json_reader.h" | |
15 #include "base/message_loop/message_loop.h" | |
16 #include "base/metrics/field_trial.h" | |
17 #include "base/strings/stringprintf.h" | |
18 #include "base/strings/utf_string_conversions.h" | |
19 #include "base/values.h" | |
20 #include "chrome/common/chrome_switches.h" | |
21 #include "chrome/common/spellcheck_common.h" | |
22 #include "chrome/common/spellcheck_marker.h" | |
23 #include "chrome/common/spellcheck_result.h" | |
24 #include "chrome/test/base/testing_profile.h" | |
25 #include "components/variations/entropy_provider.h" | |
26 #include "content/public/test/test_browser_thread.h" | |
27 #include "net/url_request/test_url_fetcher_factory.h" | |
28 #include "testing/gtest/include/gtest/gtest.h" | |
29 | |
30 namespace spellcheck { | |
31 | |
32 namespace { | |
33 | |
34 const char kCountry[] = "USA"; | |
35 const char kLanguage[] = "en"; | |
36 const char kText[] = "Helllo world."; | |
37 const int kMisspellingLength = 6; | |
38 const int kMisspellingStart = 0; | |
39 const int kRendererProcessId = 0; | |
40 const int kUrlFetcherId = 0; | |
41 | |
42 // Builds a simple spellcheck result. | |
43 SpellCheckResult BuildSpellCheckResult() { | |
44 return SpellCheckResult(SpellCheckResult::SPELLING, | |
45 kMisspellingStart, | |
46 kMisspellingLength, | |
47 base::UTF8ToUTF16("Hello")); | |
48 } | |
49 | |
50 // Returns the number of times that |needle| appears in |haystack| without | |
51 // overlaps. For example, CountOccurences("bananana", "nana") returns 1. | |
52 int CountOccurences(const std::string& haystack, const std::string& needle) { | |
53 int number_of_occurrences = 0; | |
54 for (size_t pos = haystack.find(needle); | |
55 pos != std::string::npos; | |
56 pos = haystack.find(needle, pos + needle.length())) { | |
57 ++number_of_occurrences; | |
58 } | |
59 return number_of_occurrences; | |
60 } | |
61 | |
62 class MockFeedbackSender : public spellcheck::FeedbackSender { | |
63 public: | |
64 MockFeedbackSender(net::URLRequestContextGetter* request_context, | |
65 const std::string& language, | |
66 const std::string& country) | |
67 : FeedbackSender(request_context, language, country), random_(0) {} | |
68 | |
69 void RandBytes(void* p, size_t len) override { | |
70 memset(p, 0, len); | |
71 if (len >= sizeof(random_)) | |
72 *(unsigned*)p = ++random_; | |
73 } | |
74 | |
75 private: | |
76 // For returning a different value from each call to RandUint64(). | |
77 unsigned random_; | |
78 }; | |
79 | |
80 std::string GetMisspellingId(const std::string& raw_data) { | |
81 std::unique_ptr<base::Value> parsed_data( | |
82 base::JSONReader::Read(raw_data).release()); | |
83 EXPECT_TRUE(parsed_data.get()); | |
84 base::DictionaryValue* actual_data; | |
85 EXPECT_TRUE(parsed_data->GetAsDictionary(&actual_data)); | |
86 base::ListValue* suggestions = NULL; | |
87 EXPECT_TRUE(actual_data->GetList("params.suggestionInfo", &suggestions)); | |
88 base::DictionaryValue* suggestion = NULL; | |
89 EXPECT_TRUE(suggestions->GetDictionary(0, &suggestion)); | |
90 std::string value; | |
91 EXPECT_TRUE(suggestion->GetString("userMisspellingId", &value)); | |
92 return value; | |
93 } | |
94 | |
95 } // namespace | |
96 | |
97 // A test fixture to help keep tests simple. | |
98 class FeedbackSenderTest : public testing::Test { | |
99 public: | |
100 FeedbackSenderTest() : ui_thread_(content::BrowserThread::UI, &loop_) { | |
101 feedback_.reset(new MockFeedbackSender(NULL, kLanguage, kCountry)); | |
102 feedback_->StartFeedbackCollection(); | |
103 } | |
104 | |
105 ~FeedbackSenderTest() override {} | |
106 | |
107 protected: | |
108 // Appends the "--enable-spelling-service-feedback" switch to the | |
109 // command-line. | |
110 void AppendCommandLineSwitch() { | |
111 // The command-line switch is temporary. | |
112 // TODO(rouslan): Remove the command-line switch. http://crbug.com/247726 | |
113 base::CommandLine::ForCurrentProcess()->AppendSwitch( | |
114 switches::kEnableSpellingFeedbackFieldTrial); | |
115 feedback_.reset(new MockFeedbackSender(NULL, kLanguage, kCountry)); | |
116 feedback_->StartFeedbackCollection(); | |
117 } | |
118 | |
119 // Enables the "SpellingServiceFeedback.Enabled" field trial. | |
120 void EnableFieldTrial() { | |
121 // The field trial is temporary. | |
122 // TODO(rouslan): Remove the field trial. http://crbug.com/247726 | |
123 field_trial_list_.reset( | |
124 new base::FieldTrialList(new metrics::SHA1EntropyProvider("foo"))); | |
125 field_trial_ = base::FieldTrialList::CreateFieldTrial( | |
126 kFeedbackFieldTrialName, kFeedbackFieldTrialEnabledGroupName); | |
127 field_trial_->group(); | |
128 feedback_.reset(new MockFeedbackSender(NULL, kLanguage, kCountry)); | |
129 feedback_->StartFeedbackCollection(); | |
130 } | |
131 | |
132 uint32_t AddPendingFeedback() { | |
133 std::vector<SpellCheckResult> results(1, BuildSpellCheckResult()); | |
134 feedback_->OnSpellcheckResults(kRendererProcessId, | |
135 base::UTF8ToUTF16(kText), | |
136 std::vector<SpellCheckMarker>(), | |
137 &results); | |
138 return results[0].hash; | |
139 } | |
140 | |
141 void ExpireSession() { | |
142 feedback_->session_start_ = | |
143 base::Time::Now() - | |
144 base::TimeDelta::FromHours(chrome::spellcheck_common::kSessionHours); | |
145 } | |
146 | |
147 bool UploadDataContains(const std::string& data) const { | |
148 const net::TestURLFetcher* fetcher = | |
149 fetchers_.GetFetcherByID(kUrlFetcherId); | |
150 return fetcher && CountOccurences(fetcher->upload_data(), data) > 0; | |
151 } | |
152 | |
153 bool UploadDataContains(const std::string& data, | |
154 int number_of_occurrences) const { | |
155 const net::TestURLFetcher* fetcher = | |
156 fetchers_.GetFetcherByID(kUrlFetcherId); | |
157 return fetcher && CountOccurences(fetcher->upload_data(), data) == | |
158 number_of_occurrences; | |
159 } | |
160 | |
161 // Returns true if the feedback sender would be uploading data now. The test | |
162 // does not open network connections. | |
163 bool IsUploadingData() const { | |
164 return !!fetchers_.GetFetcherByID(kUrlFetcherId); | |
165 } | |
166 | |
167 void ClearUploadData() { | |
168 fetchers_.RemoveFetcherFromMap(kUrlFetcherId); | |
169 } | |
170 | |
171 std::string GetUploadData() const { | |
172 const net::TestURLFetcher* fetcher = | |
173 fetchers_.GetFetcherByID(kUrlFetcherId); | |
174 return fetcher ? fetcher->upload_data() : std::string(); | |
175 } | |
176 | |
177 void AdjustUpdateTime(base::TimeDelta offset) { | |
178 feedback_->last_salt_update_ += offset; | |
179 } | |
180 | |
181 std::unique_ptr<MockFeedbackSender> feedback_; | |
182 | |
183 private: | |
184 base::MessageLoop loop_; | |
185 TestingProfile profile_; | |
186 content::TestBrowserThread ui_thread_; | |
187 std::unique_ptr<base::FieldTrialList> field_trial_list_; | |
188 scoped_refptr<base::FieldTrial> field_trial_; | |
189 net::TestURLFetcherFactory fetchers_; | |
190 }; | |
191 | |
192 // Do not send data if there's no feedback. | |
193 TEST_F(FeedbackSenderTest, NoFeedback) { | |
194 EXPECT_FALSE(IsUploadingData()); | |
195 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
196 std::vector<uint32_t>()); | |
197 EXPECT_FALSE(IsUploadingData()); | |
198 } | |
199 | |
200 // Do not send data if not aware of which markers are still in the document. | |
201 TEST_F(FeedbackSenderTest, NoDocumentMarkersReceived) { | |
202 EXPECT_FALSE(IsUploadingData()); | |
203 uint32_t hash = AddPendingFeedback(); | |
204 EXPECT_FALSE(IsUploadingData()); | |
205 static const int kSuggestionIndex = 1; | |
206 feedback_->SelectedSuggestion(hash, kSuggestionIndex); | |
207 EXPECT_FALSE(IsUploadingData()); | |
208 } | |
209 | |
210 // Send PENDING feedback message if the marker is still in the document, and the | |
211 // user has not performed any action on it. | |
212 TEST_F(FeedbackSenderTest, PendingFeedback) { | |
213 uint32_t hash = AddPendingFeedback(); | |
214 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
215 std::vector<uint32_t>(1, hash)); | |
216 EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\"")); | |
217 } | |
218 | |
219 TEST_F(FeedbackSenderTest, IdenticalFeedback) { | |
220 std::vector<uint32_t> hashes; | |
221 hashes.push_back(AddPendingFeedback()); | |
222 hashes.push_back(AddPendingFeedback()); | |
223 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, hashes); | |
224 std::string actual_data = GetUploadData(); | |
225 std::unique_ptr<base::DictionaryValue> actual( | |
226 static_cast<base::DictionaryValue*>( | |
227 base::JSONReader::Read(GetUploadData()).release())); | |
228 base::ListValue* suggestions = NULL; | |
229 ASSERT_TRUE(actual->GetList("params.suggestionInfo", &suggestions)); | |
230 base::DictionaryValue* suggestion0 = NULL; | |
231 ASSERT_TRUE(suggestions->GetDictionary(0, &suggestion0)); | |
232 base::DictionaryValue* suggestion1 = NULL; | |
233 ASSERT_TRUE(suggestions->GetDictionary(0, &suggestion1)); | |
234 std::string value0, value1; | |
235 ASSERT_TRUE(suggestion0->GetString("userMisspellingId", &value0)); | |
236 ASSERT_TRUE(suggestion1->GetString("userMisspellingId", &value1)); | |
237 EXPECT_EQ(value0, value1); | |
238 base::ListValue* suggestion_ids = NULL; | |
239 ASSERT_TRUE(suggestion0->GetList("userSuggestionId", &suggestion_ids)); | |
240 ASSERT_TRUE(suggestion_ids->GetString(0, &value0)); | |
241 ASSERT_TRUE(suggestion1->GetList("userSuggestionId", &suggestion_ids)); | |
242 ASSERT_TRUE(suggestion_ids->GetString(0, &value1)); | |
243 EXPECT_EQ(value0, value1); | |
244 } | |
245 | |
246 TEST_F(FeedbackSenderTest, NonidenticalFeedback) { | |
247 std::vector<uint32_t> hashes; | |
248 hashes.push_back(AddPendingFeedback()); | |
249 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, hashes); | |
250 std::string raw_data0 = GetUploadData(); | |
251 hashes.clear(); | |
252 hashes.push_back(AddPendingFeedback()); | |
253 AdjustUpdateTime(-base::TimeDelta::FromHours(25)); | |
254 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, hashes); | |
255 std::string raw_data1 = GetUploadData(); | |
256 | |
257 std::string value0(GetMisspellingId(raw_data0)); | |
258 std::string value1(GetMisspellingId(raw_data1)); | |
259 EXPECT_NE(value0, value1); | |
260 } | |
261 | |
262 // Send NO_ACTION feedback message if the marker has been removed from the | |
263 // document. | |
264 TEST_F(FeedbackSenderTest, NoActionFeedback) { | |
265 AddPendingFeedback(); | |
266 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
267 std::vector<uint32_t>()); | |
268 EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\"")); | |
269 } | |
270 | |
271 // Send SELECT feedback message if the user has selected a spelling suggestion. | |
272 TEST_F(FeedbackSenderTest, SelectFeedback) { | |
273 uint32_t hash = AddPendingFeedback(); | |
274 static const int kSuggestionIndex = 0; | |
275 feedback_->SelectedSuggestion(hash, kSuggestionIndex); | |
276 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
277 std::vector<uint32_t>()); | |
278 EXPECT_TRUE(UploadDataContains("\"actionType\":\"SELECT\"")); | |
279 EXPECT_TRUE(UploadDataContains("\"actionTargetIndex\":" + | |
280 base::StringPrintf("%d", kSuggestionIndex))); | |
281 } | |
282 | |
283 // Send ADD_TO_DICT feedback message if the user has added the misspelled word | |
284 // to the custom dictionary. | |
285 TEST_F(FeedbackSenderTest, AddToDictFeedback) { | |
286 uint32_t hash = AddPendingFeedback(); | |
287 feedback_->AddedToDictionary(hash); | |
288 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
289 std::vector<uint32_t>()); | |
290 EXPECT_TRUE(UploadDataContains("\"actionType\":\"ADD_TO_DICT\"")); | |
291 } | |
292 | |
293 // Send IN_DICTIONARY feedback message if the user has the misspelled word in | |
294 // the custom dictionary. | |
295 TEST_F(FeedbackSenderTest, InDictionaryFeedback) { | |
296 uint32_t hash = AddPendingFeedback(); | |
297 feedback_->RecordInDictionary(hash); | |
298 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
299 std::vector<uint32_t>()); | |
300 EXPECT_TRUE(UploadDataContains("\"actionType\":\"IN_DICTIONARY\"")); | |
301 } | |
302 | |
303 // Send PENDING feedback message if the user saw the spelling suggestion, but | |
304 // decided to not select it, and the marker is still in the document. | |
305 TEST_F(FeedbackSenderTest, IgnoreFeedbackMarkerInDocument) { | |
306 uint32_t hash = AddPendingFeedback(); | |
307 feedback_->IgnoredSuggestions(hash); | |
308 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
309 std::vector<uint32_t>(1, hash)); | |
310 EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\"")); | |
311 } | |
312 | |
313 // Send IGNORE feedback message if the user saw the spelling suggestion, but | |
314 // decided to not select it, and the marker is no longer in the document. | |
315 TEST_F(FeedbackSenderTest, IgnoreFeedbackMarkerNotInDocument) { | |
316 uint32_t hash = AddPendingFeedback(); | |
317 feedback_->IgnoredSuggestions(hash); | |
318 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
319 std::vector<uint32_t>()); | |
320 EXPECT_TRUE(UploadDataContains("\"actionType\":\"IGNORE\"")); | |
321 } | |
322 | |
323 // Send MANUALLY_CORRECTED feedback message if the user manually corrected the | |
324 // misspelled word. | |
325 TEST_F(FeedbackSenderTest, ManuallyCorrectedFeedback) { | |
326 uint32_t hash = AddPendingFeedback(); | |
327 static const std::string kManualCorrection = "Howdy"; | |
328 feedback_->ManuallyCorrected(hash, base::ASCIIToUTF16(kManualCorrection)); | |
329 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
330 std::vector<uint32_t>()); | |
331 EXPECT_TRUE(UploadDataContains("\"actionType\":\"MANUALLY_CORRECTED\"")); | |
332 EXPECT_TRUE(UploadDataContains("\"actionTargetValue\":\"" + | |
333 kManualCorrection + "\"")); | |
334 } | |
335 | |
336 // Send feedback messages in batch. | |
337 TEST_F(FeedbackSenderTest, BatchFeedback) { | |
338 std::vector<SpellCheckResult> results; | |
339 results.push_back(SpellCheckResult(SpellCheckResult::SPELLING, | |
340 kMisspellingStart, | |
341 kMisspellingLength, | |
342 base::ASCIIToUTF16("Hello"))); | |
343 static const int kSecondMisspellingStart = 7; | |
344 static const int kSecondMisspellingLength = 5; | |
345 results.push_back(SpellCheckResult(SpellCheckResult::SPELLING, | |
346 kSecondMisspellingStart, | |
347 kSecondMisspellingLength, | |
348 base::ASCIIToUTF16("world"))); | |
349 feedback_->OnSpellcheckResults(kRendererProcessId, | |
350 base::UTF8ToUTF16(kText), | |
351 std::vector<SpellCheckMarker>(), | |
352 &results); | |
353 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
354 std::vector<uint32_t>()); | |
355 EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\"", 2)); | |
356 } | |
357 | |
358 // Send a series of PENDING feedback messages and one final NO_ACTION feedback | |
359 // message with the same hash identifier for a single misspelling. | |
360 TEST_F(FeedbackSenderTest, SameHashFeedback) { | |
361 uint32_t hash = AddPendingFeedback(); | |
362 std::vector<uint32_t> remaining_markers(1, hash); | |
363 | |
364 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers); | |
365 EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\"")); | |
366 std::string hash_string = base::StringPrintf("\"suggestionId\":\"%u\"", hash); | |
367 EXPECT_TRUE(UploadDataContains(hash_string)); | |
368 ClearUploadData(); | |
369 | |
370 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers); | |
371 EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\"")); | |
372 EXPECT_TRUE(UploadDataContains(hash_string)); | |
373 ClearUploadData(); | |
374 | |
375 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
376 std::vector<uint32_t>()); | |
377 EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\"")); | |
378 EXPECT_TRUE(UploadDataContains(hash_string)); | |
379 ClearUploadData(); | |
380 | |
381 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
382 std::vector<uint32_t>()); | |
383 EXPECT_FALSE(IsUploadingData()); | |
384 } | |
385 | |
386 // When a session expires: | |
387 // 1) Pending feedback is finalized and sent to the server in the last message | |
388 // batch in the session. | |
389 // 2) No feedback is sent until a spellcheck request happens. | |
390 // 3) Existing markers get new hash identifiers. | |
391 TEST_F(FeedbackSenderTest, SessionExpirationFeedback) { | |
392 std::vector<SpellCheckResult> results( | |
393 1, | |
394 SpellCheckResult(SpellCheckResult::SPELLING, | |
395 kMisspellingStart, | |
396 kMisspellingLength, | |
397 base::ASCIIToUTF16("Hello"))); | |
398 feedback_->OnSpellcheckResults(kRendererProcessId, | |
399 base::UTF8ToUTF16(kText), | |
400 std::vector<SpellCheckMarker>(), | |
401 &results); | |
402 uint32_t original_hash = results[0].hash; | |
403 std::vector<uint32_t> remaining_markers(1, original_hash); | |
404 | |
405 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers); | |
406 EXPECT_FALSE(UploadDataContains("\"actionType\":\"NO_ACTION\"")); | |
407 EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\"")); | |
408 std::string original_hash_string = | |
409 base::StringPrintf("\"suggestionId\":\"%u\"", original_hash); | |
410 EXPECT_TRUE(UploadDataContains(original_hash_string)); | |
411 ClearUploadData(); | |
412 | |
413 ExpireSession(); | |
414 | |
415 // Last message batch in the current session has only finalized messages. | |
416 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers); | |
417 EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\"")); | |
418 EXPECT_FALSE(UploadDataContains("\"actionType\":\"PENDING\"")); | |
419 EXPECT_TRUE(UploadDataContains(original_hash_string)); | |
420 ClearUploadData(); | |
421 | |
422 // The next session starts on the next spellchecker request. Until then, | |
423 // there's no more feedback sent. | |
424 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers); | |
425 EXPECT_FALSE(IsUploadingData()); | |
426 | |
427 // The first spellcheck request after session expiration creates different | |
428 // document marker hash identifiers. | |
429 std::vector<SpellCheckMarker> original_markers( | |
430 1, SpellCheckMarker(results[0].hash, results[0].location)); | |
431 results[0] = SpellCheckResult(SpellCheckResult::SPELLING, | |
432 kMisspellingStart, | |
433 kMisspellingLength, | |
434 base::ASCIIToUTF16("Hello")); | |
435 feedback_->OnSpellcheckResults( | |
436 kRendererProcessId, base::UTF8ToUTF16(kText), original_markers, &results); | |
437 uint32_t updated_hash = results[0].hash; | |
438 EXPECT_NE(updated_hash, original_hash); | |
439 remaining_markers[0] = updated_hash; | |
440 | |
441 // The first feedback message batch in session |i + 1| has the new document | |
442 // marker hash identifiers. | |
443 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers); | |
444 EXPECT_FALSE(UploadDataContains("\"actionType\":\"NO_ACTION\"")); | |
445 EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\"")); | |
446 EXPECT_FALSE(UploadDataContains(original_hash_string)); | |
447 std::string updated_hash_string = | |
448 base::StringPrintf("\"suggestionId\":\"%u\"", updated_hash); | |
449 EXPECT_TRUE(UploadDataContains(updated_hash_string)); | |
450 } | |
451 | |
452 // First message in session has an indicator. | |
453 TEST_F(FeedbackSenderTest, FirstMessageInSessionIndicator) { | |
454 // Session 1, message 1 | |
455 AddPendingFeedback(); | |
456 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
457 std::vector<uint32_t>()); | |
458 EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":true")); | |
459 | |
460 // Session 1, message 2 | |
461 AddPendingFeedback(); | |
462 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
463 std::vector<uint32_t>()); | |
464 EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":false")); | |
465 | |
466 ExpireSession(); | |
467 | |
468 // Session 1, message 3 (last) | |
469 AddPendingFeedback(); | |
470 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
471 std::vector<uint32_t>()); | |
472 EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":false")); | |
473 | |
474 // Session 2, message 1 | |
475 AddPendingFeedback(); | |
476 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
477 std::vector<uint32_t>()); | |
478 EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":true")); | |
479 | |
480 // Session 2, message 2 | |
481 AddPendingFeedback(); | |
482 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
483 std::vector<uint32_t>()); | |
484 EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":false")); | |
485 } | |
486 | |
487 // Flush all feedback when the spellcheck language and country change. | |
488 TEST_F(FeedbackSenderTest, OnLanguageCountryChange) { | |
489 AddPendingFeedback(); | |
490 feedback_->OnLanguageCountryChange("pt", "BR"); | |
491 EXPECT_TRUE(UploadDataContains("\"language\":\"en\"")); | |
492 AddPendingFeedback(); | |
493 feedback_->OnLanguageCountryChange("en", "US"); | |
494 EXPECT_TRUE(UploadDataContains("\"language\":\"pt\"")); | |
495 } | |
496 | |
497 // The field names and types should correspond to the API. | |
498 TEST_F(FeedbackSenderTest, FeedbackAPI) { | |
499 AddPendingFeedback(); | |
500 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
501 std::vector<uint32_t>()); | |
502 std::string actual_data = GetUploadData(); | |
503 std::unique_ptr<base::DictionaryValue> actual( | |
504 static_cast<base::DictionaryValue*>( | |
505 base::JSONReader::Read(actual_data).release())); | |
506 actual->SetString("params.key", "TestDummyKey"); | |
507 base::ListValue* suggestions = nullptr; | |
508 actual->GetList("params.suggestionInfo", &suggestions); | |
509 base::DictionaryValue* suggestion = nullptr; | |
510 suggestions->GetDictionary(0, &suggestion); | |
511 suggestion->SetString("suggestionId", "42"); | |
512 suggestion->SetString("timestamp", "9001"); | |
513 static const std::string expected_data = | |
514 "{\"apiVersion\":\"v2\"," | |
515 "\"method\":\"spelling.feedback\"," | |
516 "\"params\":" | |
517 "{\"clientName\":\"Chrome\"," | |
518 "\"originCountry\":\"USA\"," | |
519 "\"key\":\"TestDummyKey\"," | |
520 "\"language\":\"en\"," | |
521 "\"suggestionInfo\":[{" | |
522 "\"isAutoCorrection\":false," | |
523 "\"isFirstInSession\":true," | |
524 "\"misspelledLength\":6," | |
525 "\"misspelledStart\":0," | |
526 "\"originalText\":\"Helllo world\"," | |
527 "\"suggestionId\":\"42\"," | |
528 "\"suggestions\":[\"Hello\"]," | |
529 "\"timestamp\":\"9001\"," | |
530 "\"userActions\":[{\"actionType\":\"NO_ACTION\"}]," | |
531 "\"userMisspellingId\":\"14573599553589145012\"," | |
532 "\"userSuggestionId\":[\"14761077877524043800\"]}]}}"; | |
533 std::unique_ptr<base::Value> expected = base::JSONReader::Read(expected_data); | |
534 EXPECT_TRUE(expected->Equals(actual.get())) | |
535 << "Expected data: " << expected_data | |
536 << "\nActual data: " << actual_data; | |
537 } | |
538 | |
539 // The default API version is "v2". | |
540 TEST_F(FeedbackSenderTest, DefaultApiVersion) { | |
541 AddPendingFeedback(); | |
542 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
543 std::vector<uint32_t>()); | |
544 EXPECT_TRUE(UploadDataContains("\"apiVersion\":\"v2\"")); | |
545 EXPECT_FALSE(UploadDataContains("\"apiVersion\":\"v2-internal\"")); | |
546 } | |
547 | |
548 // The API version should not change for field-trial participants that do not | |
549 // append the command-line switch. | |
550 TEST_F(FeedbackSenderTest, FieldTrialAloneHasSameApiVersion) { | |
551 EnableFieldTrial(); | |
552 | |
553 AddPendingFeedback(); | |
554 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
555 std::vector<uint32_t>()); | |
556 | |
557 EXPECT_TRUE(UploadDataContains("\"apiVersion\":\"v2\"")); | |
558 EXPECT_FALSE(UploadDataContains("\"apiVersion\":\"v2-internal\"")); | |
559 } | |
560 | |
561 // The API version should not change if the command-line switch is appended, but | |
562 // the user is not participating in the field-trial. | |
563 TEST_F(FeedbackSenderTest, CommandLineSwitchAloneHasSameApiVersion) { | |
564 AppendCommandLineSwitch(); | |
565 | |
566 AddPendingFeedback(); | |
567 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
568 std::vector<uint32_t>()); | |
569 | |
570 EXPECT_TRUE(UploadDataContains("\"apiVersion\":\"v2\"")); | |
571 EXPECT_FALSE(UploadDataContains("\"apiVersion\":\"v2-internal\"")); | |
572 } | |
573 | |
574 // The API version should be different for field-trial participants that also | |
575 // append the command-line switch. | |
576 TEST_F(FeedbackSenderTest, InternalApiVersion) { | |
577 AppendCommandLineSwitch(); | |
578 EnableFieldTrial(); | |
579 | |
580 AddPendingFeedback(); | |
581 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
582 std::vector<uint32_t>()); | |
583 | |
584 EXPECT_FALSE(UploadDataContains("\"apiVersion\":\"v2\"")); | |
585 EXPECT_TRUE(UploadDataContains("\"apiVersion\":\"v2-internal\"")); | |
586 } | |
587 | |
588 // Duplicate spellcheck results should be matched to the existing markers. | |
589 TEST_F(FeedbackSenderTest, MatchDupliateResultsWithExistingMarkers) { | |
590 uint32_t hash = AddPendingFeedback(); | |
591 std::vector<SpellCheckResult> results( | |
592 1, | |
593 SpellCheckResult(SpellCheckResult::SPELLING, | |
594 kMisspellingStart, | |
595 kMisspellingLength, | |
596 base::ASCIIToUTF16("Hello"))); | |
597 std::vector<SpellCheckMarker> markers( | |
598 1, SpellCheckMarker(hash, results[0].location)); | |
599 EXPECT_EQ(static_cast<uint32_t>(0), results[0].hash); | |
600 feedback_->OnSpellcheckResults( | |
601 kRendererProcessId, base::UTF8ToUTF16(kText), markers, &results); | |
602 EXPECT_EQ(hash, results[0].hash); | |
603 } | |
604 | |
605 // Adding a word to dictionary should trigger ADD_TO_DICT feedback for every | |
606 // occurrence of that word. | |
607 TEST_F(FeedbackSenderTest, MultipleAddToDictFeedback) { | |
608 std::vector<SpellCheckResult> results; | |
609 static const int kSentenceLength = 14; | |
610 static const int kNumberOfSentences = 2; | |
611 static const base::string16 kTextWithDuplicates = | |
612 base::ASCIIToUTF16("Helllo world. Helllo world."); | |
613 for (int i = 0; i < kNumberOfSentences; ++i) { | |
614 results.push_back(SpellCheckResult(SpellCheckResult::SPELLING, | |
615 kMisspellingStart + i * kSentenceLength, | |
616 kMisspellingLength, | |
617 base::ASCIIToUTF16("Hello"))); | |
618 } | |
619 static const int kNumberOfRenderers = 2; | |
620 int last_renderer_process_id = -1; | |
621 for (int i = 0; i < kNumberOfRenderers; ++i) { | |
622 feedback_->OnSpellcheckResults(kRendererProcessId + i, | |
623 kTextWithDuplicates, | |
624 std::vector<SpellCheckMarker>(), | |
625 &results); | |
626 last_renderer_process_id = kRendererProcessId + i; | |
627 } | |
628 std::vector<uint32_t> remaining_markers; | |
629 for (size_t i = 0; i < results.size(); ++i) | |
630 remaining_markers.push_back(results[i].hash); | |
631 feedback_->OnReceiveDocumentMarkers(last_renderer_process_id, | |
632 remaining_markers); | |
633 EXPECT_TRUE(UploadDataContains("PENDING", 2)); | |
634 EXPECT_FALSE(UploadDataContains("ADD_TO_DICT")); | |
635 | |
636 feedback_->AddedToDictionary(results[0].hash); | |
637 feedback_->OnReceiveDocumentMarkers(last_renderer_process_id, | |
638 remaining_markers); | |
639 EXPECT_FALSE(UploadDataContains("PENDING")); | |
640 EXPECT_TRUE(UploadDataContains("ADD_TO_DICT", 2)); | |
641 } | |
642 | |
643 // ADD_TO_DICT feedback for multiple occurrences of a word should trigger only | |
644 // for pending feedback. | |
645 TEST_F(FeedbackSenderTest, AddToDictOnlyPending) { | |
646 AddPendingFeedback(); | |
647 uint32_t add_to_dict_hash = AddPendingFeedback(); | |
648 uint32_t select_hash = AddPendingFeedback(); | |
649 feedback_->SelectedSuggestion(select_hash, 0); | |
650 feedback_->AddedToDictionary(add_to_dict_hash); | |
651 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
652 std::vector<uint32_t>()); | |
653 EXPECT_TRUE(UploadDataContains("SELECT", 1)); | |
654 EXPECT_TRUE(UploadDataContains("ADD_TO_DICT", 2)); | |
655 } | |
656 | |
657 // Spellcheck results that are out-of-bounds are not added to feedback. | |
658 TEST_F(FeedbackSenderTest, IgnoreOutOfBounds) { | |
659 std::vector<SpellCheckResult> results; | |
660 results.push_back(SpellCheckResult( | |
661 SpellCheckResult::SPELLING, 0, 100, base::UTF8ToUTF16("Hello"))); | |
662 results.push_back(SpellCheckResult( | |
663 SpellCheckResult::SPELLING, 100, 3, base::UTF8ToUTF16("world"))); | |
664 results.push_back(SpellCheckResult( | |
665 SpellCheckResult::SPELLING, -1, 3, base::UTF8ToUTF16("how"))); | |
666 results.push_back(SpellCheckResult( | |
667 SpellCheckResult::SPELLING, 0, 0, base::UTF8ToUTF16("are"))); | |
668 results.push_back(SpellCheckResult( | |
669 SpellCheckResult::SPELLING, 2, -1, base::UTF8ToUTF16("you"))); | |
670 feedback_->OnSpellcheckResults(kRendererProcessId, | |
671 base::UTF8ToUTF16(kText), | |
672 std::vector<SpellCheckMarker>(), | |
673 &results); | |
674 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
675 std::vector<uint32_t>()); | |
676 EXPECT_FALSE(IsUploadingData()); | |
677 } | |
678 | |
679 // FeedbackSender does not collect and upload feedback when instructed to stop. | |
680 TEST_F(FeedbackSenderTest, CanStopFeedbackCollection) { | |
681 feedback_->StopFeedbackCollection(); | |
682 AddPendingFeedback(); | |
683 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
684 std::vector<uint32_t>()); | |
685 EXPECT_FALSE(IsUploadingData()); | |
686 } | |
687 | |
688 // FeedbackSender resumes collecting and uploading feedback when instructed to | |
689 // start after stopping. | |
690 TEST_F(FeedbackSenderTest, CanResumeFeedbackCollection) { | |
691 feedback_->StopFeedbackCollection(); | |
692 feedback_->StartFeedbackCollection(); | |
693 AddPendingFeedback(); | |
694 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
695 std::vector<uint32_t>()); | |
696 EXPECT_TRUE(IsUploadingData()); | |
697 } | |
698 | |
699 // FeedbackSender does not collect data while being stopped and upload it later. | |
700 TEST_F(FeedbackSenderTest, NoFeedbackCollectionWhenStopped) { | |
701 feedback_->StopFeedbackCollection(); | |
702 AddPendingFeedback(); | |
703 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
704 std::vector<uint32_t>()); | |
705 feedback_->StartFeedbackCollection(); | |
706 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
707 std::vector<uint32_t>()); | |
708 EXPECT_FALSE(IsUploadingData()); | |
709 } | |
710 | |
711 // The feedback context is trimmed to 2 words on the left and 2 words on the | |
712 // right side of the misspelling. | |
713 TEST_F(FeedbackSenderTest, TrimFeedback) { | |
714 std::vector<SpellCheckResult> results( | |
715 1, SpellCheckResult(SpellCheckResult::SPELLING, 13, 3, | |
716 base::UTF8ToUTF16("the"))); | |
717 feedback_->OnSpellcheckResults( | |
718 kRendererProcessId, | |
719 base::UTF8ToUTF16("Far and away teh best prize that life has to offer is " | |
720 "the chance to work hard at work worth doing."), | |
721 std::vector<SpellCheckMarker>(), &results); | |
722 feedback_->OnReceiveDocumentMarkers(kRendererProcessId, | |
723 std::vector<uint32_t>()); | |
724 EXPECT_TRUE( | |
725 UploadDataContains(",\"originalText\":\"and away teh best prize\",")); | |
726 EXPECT_TRUE(UploadDataContains(",\"misspelledStart\":9,")); | |
727 } | |
728 | |
729 } // namespace spellcheck | |
OLD | NEW |