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