blob: 33f81c1c231ca76db4abdb6ff6423ae2b8f0a46f [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/autofill/automated_tests/cache_replayer.h"
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include "base/base64.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/json/json_writer.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/autofill/core/browser/proto/api_v1.pb.h"
#include "components/autofill/core/browser/proto/server.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/zlib/google/compression_utils.h"
namespace autofill {
namespace test {
namespace {
// Only run these tests on Linux because there are issues with other platforms.
// Testing on one platform gives enough confidence.
#if defined(OS_LINUX) || defined(OS_CHROMEOS)
using base::JSONWriter;
using base::Value;
// Request Response Pair for the API server
using RequestResponsePair =
std::pair<AutofillPageQueryRequest, AutofillQueryResponse>;
constexpr char kTestHTTPResponseHeader[] = "Fake HTTP Response Header";
constexpr char kHTTPBodySep[] = "\r\n\r\n";
// The host name of the autofill server.
constexpr char kHostname[] = "content-autofill.googleapis.com";
struct LightField {
uint32_t signature;
uint32_t prediction;
};
struct LightForm {
uint64_t signature;
std::vector<LightField> fields;
};
std::string CreateQueryUrl(const std::string& base64_encoded_query) {
constexpr char base_url[] =
"https://content-autofill.googleapis.com/v1/pages:get";
if (base64_encoded_query.empty())
return base_url;
return base::StrCat({base_url, "/", base64_encoded_query});
}
bool GetServerResponseForQuery(const ServerCacheReplayer& cache_replayer,
const AutofillPageQueryRequest& query,
std::string* http_text) {
return cache_replayer.GetApiServerResponseForQuery(query, http_text);
}
RequestResponsePair MakeQueryRequestResponsePair(
const std::vector<LightForm>& forms) {
AutofillPageQueryRequest query;
AutofillQueryResponse response;
for (const auto& form : forms) {
auto* query_form = query.add_forms();
query_form->set_signature(form.signature);
auto* response_form = response.add_form_suggestions();
for (const auto& field : form.fields) {
query_form->add_fields()->set_signature(field.signature);
auto* response_field = response_form->add_field_suggestions();
response_field->set_field_signature(field.signature);
response_field->set_primary_type_prediction(field.prediction);
response_field->add_predictions()->set_type(field.prediction);
}
}
return RequestResponsePair({std::move(query), std::move(response)});
}
// Returns a query request URL. If |query| is not empty, the corresponding
// query is encoded into the URL.
bool MakeQueryRequestURL(const base::Optional<AutofillPageQueryRequest>& query,
std::string* request_url) {
if (!query.has_value()) {
*request_url = CreateQueryUrl("");
return true;
}
std::string encoded_query;
std::string serialized_query;
if (!(*query).SerializeToString(&serialized_query)) {
VLOG(1) << "could not serialize Query proto";
return false;
}
base::Base64Encode(serialized_query, &encoded_query);
*request_url = CreateQueryUrl(encoded_query);
return true;
}
// Make HTTP request header given |url|.
inline std::string MakeRequestHeader(base::StringPiece url) {
return base::StrCat({"GET ", url, " ", "HTTP/1.1"});
}
// Makes string value for "SerializedRequest" json node that contains HTTP
// request content.
bool MakeSerializedRequest(const AutofillPageQueryRequest& query,
RequestType type,
std::string* serialized_request,
std::string* request_url) {
// Make body and query content for URL depending on the |type|.
std::string body;
base::Optional<AutofillPageQueryRequest> query_for_url;
if (type == RequestType::kQueryProtoGET) {
query_for_url = std::move(query);
} else {
std::string serialized_query;
std::string encoded_query;
query.SerializeToString(&serialized_query);
base::Base64Encode(serialized_query, &encoded_query);
// Wrap query payload in a request proto to interface with API Query method.
AutofillPageResourceQueryRequest request;
request.set_serialized_request(encoded_query);
request.SerializeToString(&body);
query_for_url = base::nullopt;
}
// Make header according to query content for URL.
std::string url;
if (!MakeQueryRequestURL(query_for_url, &url))
return false;
*request_url = url;
std::string header = MakeRequestHeader(url);
// Fill HTTP text.
std::string http_text =
base::JoinString(std::vector<std::string>{header, body}, kHTTPBodySep);
base::Base64Encode(http_text, serialized_request);
return true;
}
std::string MakeSerializedResponse(
const AutofillQueryResponse& query_response) {
std::string serialized_response;
query_response.SerializeToString(&serialized_response);
// The Api Environment expects the response body to be base64 encoded.
std::string tmp;
base::Base64Encode(serialized_response, &tmp);
serialized_response = tmp;
std::string compressed_query;
compression::GzipCompress(serialized_response, &compressed_query);
// TODO(vincb): Put a real header here.
std::string http_text = base::JoinString(
std::vector<std::string>{kTestHTTPResponseHeader, compressed_query},
kHTTPBodySep);
std::string encoded_http_text;
base::Base64Encode(http_text, &encoded_http_text);
return encoded_http_text;
}
// Write json node to file in text format.
bool WriteJSONNode(const base::FilePath& file_path, const base::Value& node) {
std::string json_text;
JSONWriter::WriteWithOptions(node, JSONWriter::Options::OPTIONS_PRETTY_PRINT,
&json_text);
std::string compressed_json_text;
if (!compression::GzipCompress(json_text, &compressed_json_text)) {
VLOG(1) << "Cannot compress json to gzip.";
return false;
}
if (!base::WriteFile(file_path, compressed_json_text)) {
VLOG(1) << "Could not write json at file: " << file_path;
return false;
}
return true;
}
// Write cache to file in json text format.
bool WriteJSON(const base::FilePath& file_path,
const std::vector<RequestResponsePair>& request_response_pairs,
RequestType request_type = RequestType::kQueryProtoPOST) {
// Make json list node that contains all query requests.
base::Value::DictStorage urls_dict;
for (const auto& request_response_pair : request_response_pairs) {
std::string serialized_request;
std::string url;
if (!MakeSerializedRequest(request_response_pair.first, request_type,
&serialized_request, &url)) {
return false;
}
Value::DictStorage request_response_node;
request_response_node.emplace("SerializedRequest",
std::move(serialized_request));
request_response_node.emplace(
"SerializedResponse",
MakeSerializedResponse(request_response_pair.second));
// Populate json dict node that contains Autofill Server requests per URL.
// This will construct an empty list for `url` if it didn't exist already.
auto& url_list = urls_dict.emplace(url, Value::Type::LIST).first->second;
url_list.Append(Value(std::move(request_response_node)));
}
// Make json dict node that contains requests per domain.
base::Value::DictStorage domains_dict;
domains_dict.emplace(kHostname, std::move(urls_dict));
// Make json root dict.
base::Value::DictStorage root_dict;
root_dict.emplace("Requests", std::move(domains_dict));
// Write content to JSON file.
return WriteJSONNode(file_path, Value(std::move(root_dict)));
}
TEST(AutofillCacheReplayerDeathTest,
ServerCacheReplayerConstructor_CrashesWhenNoDomainNode) {
// Make death test threadsafe.
testing::FLAGS_gtest_death_test_style = "threadsafe";
// Make writable file path.
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const base::FilePath file_path =
temp_dir.GetPath().AppendASCII("test_wpr_capture.json");
// JSON structure is not right.
const std::string invalid_json = "{\"NoDomainNode\": \"invalid_field\"}";
// Write json to file.
ASSERT_TRUE(base::WriteFile(file_path, invalid_json))
<< "there was an error when writing content to json file: " << file_path;
// Crash since json content is invalid.
ASSERT_DEATH_IF_SUPPORTED(
ServerCacheReplayer(file_path,
ServerCacheReplayer::kOptionFailOnInvalidJsonRecord),
".*");
}
TEST(AutofillCacheReplayerDeathTest,
ServerCacheReplayerConstructor_CrashesWhenNoQueryNodesAndFailOnEmpty) {
// Make death test threadsafe.
testing::FLAGS_gtest_death_test_style = "threadsafe";
// Make writable file path.
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const base::FilePath file_path =
temp_dir.GetPath().AppendASCII("test_wpr_capture.json");
// Make empty request/response pairs to write in cache.
std::vector<RequestResponsePair> request_response_pairs;
// Write cache to json and create replayer.
ASSERT_TRUE(WriteJSON(file_path, request_response_pairs));
// Crash since there are no Query nodes and set to fail on empty.
ASSERT_DEATH_IF_SUPPORTED(
ServerCacheReplayer(file_path,
ServerCacheReplayer::kOptionFailOnInvalidJsonRecord |
ServerCacheReplayer::kOptionFailOnEmpty),
".*");
}
// Test suite for GET Query death test.
class AutofillCacheReplayerGETQueryDeathTest
: public testing::TestWithParam<std::string> {};
TEST_P(
AutofillCacheReplayerGETQueryDeathTest,
ApiServerCacheReplayerConstructor_CrashesWhenInvalidRequestURLForGETQuery) {
// Parameterized death test for populating cache when keys that are obtained
// from the URL's query parameter are invalid.
// Make death test threadsafe.
testing::FLAGS_gtest_death_test_style = "threadsafe";
// Make writable file path.
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const base::FilePath file_path =
temp_dir.GetPath().AppendASCII("test_wpr_capture.json");
// Make JSON content.
// Make json list node that contains the problematic query request.
Value::DictStorage request_response_node;
// Put some textual content for HTTP request. Content does not matter because
// the Query content will be parsed from the URL that corresponds to the
// dictionary key.
request_response_node.emplace(
"SerializedRequest", base::StrCat({"GET ", CreateQueryUrl("1234").c_str(),
" HTTP/1.1\r\n\r\n"}));
request_response_node.emplace(
"SerializedResponse", MakeSerializedResponse(AutofillQueryResponse()));
base::Value::ListStorage url_list;
url_list.emplace_back(std::move(request_response_node));
// Populate json dict node that contains Autofill Server requests per URL.
base::Value::DictStorage urls_dict;
// The query parameter in the URL cannot be parsed to a proto because
// parameter value is in invalid format.
urls_dict.emplace(CreateQueryUrl(GetParam()), std::move(url_list));
// Make json dict node that contains requests per domain.
base::Value::DictStorage domains_dict;
domains_dict.emplace(kHostname, std::move(urls_dict));
// Make json root dict.
base::Value::DictStorage root_dict;
root_dict.emplace("Requests", std::move(domains_dict));
// Write content to JSON file.
ASSERT_TRUE(WriteJSONNode(file_path, Value(std::move(root_dict))));
// Make death assertion.
// Crash since request cannot be parsed to a proto.
ASSERT_DEATH_IF_SUPPORTED(
ServerCacheReplayer(file_path,
ServerCacheReplayer::kOptionFailOnInvalidJsonRecord),
".*");
}
INSTANTIATE_TEST_SUITE_P(
GetQueryParameterizedDeathTest,
AutofillCacheReplayerGETQueryDeathTest,
testing::Values( // Can be base-64 decoded, but not parsed to proto.
"1234",
// Cannot be base-64 decoded.
"^^^"));
TEST(AutofillCacheReplayerTest,
CanUseReplayerWhenNoCacheContentWithNotFailOnEmpty) {
// Make death test threadsafe.
testing::FLAGS_gtest_death_test_style = "threadsafe";
// Make writable file path.
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const base::FilePath file_path =
temp_dir.GetPath().AppendASCII("test_wpr_capture.json");
// Make empty request/response pairs to write in cache.
std::vector<RequestResponsePair> request_response_pairs;
// Write cache to json and create replayer.
ASSERT_TRUE(WriteJSON(file_path, request_response_pairs));
// Should not crash even if no cache because kOptionFailOnEmpty is not
// flipped.
ServerCacheReplayer cache_replayer(
file_path, ServerCacheReplayer::kOptionFailOnInvalidJsonRecord &
(ServerCacheReplayer::kOptionFailOnEmpty & 0));
// Should be able to read cache, which will give nothing.
std::string http_text;
AutofillPageQueryRequest query_with_no_match;
EXPECT_FALSE(GetServerResponseForQuery(cache_replayer, query_with_no_match,
&http_text));
}
template <typename U, typename V>
bool ProtobufsEqual(const U& u, const V& v) {
// Unfortunately, Chrome uses MessageLite, so we cannot use DebugString or the
// MessageDifferencer.
std::string u_serialized, v_serialized;
u.SerializeToString(&u_serialized);
v.SerializeToString(&v_serialized);
if (u_serialized != v_serialized) {
LOG(ERROR) << "Expected protobufs to be equal:\n" << u << "and:\n" << v;
LOG(ERROR) << "Note that this output is based on custom written string "
"serializers and the protobufs may be different in ways that "
"are not shown here.";
}
return u_serialized == v_serialized;
}
TEST(AutofillCacheReplayerTest, ProtobufConversion) {
AutofillRandomizedFormMetadata form_metadata;
form_metadata.mutable_id()->set_encoded_bits("foobar");
AutofillRandomizedFieldMetadata field_metadata;
field_metadata.mutable_id()->set_encoded_bits("foobarbaz");
// Form 1 (fields 101, 102), Form 2 (fields 201).
LegacyEnv::Query legacy_query;
{
legacy_query.set_client_version("DummyClient");
auto* form1 = legacy_query.add_form();
form1->set_signature(1);
form1->mutable_form_metadata()->CopyFrom(form_metadata);
auto* field101 = form1->add_field();
field101->set_signature(101);
field101->set_name("field_101");
field101->set_type("text");
field101->mutable_field_metadata()->CopyFrom(field_metadata);
auto* field102 = form1->add_field();
field102->set_signature(102);
field102->set_name("field_102");
field102->set_type("text");
auto* form2 = legacy_query.add_form();
form2->set_signature(2);
auto* field201 = form2->add_field();
field201->set_signature(201);
field201->set_name("field_201");
field201->set_type("text");
legacy_query.add_experiments(50);
legacy_query.add_experiments(51);
}
ApiEnv::Query api_query;
{
auto* form1 = api_query.add_forms();
form1->set_signature(1);
form1->mutable_metadata()->CopyFrom(form_metadata);
auto* field101 = form1->add_fields();
field101->set_signature(101);
field101->set_name("field_101");
field101->set_control_type("text");
field101->mutable_metadata()->CopyFrom(field_metadata);
auto* field102 = form1->add_fields();
field102->set_signature(102);
field102->set_name("field_102");
field102->set_control_type("text");
auto* form2 = api_query.add_forms();
form2->set_signature(2);
auto* field201 = form2->add_fields();
field201->set_signature(201);
field201->set_name("field_201");
field201->set_control_type("text");
api_query.add_experiments(50);
api_query.add_experiments(51);
}
LegacyEnv::Response legacy_response;
{
auto* field101 = legacy_response.add_field();
field101->set_overall_type_prediction(101);
auto* field101_prediction = field101->add_predictions();
field101_prediction->set_type(101);
field101_prediction->set_may_use_prefilled_placeholder(true);
field101_prediction = field101->add_predictions();
field101_prediction->set_type(1010);
field101_prediction->set_may_use_prefilled_placeholder(true);
// Todo: Password requirements
auto* field102 = legacy_response.add_field();
field102->set_overall_type_prediction(102);
auto* field102_prediction = field102->add_predictions();
field102_prediction->set_type(102);
field102_prediction->set_may_use_prefilled_placeholder(false);
auto* field201 = legacy_response.add_field();
field201->set_overall_type_prediction(201);
field201->add_predictions()->set_type(201);
}
ApiEnv::Response api_response;
{
auto* form1 = api_response.add_form_suggestions();
auto* field101 = form1->add_field_suggestions();
field101->set_field_signature(101);
field101->set_primary_type_prediction(101);
field101->add_predictions()->set_type(101);
field101->add_predictions()->set_type(1010);
field101->set_may_use_prefilled_placeholder(true);
// Todo: Password requirements
auto* field102 = form1->add_field_suggestions();
field102->set_field_signature(102);
field102->set_primary_type_prediction(102);
field102->add_predictions()->set_type(102);
field102->set_may_use_prefilled_placeholder(false);
auto* form2 = api_response.add_form_suggestions();
auto* field201 = form2->add_field_suggestions();
field201->set_field_signature(201);
field201->set_primary_type_prediction(201);
field201->add_predictions()->set_type(201);
}
// Verify equivalence of converted queries.
EXPECT_TRUE(ProtobufsEqual(api_query, api_query));
EXPECT_TRUE(ProtobufsEqual(api_query, ConvertQuery<ApiEnv>(api_query)));
EXPECT_TRUE(ProtobufsEqual(api_query, ConvertQuery<LegacyEnv>(legacy_query)));
// Verify equivalence of converted responses.
EXPECT_TRUE(ProtobufsEqual(api_response, api_response));
EXPECT_TRUE(ProtobufsEqual(api_response,
ConvertResponse<ApiEnv>(api_response, api_query)));
EXPECT_TRUE(ProtobufsEqual(
api_response, ConvertResponse<LegacyEnv>(legacy_response, legacy_query)));
}
// Test suite for Query response retrieval test.
class AutofillCacheReplayerGetResponseForQueryTest
: public testing::TestWithParam<RequestType> {};
TEST_P(AutofillCacheReplayerGetResponseForQueryTest,
FillsResponseWhenNoErrors) {
// Make writable file path.
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath file_path =
temp_dir.GetPath().AppendASCII("test_wpr_capture.json");
// Make request/response pairs to write in cache.
std::vector<RequestResponsePair> request_response_pairs;
{
LightForm form_to_add;
form_to_add.signature = 1234;
form_to_add.fields = {LightField{1234, 1}};
request_response_pairs.push_back(
MakeQueryRequestResponsePair({form_to_add}));
}
// Write cache to json.
ASSERT_TRUE(WriteJSON(file_path, request_response_pairs, GetParam()));
ServerCacheReplayer cache_replayer(
file_path, ServerCacheReplayer::kOptionFailOnInvalidJsonRecord &
ServerCacheReplayer::kOptionFailOnEmpty);
// Verify if we can get cached response.
std::string http_text_response;
ASSERT_TRUE(GetServerResponseForQuery(
cache_replayer, request_response_pairs[0].first, &http_text_response));
std::string body = SplitHTTP(http_text_response).second;
// The Api Environment expects the response to be base64 encoded.
std::string tmp;
ASSERT_TRUE(base::Base64Decode(body, &tmp));
body = tmp;
AutofillQueryResponse response_from_cache;
ASSERT_TRUE(response_from_cache.ParseFromString(body));
}
INSTANTIATE_TEST_SUITE_P(GetResponseForQueryParameterizeTest,
AutofillCacheReplayerGetResponseForQueryTest,
testing::Values(
// Read Query content from URL "q" param.
RequestType::kQueryProtoGET,
// Read Query content from HTTP body.
RequestType::kQueryProtoPOST));
TEST(AutofillCacheReplayerTest, GetResponseForQueryGivesFalseWhenNullptr) {
ServerCacheReplayer cache_replayer(ServerCache{{}});
EXPECT_FALSE(GetServerResponseForQuery(cache_replayer,
AutofillPageQueryRequest(), nullptr));
}
TEST(AutofillCacheReplayerTest, GetResponseForQueryGivesFalseWhenNoKeyMatch) {
// Make writable file path.
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath file_path =
temp_dir.GetPath().AppendASCII("test_wpr_capture.json");
// Make request/response pairs to write in cache.
std::vector<RequestResponsePair> request_response_pairs;
{
LightForm form_to_add;
form_to_add.signature = 1234;
form_to_add.fields = {LightField{1234, 1}};
request_response_pairs.push_back(
MakeQueryRequestResponsePair({form_to_add}));
}
// Write cache to json and create replayer.
ASSERT_TRUE(WriteJSON(file_path, request_response_pairs));
ServerCacheReplayer cache_replayer(
file_path, ServerCacheReplayer::kOptionFailOnInvalidJsonRecord &
ServerCacheReplayer::kOptionFailOnEmpty);
// Verify if we get false when there is no cache for the query.
std::string http_text;
AutofillPageQueryRequest query_with_no_match;
EXPECT_FALSE(GetServerResponseForQuery(cache_replayer, query_with_no_match,
&http_text));
}
TEST(AutofillCacheReplayerTest,
GetResponseForQueryGivesFalseWhenDecompressFailsBecauseInvalidHTTP) {
// Make query request and key.
LightForm form_to_add;
form_to_add.signature = 1234;
form_to_add.fields = {LightField{1234, 1}};
const AutofillPageQueryRequest query_request_for_key =
MakeQueryRequestResponsePair({form_to_add}).first;
const std::string key = GetKeyFromQuery<ApiEnv>(query_request_for_key);
const char invalid_http[] = "Dumb Nonsense That Doesn't Have a HTTP Header";
ServerCacheReplayer cache_replayer(ServerCache{{key, invalid_http}});
// Verify if we get false when invalid HTTP response to decompress.
std::string response_http_text;
EXPECT_FALSE(GetServerResponseForQuery(cache_replayer, query_request_for_key,
&response_http_text));
}
TEST(AutofillCacheReplayerTest,
GetResponseForQueryGivesTrueWhenDecompressSucceededBecauseEmptyBody) {
// Make query request and key.
LightForm form_to_add;
form_to_add.signature = 1234;
form_to_add.fields = {LightField{1234, 1}};
const AutofillPageQueryRequest query_request_for_key =
MakeQueryRequestResponsePair({form_to_add}).first;
const std::string key = GetKeyFromQuery<ApiEnv>(query_request_for_key);
const char http_without_body[] = "Test HTTP Header\r\n\r\n";
ServerCacheReplayer cache_replayer(ServerCache{{key, http_without_body}});
// Verify if we get true when no HTTP body.
std::string response_http_text;
EXPECT_TRUE(GetServerResponseForQuery(cache_replayer, query_request_for_key,
&response_http_text));
}
// Returns whether the forms in |response| and |forms| match. If both contain
// the same number of forms, a boolean is appended to the output for each form
// indicating whether the expectation and actual form matched. In case of
// gross mismatch, the function may return an empty vector.
std::vector<bool> DoFormsMatch(const AutofillQueryResponse& response,
const std::vector<LightForm>& forms) {
std::vector<bool> found;
for (int i = 0; i < std::min(static_cast<int>(forms.size()),
response.form_suggestions_size());
++i) {
const auto& expected_form = forms[i];
const auto& response_form = response.form_suggestions(i);
if (static_cast<int>(expected_form.fields.size()) !=
response_form.field_suggestions_size()) {
LOG(ERROR) << "Expected form " << i << " to have suggestions for "
<< expected_form.fields.size() << " fields but got "
<< response_form.field_suggestions_size();
found.push_back(false);
continue;
}
bool found_all_fields = true;
for (size_t j = 0; j < expected_form.fields.size(); ++j) {
const auto& expected_field = expected_form.fields[j];
if (expected_field.signature !=
response_form.field_suggestions(j).field_signature()) {
LOG(ERROR) << "Expected field " << j << " of form " << i
<< " to have signature " << expected_field.signature
<< " but got "
<< response_form.field_suggestions(j).field_signature();
found_all_fields = false;
}
if (expected_field.prediction !=
static_cast<unsigned int>(
response_form.field_suggestions(j).primary_type_prediction())) {
LOG(ERROR)
<< "Expected field " << j << " of form " << i
<< " to have primary type prediction " << expected_field.prediction
<< " but got "
<< response_form.field_suggestions(j).primary_type_prediction();
found_all_fields = false;
}
}
found.push_back(found_all_fields);
}
return found;
}
std::vector<bool> CheckFormsInCache(const ServerCacheReplayer& cache_replayer,
const std::vector<LightForm>& forms) {
RequestResponsePair request_response_pair =
MakeQueryRequestResponsePair(forms);
std::string http_text;
if (!GetServerResponseForQuery(cache_replayer, request_response_pair.first,
&http_text)) {
VLOG(1) << "Server did not respond to the query.";
return std::vector<bool>();
}
std::string body = SplitHTTP(http_text).second;
// The Api Environment expects the response to be base64 encoded.
std::string tmp;
if (!base::Base64Decode(body, &tmp)) {
LOG(ERROR) << "Unable to base64 decode contents" << body;
return std::vector<bool>();
}
body = tmp;
AutofillQueryResponse response;
CHECK(response.ParseFromString(body)) << body;
return DoFormsMatch(response, forms);
}
TEST(AutofillCacheReplayerTest, CrossEnvironmentIntegrationTest) {
// Make writable file path.
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath file_path =
temp_dir.GetPath().AppendASCII("test_wpr_capture.json");
LightForm form1;
form1.signature = 1111;
form1.fields = {LightField{1111, 1}, LightField{1112, 31},
LightField{1113, 33}};
LightForm form2;
form2.signature = 2222;
form2.fields = {LightField{2221, 2}};
LightForm form3;
form3.signature = 3333;
form3.fields = {LightField{3331, 3}};
LightForm form4;
form4.signature = 4444;
form4.fields = {LightField{4441, 4}};
LightForm form5;
form5.signature = 5555;
form5.fields = {LightField{5551, 42}};
// Make request/response pairs to write in cache.
std::vector<RequestResponsePair> request_response_pairs;
request_response_pairs.push_back(
MakeQueryRequestResponsePair({form1, form2}));
request_response_pairs.push_back(
MakeQueryRequestResponsePair({form3, form4}));
// Write cache to json and create replayer.
ASSERT_TRUE(WriteJSON(file_path, request_response_pairs));
ServerCacheReplayer cache_replayer(
file_path, ServerCacheReplayer::kOptionFailOnInvalidJsonRecord &
ServerCacheReplayer::kOptionFailOnEmpty);
std::string http_text;
// First, check the exact same key combos we sent properly respond
EXPECT_EQ(std::vector<bool>({true, true}),
CheckFormsInCache(cache_replayer, {form1, form2}));
EXPECT_EQ(std::vector<bool>({true, true}),
CheckFormsInCache(cache_replayer, {form3, form4}));
// Existing keys that were requested in a different combination are not
// processed.
EXPECT_EQ(std::vector<bool>(),
CheckFormsInCache(cache_replayer, {form1, form3}));
EXPECT_EQ(std::vector<bool>(), CheckFormsInCache(cache_replayer, {form1}));
// Not in the cache.
EXPECT_EQ(std::vector<bool>(), CheckFormsInCache(cache_replayer, {form5}));
// Now, load the same thing into the cache replayer with
// ServerCacheReplayer::kOptionSplitRequestsByForm set and expect matches
// for all combos
ServerCacheReplayer form_split_cache_replayer(
file_path, ServerCacheReplayer::kOptionSplitRequestsByForm);
// First, check the exact same key combos we sent properly respond
EXPECT_EQ(std::vector<bool>({true, true}),
CheckFormsInCache(form_split_cache_replayer, {form1, form2}));
EXPECT_EQ(std::vector<bool>({true, true}),
CheckFormsInCache(form_split_cache_replayer, {form3, form4}));
// Existing keys that were requested in a different combination are not
// processed.
EXPECT_EQ(std::vector<bool>({true, true}),
CheckFormsInCache(form_split_cache_replayer, {form1, form3}));
EXPECT_EQ(std::vector<bool>({true}),
CheckFormsInCache(form_split_cache_replayer, {form1}));
// Not in the cache.
EXPECT_EQ(std::vector<bool>(),
CheckFormsInCache(form_split_cache_replayer, {form5}));
}
#endif // if defined(OS_LINUX) || defined(OS_CHROMEOS)
} // namespace
} // namespace test
} // namespace autofill