1 // Copyright 2019 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 "chrome/browser/autofill/automated_tests/cache_replayer.h"
6
7 #include <memory>
8 #include <type_traits>
9 #include <utility>
10 #include <vector>
11
12 #include "base/base64.h"
13 #include "base/files/file_path.h"
14 #include "base/files/file_util.h"
15 #include "base/files/scoped_temp_dir.h"
16 #include "base/json/json_writer.h"
17 #include "base/strings/strcat.h"
18 #include "base/strings/string_util.h"
19 #include "base/values.h"
20 #include "build/build_config.h"
21 #include "components/autofill/core/browser/proto/server.pb.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #include "third_party/zlib/google/compression_utils.h"
24
25 namespace autofill {
26 namespace test {
27 namespace {
28
29 // Only run these tests on Linux because there are issues with other platforms.
30 // Testing on one platform gives enough confidence.
31 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
32
33 using base::JSONWriter;
34 using base::Value;
35
36 // Request Response Pair for the API server
37 using RequestResponsePair =
38 std::pair<AutofillPageQueryRequest, AutofillQueryResponse>;
39
40 constexpr char kTestHTTPResponseHeader[] = "Fake HTTP Response Header";
41 constexpr char kHTTPBodySep[] = "\r\n\r\n";
42 // The host name of the autofill server.
43 constexpr char kHostname[] = "content-autofill.googleapis.com";
44
45 struct LightField {
46 uint32_t signature;
47 uint32_t prediction;
48 };
49
50 struct LightForm {
51 uint64_t signature;
52 std::vector<LightField> fields;
53 };
54
CreateQueryUrl(const std::string & base64_encoded_query)55 std::string CreateQueryUrl(const std::string& base64_encoded_query) {
56 constexpr char base_url[] =
57 "https://content-autofill.googleapis.com/v1/pages:get";
58 if (base64_encoded_query.empty())
59 return base_url;
60 return base::StrCat({base_url, "/", base64_encoded_query});
61 }
62
GetServerResponseForQuery(const ServerCacheReplayer & cache_replayer,const AutofillPageQueryRequest & query,std::string * http_text)63 bool GetServerResponseForQuery(const ServerCacheReplayer& cache_replayer,
64 const AutofillPageQueryRequest& query,
65 std::string* http_text) {
66 return cache_replayer.GetApiServerResponseForQuery(query, http_text);
67 }
68
MakeQueryRequestResponsePair(const std::vector<LightForm> & forms)69 RequestResponsePair MakeQueryRequestResponsePair(
70 const std::vector<LightForm>& forms) {
71 AutofillPageQueryRequest query;
72 AutofillQueryResponse response;
73 for (const auto& form : forms) {
74 auto* query_form = query.add_forms();
75 query_form->set_signature(form.signature);
76 auto* response_form = response.add_form_suggestions();
77 for (const auto& field : form.fields) {
78 query_form->add_fields()->set_signature(field.signature);
79 auto* response_field = response_form->add_field_suggestions();
80 response_field->set_field_signature(field.signature);
81 response_field->set_primary_type_prediction(field.prediction);
82 response_field->add_predictions()->set_type(field.prediction);
83 }
84 }
85 return RequestResponsePair({std::move(query), std::move(response)});
86 }
87
88 // Returns a query request URL. If |query| is not empty, the corresponding
89 // query is encoded into the URL.
MakeQueryRequestURL(const base::Optional<AutofillPageQueryRequest> & query,std::string * request_url)90 bool MakeQueryRequestURL(const base::Optional<AutofillPageQueryRequest>& query,
91 std::string* request_url) {
92 if (!query.has_value()) {
93 *request_url = CreateQueryUrl("");
94 return true;
95 }
96 std::string encoded_query;
97 std::string serialized_query;
98 if (!(*query).SerializeToString(&serialized_query)) {
99 VLOG(1) << "could not serialize Query proto";
100 return false;
101 }
102 base::Base64Encode(serialized_query, &encoded_query);
103 *request_url = CreateQueryUrl(encoded_query);
104 return true;
105 }
106
107 // Make HTTP request header given |url|.
MakeRequestHeader(base::StringPiece url)108 inline std::string MakeRequestHeader(base::StringPiece url) {
109 return base::StrCat({"GET ", url, " ", "HTTP/1.1"});
110 }
111
112 // Makes string value for "SerializedRequest" json node that contains HTTP
113 // request content.
MakeSerializedRequest(const AutofillPageQueryRequest & query,RequestType type,std::string * serialized_request,std::string * request_url)114 bool MakeSerializedRequest(const AutofillPageQueryRequest& query,
115 RequestType type,
116 std::string* serialized_request,
117 std::string* request_url) {
118 // Make body and query content for URL depending on the |type|.
119 std::string body;
120 base::Optional<AutofillPageQueryRequest> query_for_url;
121 if (type == RequestType::kQueryProtoGET) {
122 query_for_url = std::move(query);
123 } else {
124 query.SerializeToString(&body);
125 query_for_url = base::nullopt;
126 }
127
128 // Make header according to query content for URL.
129 std::string url;
130 if (!MakeQueryRequestURL(query_for_url, &url))
131 return false;
132 *request_url = url;
133 std::string header = MakeRequestHeader(url);
134
135 // Fill HTTP text.
136 std::string http_text =
137 base::JoinString(std::vector<std::string>{header, body}, kHTTPBodySep);
138 base::Base64Encode(http_text, serialized_request);
139 return true;
140 }
141
MakeSerializedResponse(const AutofillQueryResponse & query_response)142 std::string MakeSerializedResponse(
143 const AutofillQueryResponse& query_response) {
144 std::string serialized_response;
145 query_response.SerializeToString(&serialized_response);
146
147 // The Api Environment expects the response body to be base64 encoded.
148 std::string tmp;
149 base::Base64Encode(serialized_response, &tmp);
150 serialized_response = tmp;
151
152 std::string compressed_query;
153 compression::GzipCompress(serialized_response, &compressed_query);
154 // TODO(vincb): Put a real header here.
155 std::string http_text = base::JoinString(
156 std::vector<std::string>{kTestHTTPResponseHeader, compressed_query},
157 kHTTPBodySep);
158 std::string encoded_http_text;
159 base::Base64Encode(http_text, &encoded_http_text);
160 return encoded_http_text;
161 }
162
163 // Write json node to file in text format.
WriteJSONNode(const base::FilePath & file_path,const base::Value & node)164 bool WriteJSONNode(const base::FilePath& file_path, const base::Value& node) {
165 std::string json_text;
166 JSONWriter::WriteWithOptions(node, JSONWriter::Options::OPTIONS_PRETTY_PRINT,
167 &json_text);
168
169 std::string compressed_json_text;
170 if (!compression::GzipCompress(json_text, &compressed_json_text)) {
171 VLOG(1) << "Cannot compress json to gzip.";
172 return false;
173 }
174
175 if (!base::WriteFile(file_path, compressed_json_text)) {
176 VLOG(1) << "Could not write json at file: " << file_path;
177 return false;
178 }
179 return true;
180 }
181
182 // Write cache to file in json text format.
WriteJSON(const base::FilePath & file_path,const std::vector<RequestResponsePair> & request_response_pairs,RequestType request_type=RequestType::kQueryProtoPOST)183 bool WriteJSON(const base::FilePath& file_path,
184 const std::vector<RequestResponsePair>& request_response_pairs,
185 RequestType request_type = RequestType::kQueryProtoPOST) {
186 // Make json list node that contains all query requests.
187 base::Value::DictStorage urls_dict;
188 for (const auto& request_response_pair : request_response_pairs) {
189 std::string serialized_request;
190 std::string url;
191 if (!MakeSerializedRequest(request_response_pair.first, request_type,
192 &serialized_request, &url)) {
193 return false;
194 }
195
196 Value::DictStorage request_response_node;
197 request_response_node.emplace("SerializedRequest",
198 std::move(serialized_request));
199 request_response_node.emplace(
200 "SerializedResponse",
201 MakeSerializedResponse(request_response_pair.second));
202 // Populate json dict node that contains Autofill Server requests per URL.
203 // This will construct an empty list for `url` if it didn't exist already.
204 auto& url_list = urls_dict.emplace(url, Value::Type::LIST).first->second;
205 url_list.Append(Value(std::move(request_response_node)));
206 }
207
208 // Make json dict node that contains requests per domain.
209 base::Value::DictStorage domains_dict;
210 domains_dict.emplace(kHostname, std::move(urls_dict));
211
212 // Make json root dict.
213 base::Value::DictStorage root_dict;
214 root_dict.emplace("Requests", std::move(domains_dict));
215
216 // Write content to JSON file.
217 return WriteJSONNode(file_path, Value(std::move(root_dict)));
218 }
219
TEST(AutofillCacheReplayerDeathTest,ServerCacheReplayerConstructor_CrashesWhenNoDomainNode)220 TEST(AutofillCacheReplayerDeathTest,
221 ServerCacheReplayerConstructor_CrashesWhenNoDomainNode) {
222 // Make death test threadsafe.
223 testing::FLAGS_gtest_death_test_style = "threadsafe";
224
225 // Make writable file path.
226 base::ScopedTempDir temp_dir;
227 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
228 const base::FilePath file_path =
229 temp_dir.GetPath().AppendASCII("test_wpr_capture.json");
230
231 // JSON structure is not right.
232 const std::string invalid_json = "{\"NoDomainNode\": \"invalid_field\"}";
233
234 // Write json to file.
235 ASSERT_TRUE(base::WriteFile(file_path, invalid_json))
236 << "there was an error when writing content to json file: " << file_path;
237
238 // Crash since json content is invalid.
239 ASSERT_DEATH_IF_SUPPORTED(
240 ServerCacheReplayer(file_path,
241 ServerCacheReplayer::kOptionFailOnInvalidJsonRecord),
242 ".*");
243 }
244
TEST(AutofillCacheReplayerDeathTest,ServerCacheReplayerConstructor_CrashesWhenNoQueryNodesAndFailOnEmpty)245 TEST(AutofillCacheReplayerDeathTest,
246 ServerCacheReplayerConstructor_CrashesWhenNoQueryNodesAndFailOnEmpty) {
247 // Make death test threadsafe.
248 testing::FLAGS_gtest_death_test_style = "threadsafe";
249
250 // Make writable file path.
251 base::ScopedTempDir temp_dir;
252 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
253 const base::FilePath file_path =
254 temp_dir.GetPath().AppendASCII("test_wpr_capture.json");
255
256 // Make empty request/response pairs to write in cache.
257 std::vector<RequestResponsePair> request_response_pairs;
258
259 // Write cache to json and create replayer.
260 ASSERT_TRUE(WriteJSON(file_path, request_response_pairs));
261
262 // Crash since there are no Query nodes and set to fail on empty.
263 ASSERT_DEATH_IF_SUPPORTED(
264 ServerCacheReplayer(file_path,
265 ServerCacheReplayer::kOptionFailOnInvalidJsonRecord |
266 ServerCacheReplayer::kOptionFailOnEmpty),
267 ".*");
268 }
269
270 // Test suite for GET Query death test.
271 class AutofillCacheReplayerGETQueryDeathTest
272 : public testing::TestWithParam<std::string> {};
273
TEST_P(AutofillCacheReplayerGETQueryDeathTest,ApiServerCacheReplayerConstructor_CrashesWhenInvalidRequestURLForGETQuery)274 TEST_P(
275 AutofillCacheReplayerGETQueryDeathTest,
276 ApiServerCacheReplayerConstructor_CrashesWhenInvalidRequestURLForGETQuery) {
277 // Parameterized death test for populating cache when keys that are obtained
278 // from the URL's query parameter are invalid.
279
280 // Make death test threadsafe.
281 testing::FLAGS_gtest_death_test_style = "threadsafe";
282
283 // Make writable file path.
284 base::ScopedTempDir temp_dir;
285 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
286 const base::FilePath file_path =
287 temp_dir.GetPath().AppendASCII("test_wpr_capture.json");
288
289 // Make JSON content.
290
291 // Make json list node that contains the problematic query request.
292 Value::DictStorage request_response_node;
293 // Put some textual content for HTTP request. Content does not matter because
294 // the Query content will be parsed from the URL that corresponds to the
295 // dictionary key.
296 request_response_node.emplace(
297 "SerializedRequest", base::StrCat({"GET ", CreateQueryUrl("1234").c_str(),
298 " HTTP/1.1\r\n\r\n"}));
299 request_response_node.emplace(
300 "SerializedResponse", MakeSerializedResponse(AutofillQueryResponse()));
301
302 base::Value::ListStorage url_list;
303 url_list.emplace_back(std::move(request_response_node));
304
305 // Populate json dict node that contains Autofill Server requests per URL.
306 base::Value::DictStorage urls_dict;
307 // The query parameter in the URL cannot be parsed to a proto because
308 // parameter value is in invalid format.
309 urls_dict.emplace(CreateQueryUrl(GetParam()), std::move(url_list));
310
311 // Make json dict node that contains requests per domain.
312 base::Value::DictStorage domains_dict;
313 domains_dict.emplace(kHostname, std::move(urls_dict));
314 // Make json root dict.
315 base::Value::DictStorage root_dict;
316 root_dict.emplace("Requests", std::move(domains_dict));
317 // Write content to JSON file.
318 ASSERT_TRUE(WriteJSONNode(file_path, Value(std::move(root_dict))));
319
320 // Make death assertion.
321
322 // Crash since request cannot be parsed to a proto.
323 ASSERT_DEATH_IF_SUPPORTED(
324 ServerCacheReplayer(file_path,
325 ServerCacheReplayer::kOptionFailOnInvalidJsonRecord),
326 ".*");
327 }
328
329 INSTANTIATE_TEST_SUITE_P(
330 GetQueryParameterizedDeathTest,
331 AutofillCacheReplayerGETQueryDeathTest,
332 testing::Values( // Can be base-64 decoded, but not parsed to proto.
333 "1234",
334 // Cannot be base-64 decoded.
335 "^^^"));
336
TEST(AutofillCacheReplayerTest,CanUseReplayerWhenNoCacheContentWithNotFailOnEmpty)337 TEST(AutofillCacheReplayerTest,
338 CanUseReplayerWhenNoCacheContentWithNotFailOnEmpty) {
339 // Make death test threadsafe.
340 testing::FLAGS_gtest_death_test_style = "threadsafe";
341
342 // Make writable file path.
343 base::ScopedTempDir temp_dir;
344 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
345 const base::FilePath file_path =
346 temp_dir.GetPath().AppendASCII("test_wpr_capture.json");
347
348 // Make empty request/response pairs to write in cache.
349 std::vector<RequestResponsePair> request_response_pairs;
350
351 // Write cache to json and create replayer.
352 ASSERT_TRUE(WriteJSON(file_path, request_response_pairs));
353
354 // Should not crash even if no cache because kOptionFailOnEmpty is not
355 // flipped.
356 ServerCacheReplayer cache_replayer(
357 file_path, ServerCacheReplayer::kOptionFailOnInvalidJsonRecord &
358 (ServerCacheReplayer::kOptionFailOnEmpty & 0));
359
360 // Should be able to read cache, which will give nothing.
361 std::string http_text;
362 AutofillPageQueryRequest query_with_no_match;
363 EXPECT_FALSE(GetServerResponseForQuery(cache_replayer, query_with_no_match,
364 &http_text));
365 }
366
367 template <typename U, typename V>
ProtobufsEqual(const U & u,const V & v)368 bool ProtobufsEqual(const U& u, const V& v) {
369 // Unfortunately, Chrome uses MessageLite, so we cannot use DebugString or the
370 // MessageDifferencer.
371 std::string u_serialized, v_serialized;
372 u.SerializeToString(&u_serialized);
373 v.SerializeToString(&v_serialized);
374 if (u_serialized != v_serialized) {
375 LOG(ERROR) << "Expected protobufs to be equal:\n" << u << "and:\n" << v;
376 LOG(ERROR) << "Note that this output is based on custom written string "
377 "serializers and the protobufs may be different in ways that "
378 "are not shown here.";
379 }
380 return u_serialized == v_serialized;
381 }
382
TEST(AutofillCacheReplayerTest,ProtobufConversion)383 TEST(AutofillCacheReplayerTest, ProtobufConversion) {
384 AutofillRandomizedFormMetadata form_metadata;
385 form_metadata.mutable_id()->set_encoded_bits("foobar");
386
387 AutofillRandomizedFieldMetadata field_metadata;
388 field_metadata.mutable_id()->set_encoded_bits("foobarbaz");
389
390 // Form 1 (fields 101, 102), Form 2 (fields 201).
391 LegacyEnv::Query legacy_query;
392 {
393 legacy_query.set_client_version("DummyClient");
394 auto* form1 = legacy_query.add_form();
395 form1->set_signature(1);
396 form1->mutable_form_metadata()->CopyFrom(form_metadata);
397 auto* field101 = form1->add_field();
398 field101->set_signature(101);
399 field101->set_name("field_101");
400 field101->set_type("text");
401 field101->mutable_field_metadata()->CopyFrom(field_metadata);
402 auto* field102 = form1->add_field();
403 field102->set_signature(102);
404 field102->set_name("field_102");
405 field102->set_type("text");
406
407 auto* form2 = legacy_query.add_form();
408 form2->set_signature(2);
409 auto* field201 = form2->add_field();
410 field201->set_signature(201);
411 field201->set_name("field_201");
412 field201->set_type("text");
413
414 legacy_query.add_experiments(50);
415 legacy_query.add_experiments(51);
416 }
417
418 ApiEnv::Query api_query;
419 {
420 auto* form1 = api_query.add_forms();
421 form1->set_signature(1);
422 form1->mutable_metadata()->CopyFrom(form_metadata);
423 auto* field101 = form1->add_fields();
424 field101->set_signature(101);
425 field101->set_name("field_101");
426 field101->set_control_type("text");
427 field101->mutable_metadata()->CopyFrom(field_metadata);
428 auto* field102 = form1->add_fields();
429 field102->set_signature(102);
430 field102->set_name("field_102");
431 field102->set_control_type("text");
432
433 auto* form2 = api_query.add_forms();
434 form2->set_signature(2);
435 auto* field201 = form2->add_fields();
436 field201->set_signature(201);
437 field201->set_name("field_201");
438 field201->set_control_type("text");
439
440 api_query.add_experiments(50);
441 api_query.add_experiments(51);
442 }
443
444 LegacyEnv::Response legacy_response;
445 {
446 auto* field101 = legacy_response.add_field();
447 field101->set_overall_type_prediction(101);
448 auto* field101_prediction = field101->add_predictions();
449 field101_prediction->set_type(101);
450 field101_prediction->set_may_use_prefilled_placeholder(true);
451 field101_prediction = field101->add_predictions();
452 field101_prediction->set_type(1010);
453 field101_prediction->set_may_use_prefilled_placeholder(true);
454 // Todo: Password requirements
455 auto* field102 = legacy_response.add_field();
456 field102->set_overall_type_prediction(102);
457 auto* field102_prediction = field102->add_predictions();
458 field102_prediction->set_type(102);
459 field102_prediction->set_may_use_prefilled_placeholder(false);
460
461 auto* field201 = legacy_response.add_field();
462 field201->set_overall_type_prediction(201);
463 field201->add_predictions()->set_type(201);
464 }
465
466 ApiEnv::Response api_response;
467 {
468 auto* form1 = api_response.add_form_suggestions();
469 auto* field101 = form1->add_field_suggestions();
470 field101->set_field_signature(101);
471 field101->set_primary_type_prediction(101);
472 field101->add_predictions()->set_type(101);
473 field101->add_predictions()->set_type(1010);
474 field101->set_may_use_prefilled_placeholder(true);
475 // Todo: Password requirements
476 auto* field102 = form1->add_field_suggestions();
477 field102->set_field_signature(102);
478 field102->set_primary_type_prediction(102);
479 field102->add_predictions()->set_type(102);
480 field102->set_may_use_prefilled_placeholder(false);
481
482 auto* form2 = api_response.add_form_suggestions();
483 auto* field201 = form2->add_field_suggestions();
484 field201->set_field_signature(201);
485 field201->set_primary_type_prediction(201);
486 field201->add_predictions()->set_type(201);
487 }
488
489 // Verify equivalence of converted queries.
490 EXPECT_TRUE(ProtobufsEqual(api_query, api_query));
491 EXPECT_TRUE(ProtobufsEqual(api_query, ConvertQuery<ApiEnv>(api_query)));
492 EXPECT_TRUE(ProtobufsEqual(api_query, ConvertQuery<LegacyEnv>(legacy_query)));
493
494 // Verify equivalence of converted responses.
495 EXPECT_TRUE(ProtobufsEqual(api_response, api_response));
496 EXPECT_TRUE(ProtobufsEqual(api_response,
497 ConvertResponse<ApiEnv>(api_response, api_query)));
498 EXPECT_TRUE(ProtobufsEqual(
499 api_response, ConvertResponse<LegacyEnv>(legacy_response, legacy_query)));
500 }
501
502 // Test suite for Query response retrieval test.
503 class AutofillCacheReplayerGetResponseForQueryTest
504 : public testing::TestWithParam<RequestType> {};
505
TEST_P(AutofillCacheReplayerGetResponseForQueryTest,FillsResponseWhenNoErrors)506 TEST_P(AutofillCacheReplayerGetResponseForQueryTest,
507 FillsResponseWhenNoErrors) {
508 // Make writable file path.
509 base::ScopedTempDir temp_dir;
510 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
511 base::FilePath file_path =
512 temp_dir.GetPath().AppendASCII("test_wpr_capture.json");
513
514 // Make request/response pairs to write in cache.
515 std::vector<RequestResponsePair> request_response_pairs;
516 {
517 LightForm form_to_add;
518 form_to_add.signature = 1234;
519 form_to_add.fields = {LightField{1234, 1}};
520 request_response_pairs.push_back(
521 MakeQueryRequestResponsePair({form_to_add}));
522 }
523
524 // Write cache to json.
525 ASSERT_TRUE(WriteJSON(file_path, request_response_pairs, GetParam()));
526
527 ServerCacheReplayer cache_replayer(
528 file_path, ServerCacheReplayer::kOptionFailOnInvalidJsonRecord &
529 ServerCacheReplayer::kOptionFailOnEmpty);
530
531 // Verify if we can get cached response.
532 std::string http_text_response;
533 ASSERT_TRUE(GetServerResponseForQuery(
534 cache_replayer, request_response_pairs[0].first, &http_text_response));
535 std::string body = SplitHTTP(http_text_response).second;
536
537 // The Api Environment expects the response to be base64 encoded.
538 std::string tmp;
539 ASSERT_TRUE(base::Base64Decode(body, &tmp));
540 body = tmp;
541
542 AutofillQueryResponse response_from_cache;
543 ASSERT_TRUE(response_from_cache.ParseFromString(body));
544 }
545
546 INSTANTIATE_TEST_SUITE_P(GetResponseForQueryParameterizeTest,
547 AutofillCacheReplayerGetResponseForQueryTest,
548 testing::Values(
549 // Read Query content from URL "q" param.
550 RequestType::kQueryProtoGET,
551 // Read Query content from HTTP body.
552 RequestType::kQueryProtoPOST));
TEST(AutofillCacheReplayerTest,GetResponseForQueryGivesFalseWhenNullptr)553 TEST(AutofillCacheReplayerTest, GetResponseForQueryGivesFalseWhenNullptr) {
554 ServerCacheReplayer cache_replayer(ServerCache{{}});
555 EXPECT_FALSE(GetServerResponseForQuery(cache_replayer,
556 AutofillPageQueryRequest(), nullptr));
557 }
558
TEST(AutofillCacheReplayerTest,GetResponseForQueryGivesFalseWhenNoKeyMatch)559 TEST(AutofillCacheReplayerTest, GetResponseForQueryGivesFalseWhenNoKeyMatch) {
560 // Make writable file path.
561 base::ScopedTempDir temp_dir;
562 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
563 base::FilePath file_path =
564 temp_dir.GetPath().AppendASCII("test_wpr_capture.json");
565
566 // Make request/response pairs to write in cache.
567 std::vector<RequestResponsePair> request_response_pairs;
568 {
569 LightForm form_to_add;
570 form_to_add.signature = 1234;
571 form_to_add.fields = {LightField{1234, 1}};
572 request_response_pairs.push_back(
573 MakeQueryRequestResponsePair({form_to_add}));
574 }
575
576 // Write cache to json and create replayer.
577 ASSERT_TRUE(WriteJSON(file_path, request_response_pairs));
578 ServerCacheReplayer cache_replayer(
579 file_path, ServerCacheReplayer::kOptionFailOnInvalidJsonRecord &
580 ServerCacheReplayer::kOptionFailOnEmpty);
581
582 // Verify if we get false when there is no cache for the query.
583 std::string http_text;
584 AutofillPageQueryRequest query_with_no_match;
585 EXPECT_FALSE(GetServerResponseForQuery(cache_replayer, query_with_no_match,
586 &http_text));
587 }
588
TEST(AutofillCacheReplayerTest,GetResponseForQueryGivesFalseWhenDecompressFailsBecauseInvalidHTTP)589 TEST(AutofillCacheReplayerTest,
590 GetResponseForQueryGivesFalseWhenDecompressFailsBecauseInvalidHTTP) {
591 // Make query request and key.
592 LightForm form_to_add;
593 form_to_add.signature = 1234;
594 form_to_add.fields = {LightField{1234, 1}};
595 const AutofillPageQueryRequest query_request_for_key =
596 MakeQueryRequestResponsePair({form_to_add}).first;
597 const std::string key = GetKeyFromQuery<ApiEnv>(query_request_for_key);
598
599 const char invalid_http[] = "Dumb Nonsense That Doesn't Have a HTTP Header";
600 ServerCacheReplayer cache_replayer(ServerCache{{key, invalid_http}});
601
602 // Verify if we get false when invalid HTTP response to decompress.
603 std::string response_http_text;
604 EXPECT_FALSE(GetServerResponseForQuery(cache_replayer, query_request_for_key,
605 &response_http_text));
606 }
607
TEST(AutofillCacheReplayerTest,GetResponseForQueryGivesTrueWhenDecompressSucceededBecauseEmptyBody)608 TEST(AutofillCacheReplayerTest,
609 GetResponseForQueryGivesTrueWhenDecompressSucceededBecauseEmptyBody) {
610 // Make query request and key.
611 LightForm form_to_add;
612 form_to_add.signature = 1234;
613 form_to_add.fields = {LightField{1234, 1}};
614 const AutofillPageQueryRequest query_request_for_key =
615 MakeQueryRequestResponsePair({form_to_add}).first;
616 const std::string key = GetKeyFromQuery<ApiEnv>(query_request_for_key);
617
618 const char http_without_body[] = "Test HTTP Header\r\n\r\n";
619 ServerCacheReplayer cache_replayer(ServerCache{{key, http_without_body}});
620
621 // Verify if we get true when no HTTP body.
622 std::string response_http_text;
623 EXPECT_TRUE(GetServerResponseForQuery(cache_replayer, query_request_for_key,
624 &response_http_text));
625 }
626
627 // Returns whether the forms in |response| and |forms| match. If both contain
628 // the same number of forms, a boolean is appended to the output for each form
629 // indicating whether the expectation and actual form matched. In case of
630 // gross mismatch, the function may return an empty vector.
DoFormsMatch(const AutofillQueryResponse & response,const std::vector<LightForm> & forms)631 std::vector<bool> DoFormsMatch(const AutofillQueryResponse& response,
632 const std::vector<LightForm>& forms) {
633 std::vector<bool> found;
634 for (int i = 0; i < std::min(static_cast<int>(forms.size()),
635 response.form_suggestions_size());
636 ++i) {
637 const auto& expected_form = forms[i];
638 const auto& response_form = response.form_suggestions(i);
639 if (static_cast<int>(expected_form.fields.size()) !=
640 response_form.field_suggestions_size()) {
641 LOG(ERROR) << "Expected form " << i << " to have suggestions for "
642 << expected_form.fields.size() << " fields but got "
643 << response_form.field_suggestions_size();
644 found.push_back(false);
645 continue;
646 }
647 bool found_all_fields = true;
648 for (size_t j = 0; j < expected_form.fields.size(); ++j) {
649 const auto& expected_field = expected_form.fields[j];
650 if (expected_field.signature !=
651 response_form.field_suggestions(j).field_signature()) {
652 LOG(ERROR) << "Expected field " << j << " of form " << i
653 << " to have signature " << expected_field.signature
654 << " but got "
655 << response_form.field_suggestions(j).field_signature();
656 found_all_fields = false;
657 }
658 if (expected_field.prediction !=
659 static_cast<unsigned int>(
660 response_form.field_suggestions(j).primary_type_prediction())) {
661 LOG(ERROR)
662 << "Expected field " << j << " of form " << i
663 << " to have primary type prediction " << expected_field.prediction
664 << " but got "
665 << response_form.field_suggestions(j).primary_type_prediction();
666 found_all_fields = false;
667 }
668 }
669 found.push_back(found_all_fields);
670 }
671 return found;
672 }
673
CheckFormsInCache(const ServerCacheReplayer & cache_replayer,const std::vector<LightForm> & forms)674 std::vector<bool> CheckFormsInCache(const ServerCacheReplayer& cache_replayer,
675 const std::vector<LightForm>& forms) {
676 RequestResponsePair request_response_pair =
677 MakeQueryRequestResponsePair(forms);
678 std::string http_text;
679 if (!GetServerResponseForQuery(cache_replayer, request_response_pair.first,
680 &http_text)) {
681 VLOG(1) << "Server did not respond to the query.";
682 return std::vector<bool>();
683 }
684 std::string body = SplitHTTP(http_text).second;
685
686 // The Api Environment expects the response to be base64 encoded.
687 std::string tmp;
688 if (!base::Base64Decode(body, &tmp)) {
689 LOG(ERROR) << "Unable to base64 decode contents" << body;
690 return std::vector<bool>();
691 }
692 body = tmp;
693
694 AutofillQueryResponse response;
695 CHECK(response.ParseFromString(body)) << body;
696 return DoFormsMatch(response, forms);
697 }
698
TEST(AutofillCacheReplayerTest,CrossEnvironmentIntegrationTest)699 TEST(AutofillCacheReplayerTest, CrossEnvironmentIntegrationTest) {
700 // Make writable file path.
701 base::ScopedTempDir temp_dir;
702 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
703 base::FilePath file_path =
704 temp_dir.GetPath().AppendASCII("test_wpr_capture.json");
705
706 LightForm form1;
707 form1.signature = 1111;
708 form1.fields = {LightField{1111, 1}, LightField{1112, 31},
709 LightField{1113, 33}};
710 LightForm form2;
711 form2.signature = 2222;
712 form2.fields = {LightField{2221, 2}};
713 LightForm form3;
714 form3.signature = 3333;
715 form3.fields = {LightField{3331, 3}};
716 LightForm form4;
717 form4.signature = 4444;
718 form4.fields = {LightField{4441, 4}};
719 LightForm form5;
720 form5.signature = 5555;
721 form5.fields = {LightField{5551, 42}};
722
723 // Make request/response pairs to write in cache.
724 std::vector<RequestResponsePair> request_response_pairs;
725 request_response_pairs.push_back(
726 MakeQueryRequestResponsePair({form1, form2}));
727 request_response_pairs.push_back(
728 MakeQueryRequestResponsePair({form3, form4}));
729
730 // Write cache to json and create replayer.
731 ASSERT_TRUE(WriteJSON(file_path, request_response_pairs));
732 ServerCacheReplayer cache_replayer(
733 file_path, ServerCacheReplayer::kOptionFailOnInvalidJsonRecord &
734 ServerCacheReplayer::kOptionFailOnEmpty);
735
736 std::string http_text;
737
738 // First, check the exact same key combos we sent properly respond
739 EXPECT_EQ(std::vector<bool>({true, true}),
740 CheckFormsInCache(cache_replayer, {form1, form2}));
741 EXPECT_EQ(std::vector<bool>({true, true}),
742 CheckFormsInCache(cache_replayer, {form3, form4}));
743
744 // Existing keys that were requested in a different combination are not
745 // processed.
746 EXPECT_EQ(std::vector<bool>(),
747 CheckFormsInCache(cache_replayer, {form1, form3}));
748 EXPECT_EQ(std::vector<bool>(), CheckFormsInCache(cache_replayer, {form1}));
749
750 // Not in the cache.
751 EXPECT_EQ(std::vector<bool>(), CheckFormsInCache(cache_replayer, {form5}));
752
753 // Now, load the same thing into the cache replayer with
754 // ServerCacheReplayer::kOptionSplitRequestsByForm set and expect matches
755 // for all combos
756 ServerCacheReplayer form_split_cache_replayer(
757 file_path, ServerCacheReplayer::kOptionSplitRequestsByForm);
758
759 // First, check the exact same key combos we sent properly respond
760 EXPECT_EQ(std::vector<bool>({true, true}),
761 CheckFormsInCache(form_split_cache_replayer, {form1, form2}));
762 EXPECT_EQ(std::vector<bool>({true, true}),
763 CheckFormsInCache(form_split_cache_replayer, {form3, form4}));
764
765 // Existing keys that were requested in a different combination are not
766 // processed.
767 EXPECT_EQ(std::vector<bool>({true, true}),
768 CheckFormsInCache(form_split_cache_replayer, {form1, form3}));
769 EXPECT_EQ(std::vector<bool>({true}),
770 CheckFormsInCache(form_split_cache_replayer, {form1}));
771
772 // Not in the cache.
773 EXPECT_EQ(std::vector<bool>(),
774 CheckFormsInCache(form_split_cache_replayer, {form5}));
775 }
776 #endif // if defined(OS_LINUX) || defined(OS_CHROMEOS)
777 } // namespace
778 } // namespace test
779 } // namespace autofill
780