blob: e0e51ca179ab6cc76049d7f352603913cb5b705f [file] [log] [blame]
// Copyright 2017 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 "net/reporting/reporting_header_parser.h"
#include <sstream>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/json/json_reader.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/time/time.h"
#include "base/values.h"
#include "net/reporting/mock_persistent_reporting_store.h"
#include "net/reporting/reporting_cache.h"
#include "net/reporting/reporting_endpoint.h"
#include "net/reporting/reporting_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace net {
namespace {
using CommandType = MockPersistentReportingStore::Command::Type;
// This test is parametrized on a boolean that represents whether to use a
// MockPersistentReportingStore.
class ReportingHeaderParserTest : public ReportingTestBase,
public ::testing::WithParamInterface<bool> {
protected:
ReportingHeaderParserTest() : ReportingTestBase() {
ReportingPolicy policy;
policy.max_endpoints_per_origin = 10;
policy.max_endpoint_count = 20;
UsePolicy(policy);
if (GetParam())
store_ = std::make_unique<MockPersistentReportingStore>();
else
store_ = nullptr;
UseStore(store_.get());
}
~ReportingHeaderParserTest() override = default;
void SetUp() override {
// All ReportingCache methods assume that the store has been initialized.
if (mock_store()) {
mock_store()->LoadReportingClients(
base::BindOnce(&ReportingCache::AddClientsLoadedFromStore,
base::Unretained(cache())));
mock_store()->FinishLoading(true);
}
}
MockPersistentReportingStore* mock_store() { return store_.get(); }
ReportingEndpointGroup MakeEndpointGroup(
const std::string& name,
const std::vector<ReportingEndpoint::EndpointInfo>& endpoints,
OriginSubdomains include_subdomains = OriginSubdomains::DEFAULT,
base::TimeDelta ttl = base::TimeDelta::FromDays(1),
url::Origin origin = url::Origin()) {
ReportingEndpointGroupKey group_key(NetworkIsolationKey() /* unused */,
url::Origin() /* unused */, name);
ReportingEndpointGroup group;
group.group_key = group_key;
group.include_subdomains = include_subdomains;
group.ttl = ttl;
group.endpoints = std::move(endpoints);
return group;
}
// Constructs a string which would represent a single group in a Report-To
// header. If |group_name| is an empty string, the group name will be omitted
// (and thus default to "default" when parsed). Setting |omit_defaults| omits
// the priority, weight, and include_subdomains fields if they are default,
// otherwise they are spelled out fully.
std::string ConstructHeaderGroupString(const ReportingEndpointGroup& group,
bool omit_defaults = true) {
std::ostringstream s;
s << "{ ";
if (!group.group_key.group_name.empty()) {
s << "\"group\": \"" << group.group_key.group_name << "\", ";
}
s << "\"max_age\": " << group.ttl.InSeconds() << ", ";
if (group.include_subdomains != OriginSubdomains::DEFAULT) {
s << "\"include_subdomains\": true, ";
} else if (!omit_defaults) {
s << "\"include_subdomains\": false, ";
}
s << "\"endpoints\": [";
for (const ReportingEndpoint::EndpointInfo& endpoint_info :
group.endpoints) {
s << "{ ";
s << "\"url\": \"" << endpoint_info.url.spec() << "\"";
if (!omit_defaults ||
endpoint_info.priority !=
ReportingEndpoint::EndpointInfo::kDefaultPriority) {
s << ", \"priority\": " << endpoint_info.priority;
}
if (!omit_defaults ||
endpoint_info.weight !=
ReportingEndpoint::EndpointInfo::kDefaultWeight) {
s << ", \"weight\": " << endpoint_info.weight;
}
s << " }, ";
}
if (!group.endpoints.empty())
s.seekp(-2, s.cur); // Overwrite trailing comma and space.
s << "]";
s << " }";
return s.str();
}
void ParseHeader(const GURL& url, const std::string& json) {
std::unique_ptr<base::Value> value =
base::JSONReader::ReadDeprecated("[" + json + "]");
if (value)
ReportingHeaderParser::ParseHeader(context(), url, std::move(value));
}
const GURL kUrl_ = GURL("https://origin.test/path");
const url::Origin kOrigin_ =
url::Origin::Create(GURL("https://origin.test/"));
const GURL kUrl2_ = GURL("https://origin2.test/path");
const url::Origin kOrigin2_ =
url::Origin::Create(GURL("https://origin2.test/"));
const GURL kUrlEtld_ = GURL("https://co.uk/foo.html/");
const url::Origin kOriginEtld_ = url::Origin::Create(kUrlEtld_);
const GURL kEndpoint_ = GURL("https://endpoint.test/");
const GURL kEndpoint2_ = GURL("https://endpoint2.test/");
const GURL kEndpoint3_ = GURL("https://endpoint3.test/");
const std::string kGroup_ = "group";
const std::string kGroup2_ = "group2";
const std::string kType_ = "type";
const ReportingEndpointGroupKey kGroupKey_ =
ReportingEndpointGroupKey(NetworkIsolationKey(), kOrigin_, kGroup_);
const ReportingEndpointGroupKey kGroupKey2_ =
ReportingEndpointGroupKey(NetworkIsolationKey(), kOrigin_, kGroup2_);
private:
std::unique_ptr<MockPersistentReportingStore> store_;
};
// TODO(juliatuttle): Ideally these tests should be expecting that JSON parsing
// (and therefore header parsing) may happen asynchronously, but the entire
// pipeline is also tested by NetworkErrorLoggingEndToEndTest.
TEST_P(ReportingHeaderParserTest, Invalid) {
static const struct {
const char* header_value;
const char* description;
} kInvalidHeaderTestCases[] = {
{"{\"max_age\":1, \"endpoints\": [{}]}", "missing url"},
{"{\"max_age\":1, \"endpoints\": [{\"url\":0}]}", "non-string url"},
{"{\"max_age\":1, \"endpoints\": [{\"url\":\"http://insecure/\"}]}",
"insecure url"},
{"{\"endpoints\": [{\"url\":\"https://endpoint/\"}]}", "missing max_age"},
{"{\"max_age\":\"\", \"endpoints\": [{\"url\":\"https://endpoint/\"}]}",
"non-integer max_age"},
{"{\"max_age\":-1, \"endpoints\": [{\"url\":\"https://endpoint/\"}]}",
"negative max_age"},
{"{\"max_age\":1, \"group\":0, "
"\"endpoints\": [{\"url\":\"https://endpoint/\"}]}",
"non-string group"},
// Note that a non-boolean include_subdomains field is *not* invalid, per
// the spec.
// Priority should be a nonnegative integer.
{"{\"max_age\":1, "
"\"endpoints\": [{\"url\":\"https://endpoint/\",\"priority\":\"\"}]}",
"non-integer priority"},
{"{\"max_age\":1, "
"\"endpoints\": [{\"url\":\"https://endpoint/\",\"priority\":-1}]}",
"negative priority"},
// Weight should be a non-negative integer.
{"{\"max_age\":1, "
"\"endpoints\": [{\"url\":\"https://endpoint/\",\"weight\":\"\"}]}",
"non-integer weight"},
{"{\"max_age\":1, "
"\"endpoints\": [{\"url\":\"https://endpoint/\",\"weight\":-1}]}",
"negative weight"},
{"[{\"max_age\":1, \"endpoints\": [{\"url\":\"https://a/\"}]},"
"{\"max_age\":1, \"endpoints\": [{\"url\":\"https://b/\"}]}]",
"wrapped in list"}};
for (size_t i = 0; i < base::size(kInvalidHeaderTestCases); ++i) {
auto& test_case = kInvalidHeaderTestCases[i];
ParseHeader(kUrl_, test_case.header_value);
EXPECT_EQ(0u, cache()->GetEndpointCount())
<< "Invalid Report-To header (" << test_case.description << ": \""
<< test_case.header_value << "\") parsed as valid.";
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(0, mock_store()->StoredEndpointsCount());
EXPECT_EQ(0, mock_store()->StoredEndpointGroupsCount());
}
}
}
TEST_P(ReportingHeaderParserTest, Basic) {
std::vector<ReportingEndpoint::EndpointInfo> endpoints = {{kEndpoint_}};
std::string header =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints));
ParseHeader(kUrl_, header);
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT));
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
EXPECT_EQ(1u, cache()->GetEndpointCount());
ReportingEndpoint endpoint = FindEndpointInCache(kGroupKey_, kEndpoint_);
ASSERT_TRUE(endpoint);
EXPECT_EQ(kOrigin_, endpoint.group_key.origin);
EXPECT_EQ(kGroup_, endpoint.group_key.group_name);
EXPECT_EQ(kEndpoint_, endpoint.info.url);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultPriority,
endpoint.info.priority);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultWeight,
endpoint.info.weight);
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(1, mock_store()->StoredEndpointsCount());
EXPECT_EQ(1, mock_store()->StoredEndpointGroupsCount());
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
}
TEST_P(ReportingHeaderParserTest, OmittedGroupName) {
ReportingEndpointGroupKey kGroupKey(NetworkIsolationKey(), kOrigin_,
"default");
std::vector<ReportingEndpoint::EndpointInfo> endpoints = {{kEndpoint_}};
std::string header =
ConstructHeaderGroupString(MakeEndpointGroup(std::string(), endpoints));
ParseHeader(kUrl_, header);
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(EndpointGroupExistsInCache(kGroupKey, OriginSubdomains::DEFAULT));
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
EXPECT_EQ(1u, cache()->GetEndpointCount());
ReportingEndpoint endpoint = FindEndpointInCache(kGroupKey, kEndpoint_);
ASSERT_TRUE(endpoint);
EXPECT_EQ(kOrigin_, endpoint.group_key.origin);
EXPECT_EQ("default", endpoint.group_key.group_name);
EXPECT_EQ(kEndpoint_, endpoint.info.url);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultPriority,
endpoint.info.priority);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultWeight,
endpoint.info.weight);
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(1, mock_store()->StoredEndpointsCount());
EXPECT_EQ(1, mock_store()->StoredEndpointGroupsCount());
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
}
TEST_P(ReportingHeaderParserTest, IncludeSubdomainsTrue) {
std::vector<ReportingEndpoint::EndpointInfo> endpoints = {{kEndpoint_}};
std::string header = ConstructHeaderGroupString(
MakeEndpointGroup(kGroup_, endpoints, OriginSubdomains::INCLUDE));
ParseHeader(kUrl_, header);
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::INCLUDE));
EXPECT_EQ(1u, cache()->GetEndpointCount());
EXPECT_TRUE(EndpointExistsInCache(kGroupKey_, kEndpoint_));
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(1, mock_store()->StoredEndpointsCount());
EXPECT_EQ(1, mock_store()->StoredEndpointGroupsCount());
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
}
TEST_P(ReportingHeaderParserTest, IncludeSubdomainsFalse) {
std::vector<ReportingEndpoint::EndpointInfo> endpoints = {{kEndpoint_}};
std::string header = ConstructHeaderGroupString(
MakeEndpointGroup(kGroup_, endpoints, OriginSubdomains::EXCLUDE),
false /* omit_defaults */);
ParseHeader(kUrl_, header);
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::EXCLUDE));
EXPECT_EQ(1u, cache()->GetEndpointCount());
EXPECT_TRUE(EndpointExistsInCache(kGroupKey_, kEndpoint_));
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(1, mock_store()->StoredEndpointsCount());
EXPECT_EQ(1, mock_store()->StoredEndpointGroupsCount());
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
}
TEST_P(ReportingHeaderParserTest, IncludeSubdomainsEtldRejected) {
ReportingEndpointGroupKey kGroupKey(NetworkIsolationKey(), kOriginEtld_,
kGroup_);
std::vector<ReportingEndpoint::EndpointInfo> endpoints = {{kEndpoint_}};
std::string header = ConstructHeaderGroupString(
MakeEndpointGroup(kGroup_, endpoints, OriginSubdomains::INCLUDE));
ParseHeader(kUrlEtld_, header);
EXPECT_EQ(0u, cache()->GetEndpointGroupCountForTesting());
EXPECT_FALSE(
EndpointGroupExistsInCache(kGroupKey, OriginSubdomains::INCLUDE));
EXPECT_EQ(0u, cache()->GetEndpointCount());
EXPECT_FALSE(EndpointExistsInCache(kGroupKey, kEndpoint_));
}
TEST_P(ReportingHeaderParserTest, NonIncludeSubdomainsEtldAccepted) {
ReportingEndpointGroupKey kGroupKey(NetworkIsolationKey(), kOriginEtld_,
kGroup_);
std::vector<ReportingEndpoint::EndpointInfo> endpoints = {{kEndpoint_}};
std::string header = ConstructHeaderGroupString(
MakeEndpointGroup(kGroup_, endpoints, OriginSubdomains::EXCLUDE));
ParseHeader(kUrlEtld_, header);
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(EndpointGroupExistsInCache(kGroupKey, OriginSubdomains::EXCLUDE));
EXPECT_EQ(1u, cache()->GetEndpointCount());
EXPECT_TRUE(EndpointExistsInCache(kGroupKey, kEndpoint_));
}
TEST_P(ReportingHeaderParserTest, IncludeSubdomainsNotBoolean) {
std::string header =
"{\"group\": \"" + kGroup_ +
"\", "
"\"max_age\":86400, \"include_subdomains\": \"NotABoolean\", "
"\"endpoints\": [{\"url\":\"" +
kEndpoint_.spec() + "\"}]}";
ParseHeader(kUrl_, header);
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT));
EXPECT_EQ(1u, cache()->GetEndpointCount());
EXPECT_TRUE(EndpointExistsInCache(kGroupKey_, kEndpoint_));
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(1, mock_store()->StoredEndpointsCount());
EXPECT_EQ(1, mock_store()->StoredEndpointGroupsCount());
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
}
TEST_P(ReportingHeaderParserTest, NonDefaultPriority) {
const int kNonDefaultPriority = 10;
std::vector<ReportingEndpoint::EndpointInfo> endpoints = {
{kEndpoint_, kNonDefaultPriority}};
std::string header =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints));
ParseHeader(kUrl_, header);
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT));
EXPECT_EQ(1u, cache()->GetEndpointCount());
ReportingEndpoint endpoint = FindEndpointInCache(kGroupKey_, kEndpoint_);
ASSERT_TRUE(endpoint);
EXPECT_EQ(kNonDefaultPriority, endpoint.info.priority);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultWeight,
endpoint.info.weight);
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(1, mock_store()->StoredEndpointsCount());
EXPECT_EQ(1, mock_store()->StoredEndpointGroupsCount());
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
}
TEST_P(ReportingHeaderParserTest, NonDefaultWeight) {
const int kNonDefaultWeight = 10;
std::vector<ReportingEndpoint::EndpointInfo> endpoints = {
{kEndpoint_, ReportingEndpoint::EndpointInfo::kDefaultPriority,
kNonDefaultWeight}};
std::string header =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints));
ParseHeader(kUrl_, header);
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT));
EXPECT_EQ(1u, cache()->GetEndpointCount());
ReportingEndpoint endpoint = FindEndpointInCache(kGroupKey_, kEndpoint_);
ASSERT_TRUE(endpoint);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultPriority,
endpoint.info.priority);
EXPECT_EQ(kNonDefaultWeight, endpoint.info.weight);
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(1, mock_store()->StoredEndpointsCount());
EXPECT_EQ(1, mock_store()->StoredEndpointGroupsCount());
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
}
TEST_P(ReportingHeaderParserTest, MaxAge) {
const int kMaxAgeSecs = 100;
base::TimeDelta ttl = base::TimeDelta::FromSeconds(kMaxAgeSecs);
base::Time expires = clock()->Now() + ttl;
std::vector<ReportingEndpoint::EndpointInfo> endpoints = {{kEndpoint_}};
std::string header = ConstructHeaderGroupString(
MakeEndpointGroup(kGroup_, endpoints, OriginSubdomains::DEFAULT, ttl));
ParseHeader(kUrl_, header);
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT,
expires));
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(1, mock_store()->StoredEndpointsCount());
EXPECT_EQ(1, mock_store()->StoredEndpointGroupsCount());
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
}
TEST_P(ReportingHeaderParserTest, MultipleEndpointsSameGroup) {
std::vector<ReportingEndpoint::EndpointInfo> endpoints = {{kEndpoint_},
{kEndpoint2_}};
std::string header =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints));
ParseHeader(kUrl_, header);
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT));
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
EXPECT_EQ(2u, cache()->GetEndpointCount());
ReportingEndpoint endpoint = FindEndpointInCache(kGroupKey_, kEndpoint_);
ASSERT_TRUE(endpoint);
EXPECT_EQ(kOrigin_, endpoint.group_key.origin);
EXPECT_EQ(kGroup_, endpoint.group_key.group_name);
EXPECT_EQ(kEndpoint_, endpoint.info.url);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultPriority,
endpoint.info.priority);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultWeight,
endpoint.info.weight);
ReportingEndpoint endpoint2 = FindEndpointInCache(kGroupKey_, kEndpoint2_);
ASSERT_TRUE(endpoint2);
EXPECT_EQ(kOrigin_, endpoint2.group_key.origin);
EXPECT_EQ(kGroup_, endpoint2.group_key.group_name);
EXPECT_EQ(kEndpoint2_, endpoint2.info.url);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultPriority,
endpoint2.info.priority);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultWeight,
endpoint2.info.weight);
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(2, mock_store()->StoredEndpointsCount());
EXPECT_EQ(1, mock_store()->StoredEndpointGroupsCount());
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint2_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
}
TEST_P(ReportingHeaderParserTest, MultipleEndpointsDifferentGroups) {
ReportingEndpointGroupKey kOtherGroupKey(NetworkIsolationKey(), kOrigin_,
kGroup2_);
std::vector<ReportingEndpoint::EndpointInfo> endpoints1 = {{kEndpoint_}};
std::vector<ReportingEndpoint::EndpointInfo> endpoints2 = {{kEndpoint_}};
std::string header =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints1)) +
", " +
ConstructHeaderGroupString(MakeEndpointGroup(kGroup2_, endpoints2));
ParseHeader(kUrl_, header);
EXPECT_EQ(2u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT));
EXPECT_TRUE(
EndpointGroupExistsInCache(kOtherGroupKey, OriginSubdomains::DEFAULT));
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
EXPECT_EQ(2u, cache()->GetEndpointCount());
ReportingEndpoint endpoint = FindEndpointInCache(kGroupKey_, kEndpoint_);
ASSERT_TRUE(endpoint);
EXPECT_EQ(kOrigin_, endpoint.group_key.origin);
EXPECT_EQ(kGroup_, endpoint.group_key.group_name);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultPriority,
endpoint.info.priority);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultWeight,
endpoint.info.weight);
ReportingEndpoint endpoint2 = FindEndpointInCache(kOtherGroupKey, kEndpoint_);
ASSERT_TRUE(endpoint2);
EXPECT_EQ(kOrigin_, endpoint2.group_key.origin);
EXPECT_EQ(kGroup2_, endpoint2.group_key.group_name);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultPriority,
endpoint2.info.priority);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultWeight,
endpoint2.info.weight);
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(2, mock_store()->StoredEndpointsCount());
EXPECT_EQ(2, mock_store()->StoredEndpointGroupsCount());
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kOtherGroupKey, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kOtherGroupKey);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
}
TEST_P(ReportingHeaderParserTest, MultipleHeadersFromDifferentOrigins) {
// First origin sets a header with two endpoints in the same group.
std::vector<ReportingEndpoint::EndpointInfo> endpoints1 = {{kEndpoint_},
{kEndpoint2_}};
std::string header1 =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints1));
ParseHeader(kUrl_, header1);
// Second origin has two endpoint groups.
std::vector<ReportingEndpoint::EndpointInfo> endpoints2 = {{kEndpoint_}};
std::vector<ReportingEndpoint::EndpointInfo> endpoints3 = {{kEndpoint2_}};
std::string header2 =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints2)) +
", " +
ConstructHeaderGroupString(MakeEndpointGroup(kGroup2_, endpoints3));
ParseHeader(kUrl2_, header2);
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin2_));
ReportingEndpointGroupKey kGroupKey1(NetworkIsolationKey(), kOrigin2_,
kGroup_);
ReportingEndpointGroupKey kGroupKey2(NetworkIsolationKey(), kOrigin2_,
kGroup2_);
EXPECT_EQ(3u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT));
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey1, OriginSubdomains::DEFAULT));
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey2, OriginSubdomains::DEFAULT));
EXPECT_EQ(4u, cache()->GetEndpointCount());
EXPECT_TRUE(FindEndpointInCache(kGroupKey_, kEndpoint_));
EXPECT_TRUE(FindEndpointInCache(kGroupKey_, kEndpoint2_));
EXPECT_TRUE(FindEndpointInCache(kGroupKey1, kEndpoint_));
EXPECT_TRUE(FindEndpointInCache(kGroupKey2, kEndpoint2_));
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(4, mock_store()->StoredEndpointsCount());
EXPECT_EQ(3, mock_store()->StoredEndpointGroupsCount());
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint2_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey1, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey2, kEndpoint2_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey1);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey2);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
}
TEST_P(ReportingHeaderParserTest,
HeaderErroneouslyContainsMultipleGroupsOfSameName) {
std::vector<ReportingEndpoint::EndpointInfo> endpoints1 = {{kEndpoint_}};
std::vector<ReportingEndpoint::EndpointInfo> endpoints2 = {{kEndpoint2_}};
std::string header =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints1)) +
", " + ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints2));
ParseHeader(kUrl_, header);
// Result is as if they set the two groups with the same name as one group.
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT));
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
EXPECT_EQ(2u, cache()->GetEndpointCount());
ReportingEndpoint endpoint = FindEndpointInCache(kGroupKey_, kEndpoint_);
ASSERT_TRUE(endpoint);
EXPECT_EQ(kOrigin_, endpoint.group_key.origin);
EXPECT_EQ(kGroup_, endpoint.group_key.group_name);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultPriority,
endpoint.info.priority);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultWeight,
endpoint.info.weight);
ReportingEndpoint endpoint2 = FindEndpointInCache(kGroupKey_, kEndpoint2_);
ASSERT_TRUE(endpoint2);
EXPECT_EQ(kOrigin_, endpoint2.group_key.origin);
EXPECT_EQ(kGroup_, endpoint2.group_key.group_name);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultPriority,
endpoint2.info.priority);
EXPECT_EQ(ReportingEndpoint::EndpointInfo::kDefaultWeight,
endpoint2.info.weight);
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(2, mock_store()->StoredEndpointsCount());
EXPECT_EQ(1, mock_store()->StoredEndpointGroupsCount());
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint2_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
}
TEST_P(ReportingHeaderParserTest,
HeaderErroneouslyContainsGroupsWithRedundantEndpoints) {
std::vector<ReportingEndpoint::EndpointInfo> endpoints = {{kEndpoint_},
{kEndpoint_}};
std::string header =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints));
ParseHeader(kUrl_, header);
// We should dedupe the identical endpoint URLs.
EXPECT_EQ(1u, cache()->GetEndpointCount());
ASSERT_TRUE(FindEndpointInCache(kGroupKey_, kEndpoint_));
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT));
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
}
TEST_P(ReportingHeaderParserTest,
HeaderErroneouslyContainsMultipleGroupsOfSameNameAndEndpoints) {
std::vector<ReportingEndpoint::EndpointInfo> endpoints = {{kEndpoint_}};
std::string header =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints)) + ", " +
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints));
ParseHeader(kUrl_, header);
// We should dedupe the identical endpoint URLs, even when they're in
// different headers.
EXPECT_EQ(1u, cache()->GetEndpointCount());
ASSERT_TRUE(FindEndpointInCache(kGroupKey_, kEndpoint_));
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT));
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
}
TEST_P(ReportingHeaderParserTest,
HeaderErroneouslyContainsGroupsOfSameNameAndOverlappingEndpoints) {
std::vector<ReportingEndpoint::EndpointInfo> endpoints1 = {{kEndpoint_},
{kEndpoint2_}};
std::vector<ReportingEndpoint::EndpointInfo> endpoints2 = {{kEndpoint_},
{kEndpoint3_}};
std::string header =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints1)) +
", " + ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints2));
ParseHeader(kUrl_, header);
// We should dedupe the identical endpoint URLs, even when they're in
// different headers.
EXPECT_EQ(3u, cache()->GetEndpointCount());
ASSERT_TRUE(FindEndpointInCache(kGroupKey_, kEndpoint_));
ASSERT_TRUE(FindEndpointInCache(kGroupKey_, kEndpoint2_));
ASSERT_TRUE(FindEndpointInCache(kGroupKey_, kEndpoint3_));
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT));
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
}
TEST_P(ReportingHeaderParserTest, OverwriteOldHeader) {
// First, the origin sets a header with two endpoints in the same group.
std::vector<ReportingEndpoint::EndpointInfo> endpoints1 = {
{kEndpoint_, 10 /* priority */}, {kEndpoint2_}};
std::string header1 =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints1));
ParseHeader(kUrl_, header1);
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT));
EXPECT_EQ(2u, cache()->GetEndpointCount());
EXPECT_TRUE(FindEndpointInCache(kGroupKey_, kEndpoint_));
EXPECT_TRUE(FindEndpointInCache(kGroupKey_, kEndpoint2_));
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(2,
mock_store()->CountCommands(CommandType::ADD_REPORTING_ENDPOINT));
EXPECT_EQ(1, mock_store()->CountCommands(
CommandType::ADD_REPORTING_ENDPOINT_GROUP));
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint2_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
// Second header from the same origin should overwrite the previous one.
std::vector<ReportingEndpoint::EndpointInfo> endpoints2 = {
// This endpoint should update the priority of the existing one.
{kEndpoint_, 20 /* priority */}};
// The second endpoint in this group will be deleted.
// This group is new.
std::vector<ReportingEndpoint::EndpointInfo> endpoints3 = {{kEndpoint2_}};
std::string header2 =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints2)) +
", " +
ConstructHeaderGroupString(MakeEndpointGroup(kGroup2_, endpoints3));
ParseHeader(kUrl_, header2);
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT));
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey2_, OriginSubdomains::DEFAULT));
EXPECT_EQ(2u, cache()->GetEndpointCount());
EXPECT_TRUE(FindEndpointInCache(kGroupKey_, kEndpoint_));
EXPECT_EQ(20, FindEndpointInCache(kGroupKey_, kEndpoint_).info.priority);
EXPECT_FALSE(FindEndpointInCache(kGroupKey_, kEndpoint2_));
EXPECT_TRUE(FindEndpointInCache(kGroupKey2_, kEndpoint2_));
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(2 + 1,
mock_store()->CountCommands(CommandType::ADD_REPORTING_ENDPOINT));
EXPECT_EQ(1 + 1, mock_store()->CountCommands(
CommandType::ADD_REPORTING_ENDPOINT_GROUP));
EXPECT_EQ(
1, mock_store()->CountCommands(CommandType::DELETE_REPORTING_ENDPOINT));
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey2_, kEndpoint2_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey2_);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint2_);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
}
TEST_P(ReportingHeaderParserTest, OverwriteOldHeaderWithCompletelyNew) {
ReportingEndpointGroupKey kGroupKey1(NetworkIsolationKey(), kOrigin_, "1");
ReportingEndpointGroupKey kGroupKey2(NetworkIsolationKey(), kOrigin_, "2");
ReportingEndpointGroupKey kGroupKey3(NetworkIsolationKey(), kOrigin_, "3");
ReportingEndpointGroupKey kGroupKey4(NetworkIsolationKey(), kOrigin_, "4");
ReportingEndpointGroupKey kGroupKey5(NetworkIsolationKey(), kOrigin_, "5");
std::vector<ReportingEndpoint::EndpointInfo> endpoints1_1 = {{MakeURL(10)},
{MakeURL(11)}};
std::vector<ReportingEndpoint::EndpointInfo> endpoints2_1 = {{MakeURL(20)},
{MakeURL(21)}};
std::vector<ReportingEndpoint::EndpointInfo> endpoints3_1 = {{MakeURL(30)},
{MakeURL(31)}};
std::string header1 =
ConstructHeaderGroupString(MakeEndpointGroup("1", endpoints1_1)) + ", " +
ConstructHeaderGroupString(MakeEndpointGroup("2", endpoints2_1)) + ", " +
ConstructHeaderGroupString(MakeEndpointGroup("3", endpoints3_1));
ParseHeader(kUrl_, header1);
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
EXPECT_EQ(3u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey1, OriginSubdomains::DEFAULT));
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey2, OriginSubdomains::DEFAULT));
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey3, OriginSubdomains::DEFAULT));
EXPECT_EQ(6u, cache()->GetEndpointCount());
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(6,
mock_store()->CountCommands(CommandType::ADD_REPORTING_ENDPOINT));
EXPECT_EQ(3, mock_store()->CountCommands(
CommandType::ADD_REPORTING_ENDPOINT_GROUP));
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey1, endpoints1_1[0].url);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey1, endpoints1_1[1].url);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey2, endpoints2_1[0].url);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey2, endpoints2_1[1].url);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey3, endpoints3_1[0].url);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey3, endpoints3_1[1].url);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey1);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey2);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey3);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
// Replace endpoints in each group with completely new endpoints.
std::vector<ReportingEndpoint::EndpointInfo> endpoints1_2 = {{MakeURL(12)}};
std::vector<ReportingEndpoint::EndpointInfo> endpoints2_2 = {{MakeURL(22)}};
std::vector<ReportingEndpoint::EndpointInfo> endpoints3_2 = {{MakeURL(32)}};
std::string header2 =
ConstructHeaderGroupString(MakeEndpointGroup("1", endpoints1_2)) + ", " +
ConstructHeaderGroupString(MakeEndpointGroup("2", endpoints2_2)) + ", " +
ConstructHeaderGroupString(MakeEndpointGroup("3", endpoints3_2));
ParseHeader(kUrl_, header2);
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
EXPECT_EQ(3u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey1, OriginSubdomains::DEFAULT));
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey2, OriginSubdomains::DEFAULT));
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey3, OriginSubdomains::DEFAULT));
EXPECT_EQ(3u, cache()->GetEndpointCount());
EXPECT_TRUE(FindEndpointInCache(kGroupKey1, MakeURL(12)));
EXPECT_FALSE(FindEndpointInCache(kGroupKey1, MakeURL(10)));
EXPECT_FALSE(FindEndpointInCache(kGroupKey1, MakeURL(11)));
EXPECT_TRUE(FindEndpointInCache(kGroupKey2, MakeURL(22)));
EXPECT_FALSE(FindEndpointInCache(kGroupKey2, MakeURL(20)));
EXPECT_FALSE(FindEndpointInCache(kGroupKey2, MakeURL(21)));
EXPECT_TRUE(FindEndpointInCache(kGroupKey3, MakeURL(32)));
EXPECT_FALSE(FindEndpointInCache(kGroupKey3, MakeURL(30)));
EXPECT_FALSE(FindEndpointInCache(kGroupKey3, MakeURL(31)));
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(6 + 3,
mock_store()->CountCommands(CommandType::ADD_REPORTING_ENDPOINT));
EXPECT_EQ(3, mock_store()->CountCommands(
CommandType::ADD_REPORTING_ENDPOINT_GROUP));
EXPECT_EQ(
6, mock_store()->CountCommands(CommandType::DELETE_REPORTING_ENDPOINT));
EXPECT_EQ(0, mock_store()->CountCommands(
CommandType::DELETE_REPORTING_ENDPOINT_GROUP));
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey1, endpoints1_2[0].url);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey2, endpoints2_2[0].url);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey3, endpoints3_2[0].url);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey1, endpoints1_1[0].url);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey1, endpoints1_1[1].url);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey2, endpoints2_1[0].url);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey2, endpoints2_1[1].url);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey3, endpoints3_1[0].url);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey3, endpoints3_1[1].url);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
// Replace all the groups with completely new groups.
std::vector<ReportingEndpoint::EndpointInfo> endpoints4_3 = {{MakeURL(40)}};
std::vector<ReportingEndpoint::EndpointInfo> endpoints5_3 = {{MakeURL(50)}};
std::string header3 =
ConstructHeaderGroupString(MakeEndpointGroup("4", endpoints4_3)) + ", " +
ConstructHeaderGroupString(MakeEndpointGroup("5", endpoints5_3));
ParseHeader(kUrl_, header3);
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
EXPECT_EQ(2u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey4, OriginSubdomains::DEFAULT));
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey4, OriginSubdomains::DEFAULT));
EXPECT_FALSE(
EndpointGroupExistsInCache(kGroupKey1, OriginSubdomains::DEFAULT));
EXPECT_FALSE(
EndpointGroupExistsInCache(kGroupKey2, OriginSubdomains::DEFAULT));
EXPECT_FALSE(
EndpointGroupExistsInCache(kGroupKey3, OriginSubdomains::DEFAULT));
EXPECT_EQ(2u, cache()->GetEndpointCount());
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(6 + 3 + 2,
mock_store()->CountCommands(CommandType::ADD_REPORTING_ENDPOINT));
EXPECT_EQ(3 + 2, mock_store()->CountCommands(
CommandType::ADD_REPORTING_ENDPOINT_GROUP));
EXPECT_EQ(6 + 3, mock_store()->CountCommands(
CommandType::DELETE_REPORTING_ENDPOINT));
EXPECT_EQ(3, mock_store()->CountCommands(
CommandType::DELETE_REPORTING_ENDPOINT_GROUP));
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey4, endpoints4_3[0].url);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey5, endpoints5_3[0].url);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey4);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey5);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey1, endpoints1_2[0].url);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey2, endpoints2_2[0].url);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey3, endpoints3_2[0].url);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
kGroupKey1);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
kGroupKey2);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
kGroupKey3);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
}
TEST_P(ReportingHeaderParserTest, ZeroMaxAgeRemovesEndpointGroup) {
// Without a pre-existing client, max_age: 0 should do nothing.
ASSERT_EQ(0u, cache()->GetEndpointCount());
ParseHeader(kUrl_, "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
"\"}],\"max_age\":0}");
EXPECT_EQ(0u, cache()->GetEndpointCount());
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(0,
mock_store()->CountCommands(CommandType::ADD_REPORTING_ENDPOINT));
EXPECT_EQ(0, mock_store()->CountCommands(
CommandType::ADD_REPORTING_ENDPOINT_GROUP));
}
// Set a header with two endpoint groups.
std::vector<ReportingEndpoint::EndpointInfo> endpoints1 = {{kEndpoint_}};
std::vector<ReportingEndpoint::EndpointInfo> endpoints2 = {{kEndpoint2_}};
std::string header1 =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints1)) +
", " +
ConstructHeaderGroupString(MakeEndpointGroup(kGroup2_, endpoints2));
ParseHeader(kUrl_, header1);
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
EXPECT_EQ(2u, cache()->GetEndpointGroupCountForTesting());
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT));
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey2_, OriginSubdomains::DEFAULT));
EXPECT_EQ(2u, cache()->GetEndpointCount());
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(2,
mock_store()->CountCommands(CommandType::ADD_REPORTING_ENDPOINT));
EXPECT_EQ(2, mock_store()->CountCommands(
CommandType::ADD_REPORTING_ENDPOINT_GROUP));
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey2_, kEndpoint2_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey2_);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
// Set another header with max_age: 0 to delete one of the groups.
std::string header2 = ConstructHeaderGroupString(MakeEndpointGroup(
kGroup_, endpoints1, OriginSubdomains::DEFAULT,
base::TimeDelta::FromSeconds(0))) +
", " +
ConstructHeaderGroupString(MakeEndpointGroup(
kGroup2_, endpoints2)); // Other group stays.
ParseHeader(kUrl_, header2);
EXPECT_TRUE(ClientExistsInCacheForOrigin(kOrigin_));
EXPECT_EQ(1u, cache()->GetEndpointGroupCountForTesting());
// Group was deleted.
EXPECT_FALSE(
EndpointGroupExistsInCache(kGroupKey_, OriginSubdomains::DEFAULT));
// Other group remains in the cache.
EXPECT_TRUE(
EndpointGroupExistsInCache(kGroupKey2_, OriginSubdomains::DEFAULT));
EXPECT_EQ(1u, cache()->GetEndpointCount());
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(2,
mock_store()->CountCommands(CommandType::ADD_REPORTING_ENDPOINT));
EXPECT_EQ(2, mock_store()->CountCommands(
CommandType::ADD_REPORTING_ENDPOINT_GROUP));
EXPECT_EQ(
1, mock_store()->CountCommands(CommandType::DELETE_REPORTING_ENDPOINT));
EXPECT_EQ(1, mock_store()->CountCommands(
CommandType::DELETE_REPORTING_ENDPOINT_GROUP));
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
// Set another header with max_age: 0 to delete the other group. (Should work
// even if the endpoints field is an empty list.)
std::string header3 = ConstructHeaderGroupString(MakeEndpointGroup(
kGroup2_, std::vector<ReportingEndpoint::EndpointInfo>(),
OriginSubdomains::DEFAULT, base::TimeDelta::FromSeconds(0)));
ParseHeader(kUrl_, header3);
// Deletion of the last remaining group also deletes the client for this
// origin.
EXPECT_FALSE(ClientExistsInCacheForOrigin(kOrigin_));
EXPECT_EQ(0u, cache()->GetEndpointGroupCountForTesting());
EXPECT_EQ(0u, cache()->GetEndpointCount());
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(2,
mock_store()->CountCommands(CommandType::ADD_REPORTING_ENDPOINT));
EXPECT_EQ(2, mock_store()->CountCommands(
CommandType::ADD_REPORTING_ENDPOINT_GROUP));
EXPECT_EQ(1 + 1, mock_store()->CountCommands(
CommandType::DELETE_REPORTING_ENDPOINT));
EXPECT_EQ(1 + 1, mock_store()->CountCommands(
CommandType::DELETE_REPORTING_ENDPOINT_GROUP));
MockPersistentReportingStore::CommandList expected_commands;
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey2_, kEndpoint2_);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
kGroupKey2_);
EXPECT_THAT(mock_store()->GetAllCommands(),
testing::IsSupersetOf(expected_commands));
}
}
TEST_P(ReportingHeaderParserTest, EvictEndpointsOverPerOriginLimit1) {
// Set a header with too many endpoints, all in the same group.
std::vector<ReportingEndpoint::EndpointInfo> endpoints;
for (size_t i = 0; i < policy().max_endpoints_per_origin + 1; ++i) {
endpoints.push_back({MakeURL(i)});
}
std::string header =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints));
ParseHeader(kUrl_, header);
// Endpoint count should be at most the limit.
EXPECT_GE(policy().max_endpoints_per_origin, cache()->GetEndpointCount());
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(policy().max_endpoints_per_origin + 1,
static_cast<unsigned long>(mock_store()->CountCommands(
CommandType::ADD_REPORTING_ENDPOINT)));
EXPECT_EQ(1, mock_store()->CountCommands(
CommandType::ADD_REPORTING_ENDPOINT_GROUP));
EXPECT_EQ(
1, mock_store()->CountCommands(CommandType::DELETE_REPORTING_ENDPOINT));
}
}
TEST_P(ReportingHeaderParserTest, EvictEndpointsOverPerOriginLimit2) {
// Set a header with too many endpoints, in different groups.
std::string header;
for (size_t i = 0; i < policy().max_endpoints_per_origin + 1; ++i) {
std::vector<ReportingEndpoint::EndpointInfo> endpoints = {{MakeURL(i)}};
header = header + ConstructHeaderGroupString(MakeEndpointGroup(
base::NumberToString(i), endpoints));
if (i != policy().max_endpoints_per_origin)
header = header + ", ";
}
ParseHeader(kUrl_, header);
// Endpoint count should be at most the limit.
EXPECT_GE(policy().max_endpoints_per_origin, cache()->GetEndpointCount());
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(policy().max_endpoints_per_origin + 1,
static_cast<unsigned long>(mock_store()->CountCommands(
CommandType::ADD_REPORTING_ENDPOINT)));
EXPECT_EQ(policy().max_endpoints_per_origin + 1,
static_cast<unsigned long>(mock_store()->CountCommands(
CommandType::ADD_REPORTING_ENDPOINT_GROUP)));
EXPECT_EQ(
1, mock_store()->CountCommands(CommandType::DELETE_REPORTING_ENDPOINT));
EXPECT_EQ(1, mock_store()->CountCommands(
CommandType::DELETE_REPORTING_ENDPOINT_GROUP));
}
}
TEST_P(ReportingHeaderParserTest, EvictEndpointsOverGlobalLimit) {
// Set headers from different origins up to the global limit.
for (size_t i = 0; i < policy().max_endpoint_count; ++i) {
std::vector<ReportingEndpoint::EndpointInfo> endpoints = {{MakeURL(i)}};
std::string header =
ConstructHeaderGroupString(MakeEndpointGroup(kGroup_, endpoints));
ParseHeader(MakeURL(i), header);
}
EXPECT_EQ(policy().max_endpoint_count, cache()->GetEndpointCount());
// Parse one more header to trigger eviction.
ParseHeader(kUrl_, "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
"\"}],\"max_age\":1}");
// Endpoint count should be at most the limit.
EXPECT_GE(policy().max_endpoint_count, cache()->GetEndpointCount());
if (mock_store()) {
mock_store()->Flush();
EXPECT_EQ(policy().max_endpoint_count + 1,
static_cast<unsigned long>(mock_store()->CountCommands(
CommandType::ADD_REPORTING_ENDPOINT)));
EXPECT_EQ(policy().max_endpoint_count + 1,
static_cast<unsigned long>(mock_store()->CountCommands(
CommandType::ADD_REPORTING_ENDPOINT_GROUP)));
EXPECT_EQ(
1, mock_store()->CountCommands(CommandType::DELETE_REPORTING_ENDPOINT));
EXPECT_EQ(1, mock_store()->CountCommands(
CommandType::DELETE_REPORTING_ENDPOINT_GROUP));
}
}
INSTANTIATE_TEST_SUITE_P(ReportingHeaderParserStoreTest,
ReportingHeaderParserTest,
testing::Bool());
} // namespace
} // namespace net