blob: 944b149580189934997c212b034729c95fec15a6 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/reporting/reporting_service.h"
#include <memory>
#include <string>
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/tick_clock.h"
#include "base/values.h"
#include "net/base/features.h"
#include "net/base/isolation_info.h"
#include "net/base/network_anonymization_key.h"
#include "net/base/schemeful_site.h"
#include "net/reporting/mock_persistent_reporting_store.h"
#include "net/reporting/reporting_browsing_data_remover.h"
#include "net/reporting/reporting_cache.h"
#include "net/reporting/reporting_context.h"
#include "net/reporting/reporting_endpoint.h"
#include "net/reporting/reporting_policy.h"
#include "net/reporting/reporting_report.h"
#include "net/reporting/reporting_service.h"
#include "net/reporting/reporting_test_util.h"
#include "net/test/test_with_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace net {
namespace {
using CommandType = MockPersistentReportingStore::Command::Type;
// The tests are parametrized on a boolean value which represents whether to use
// a MockPersistentReportingStore (if false, no store is used).
class ReportingServiceTest : public ::testing::TestWithParam<bool>,
public WithTaskEnvironment {
protected:
const GURL kUrl_ = GURL("https://origin/path");
const GURL kUrl2_ = GURL("https://origin2/path");
const url::Origin kOrigin_ = url::Origin::Create(kUrl_);
const url::Origin kOrigin2_ = url::Origin::Create(kUrl2_);
const GURL kEndpoint_ = GURL("https://endpoint/");
const GURL kEndpoint2_ = GURL("https://endpoint2/");
const std::string kUserAgent_ = "Mozilla/1.0";
const std::string kGroup_ = "group";
const std::string kGroup2_ = "group2";
const std::string kType_ = "type";
const absl::optional<base::UnguessableToken> kReportingSource_ =
base::UnguessableToken::Create();
const NetworkAnonymizationKey kNak_ =
NetworkAnonymizationKey::CreateSameSite(SchemefulSite(kOrigin_));
const NetworkAnonymizationKey kNak2_ =
NetworkAnonymizationKey::CreateSameSite(SchemefulSite(kOrigin2_));
const ReportingEndpointGroupKey kGroupKey_ =
ReportingEndpointGroupKey(kNak_, kOrigin_, kGroup_);
const ReportingEndpointGroupKey kGroupKey2_ =
ReportingEndpointGroupKey(kNak2_, kOrigin2_, kGroup_);
const IsolationInfo kIsolationInfo_ =
IsolationInfo::Create(IsolationInfo::RequestType::kOther,
kOrigin_,
kOrigin_,
SiteForCookies::FromOrigin(kOrigin_));
ReportingServiceTest() {
feature_list_.InitAndEnableFeature(
features::kPartitionNelAndReportingByNetworkIsolationKey);
Init();
}
// Initializes, or re-initializes, |service_| and its dependencies.
void Init() {
if (GetParam())
store_ = std::make_unique<MockPersistentReportingStore>();
else
store_ = nullptr;
auto test_context = std::make_unique<TestReportingContext>(
&clock_, &tick_clock_, ReportingPolicy(), store_.get());
context_ = test_context.get();
service_ = ReportingService::CreateForTesting(std::move(test_context));
}
// If the store exists, simulate finishing loading the store, which should
// make the rest of the test run synchronously.
void FinishLoading(bool load_success) {
if (store_)
store_->FinishLoading(load_success);
}
MockPersistentReportingStore* store() { return store_.get(); }
TestReportingContext* context() { return context_; }
ReportingService* service() { return service_.get(); }
private:
base::test::ScopedFeatureList feature_list_;
base::SimpleTestClock clock_;
base::SimpleTestTickClock tick_clock_;
std::unique_ptr<MockPersistentReportingStore> store_;
raw_ptr<TestReportingContext> context_;
std::unique_ptr<ReportingService> service_;
};
TEST_P(ReportingServiceTest, QueueReport) {
service()->QueueReport(kUrl_, kReportingSource_, kNak_, kUserAgent_, kGroup_,
kType_, base::Value::Dict(), 0);
FinishLoading(true /* load_success */);
std::vector<const ReportingReport*> reports;
context()->cache()->GetReports(&reports);
ASSERT_EQ(1u, reports.size());
EXPECT_EQ(kUrl_, reports[0]->url);
EXPECT_EQ(kNak_, reports[0]->network_anonymization_key);
EXPECT_EQ(kUserAgent_, reports[0]->user_agent);
EXPECT_EQ(kGroup_, reports[0]->group);
EXPECT_EQ(kType_, reports[0]->type);
}
TEST_P(ReportingServiceTest, QueueReportSanitizeUrl) {
// Same as kUrl_ but with username, password, and fragment.
GURL url = GURL("https://username:password@origin/path#fragment");
service()->QueueReport(url, kReportingSource_, kNak_, kUserAgent_, kGroup_,
kType_, base::Value::Dict(), 0);
FinishLoading(true /* load_success */);
std::vector<const ReportingReport*> reports;
context()->cache()->GetReports(&reports);
ASSERT_EQ(1u, reports.size());
EXPECT_EQ(kUrl_, reports[0]->url);
EXPECT_EQ(kNak_, reports[0]->network_anonymization_key);
EXPECT_EQ(kUserAgent_, reports[0]->user_agent);
EXPECT_EQ(kGroup_, reports[0]->group);
EXPECT_EQ(kType_, reports[0]->type);
}
TEST_P(ReportingServiceTest, DontQueueReportInvalidUrl) {
GURL url = GURL("https://");
// This does not trigger an attempt to load from the store because the url
// is immediately rejected as invalid.
service()->QueueReport(url, kReportingSource_, kNak_, kUserAgent_, kGroup_,
kType_, base::Value::Dict(), 0);
std::vector<const ReportingReport*> reports;
context()->cache()->GetReports(&reports);
ASSERT_EQ(0u, reports.size());
}
TEST_P(ReportingServiceTest, QueueReportNetworkIsolationKeyDisabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndDisableFeature(
features::kPartitionNelAndReportingByNetworkIsolationKey);
// Re-create the store, so it reads the new feature value.
Init();
service()->QueueReport(kUrl_, kReportingSource_, kNak_, kUserAgent_, kGroup_,
kType_, base::Value::Dict(), 0);
FinishLoading(true /* load_success */);
std::vector<const ReportingReport*> reports;
context()->cache()->GetReports(&reports);
ASSERT_EQ(1u, reports.size());
// NetworkAnonymizationKey should be empty, instead of kNak_;
EXPECT_EQ(NetworkAnonymizationKey(), reports[0]->network_anonymization_key);
EXPECT_NE(kNak_, reports[0]->network_anonymization_key);
EXPECT_EQ(kUrl_, reports[0]->url);
EXPECT_EQ(kUserAgent_, reports[0]->user_agent);
EXPECT_EQ(kGroup_, reports[0]->group);
EXPECT_EQ(kType_, reports[0]->type);
}
TEST_P(ReportingServiceTest, ProcessReportToHeader) {
service()->ProcessReportToHeader(kOrigin_, kNak_,
"{\"endpoints\":[{\"url\":\"" +
kEndpoint_.spec() +
"\"}],"
"\"group\":\"" +
kGroup_ +
"\","
"\"max_age\":86400}");
FinishLoading(true /* load_success */);
EXPECT_EQ(1u, context()->cache()->GetEndpointCount());
EXPECT_TRUE(context()->cache()->GetEndpointForTesting(
ReportingEndpointGroupKey(kNak_, kOrigin_, kGroup_), kEndpoint_));
}
TEST_P(ReportingServiceTest, ProcessReportingEndpointsHeader) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(net::features::kDocumentReporting);
auto parsed_header =
ParseReportingEndpoints(kGroup_ + "=\"" + kEndpoint_.spec() + "\"");
ASSERT_TRUE(parsed_header.has_value());
service()->SetDocumentReportingEndpoints(*kReportingSource_, kOrigin_,
kIsolationInfo_, *parsed_header);
FinishLoading(true /* load_success */);
// Endpoint should not be part of the persistent store.
EXPECT_EQ(0u, context()->cache()->GetEndpointCount());
// Endpoint should be associated with the reporting source.
ReportingEndpoint cached_endpoint =
context()->cache()->GetV1EndpointForTesting(*kReportingSource_, kGroup_);
EXPECT_TRUE(cached_endpoint);
// Ensure that the NIK is stored properly with the endpoint group.
EXPECT_FALSE(cached_endpoint.group_key.network_anonymization_key.IsEmpty());
}
TEST_P(ReportingServiceTest,
ProcessReportingEndpointsHeaderNetworkIsolationKeyDisabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures(
{net::features::kDocumentReporting},
{features::kPartitionNelAndReportingByNetworkIsolationKey});
// Re-create the store, so it reads the new feature value.
Init();
auto parsed_header =
ParseReportingEndpoints(kGroup_ + "=\"" + kEndpoint_.spec() + "\"");
ASSERT_TRUE(parsed_header.has_value());
service()->SetDocumentReportingEndpoints(*kReportingSource_, kOrigin_,
kIsolationInfo_, *parsed_header);
FinishLoading(true /* load_success */);
// Endpoint should not be part of the persistent store.
EXPECT_EQ(0u, context()->cache()->GetEndpointCount());
// Endpoint should be associated with the reporting source.
ReportingEndpoint cached_endpoint =
context()->cache()->GetV1EndpointForTesting(*kReportingSource_, kGroup_);
EXPECT_TRUE(cached_endpoint);
// When isolation is disabled, cached endpoints should have a null NIK.
EXPECT_TRUE(cached_endpoint.group_key.network_anonymization_key.IsEmpty());
}
TEST_P(ReportingServiceTest, SendReportsAndRemoveSource) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(net::features::kDocumentReporting);
auto parsed_header =
ParseReportingEndpoints(kGroup_ + "=\"" + kEndpoint_.spec() + "\", " +
kGroup2_ + "=\"" + kEndpoint2_.spec() + "\"");
ASSERT_TRUE(parsed_header.has_value());
service()->SetDocumentReportingEndpoints(*kReportingSource_, kOrigin_,
kIsolationInfo_, *parsed_header);
// This report should be sent immediately, starting the delivery agent timer.
service()->QueueReport(kUrl_, kReportingSource_, kNak_, kUserAgent_, kGroup_,
kType_, base::Value::Dict(), 0);
FinishLoading(true /* load_success */);
std::vector<const ReportingReport*> reports;
context()->cache()->GetReports(&reports);
ASSERT_EQ(1u, reports.size());
EXPECT_EQ(0u, context()->cache()->GetReportCountWithStatusForTesting(
ReportingReport::Status::QUEUED));
// Now simulate the source being destroyed.
service()->SendReportsAndRemoveSource(*kReportingSource_);
// There should be no queued reports, but the previously sent report should
// still be pending.
EXPECT_EQ(0u, context()->cache()->GetReportCountWithStatusForTesting(
ReportingReport::Status::QUEUED));
EXPECT_EQ(1u, context()->cache()->GetReportCountWithStatusForTesting(
ReportingReport::Status::PENDING));
// Source should be marked as expired.
ASSERT_TRUE(
context()->cache()->GetExpiredSources().contains(*kReportingSource_));
}
// Flaky in ChromeOS: crbug.com/1356127
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_SendReportsAndRemoveSourceWithPendingReports \
DISABLED_SendReportsAndRemoveSourceWithPendingReports
#else
#define MAYBE_SendReportsAndRemoveSourceWithPendingReports \
SendReportsAndRemoveSourceWithPendingReports
#endif
TEST_P(ReportingServiceTest,
MAYBE_SendReportsAndRemoveSourceWithPendingReports) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(net::features::kDocumentReporting);
auto parsed_header =
ParseReportingEndpoints(kGroup_ + "=\"" + kEndpoint_.spec() + "\", " +
kGroup2_ + "=\"" + kEndpoint2_.spec() + "\"");
ASSERT_TRUE(parsed_header.has_value());
service()->SetDocumentReportingEndpoints(*kReportingSource_, kOrigin_,
kIsolationInfo_, *parsed_header);
// This report should be sent immediately, starting the delivery agent timer.
service()->QueueReport(kUrl_, kReportingSource_, kNak_, kUserAgent_, kGroup_,
kType_, base::Value::Dict(), 0);
FinishLoading(true /* load_success */);
std::vector<const ReportingReport*> reports;
context()->cache()->GetReports(&reports);
ASSERT_EQ(1u, reports.size());
EXPECT_EQ(0u, context()->cache()->GetReportCountWithStatusForTesting(
ReportingReport::Status::QUEUED));
EXPECT_EQ(1u, context()->cache()->GetReportCountWithStatusForTesting(
ReportingReport::Status::PENDING));
// Queue another report, which should remain queued.
service()->QueueReport(kUrl_, kReportingSource_, kNak_, kUserAgent_, kGroup_,
kType_, base::Value::Dict(), 0);
EXPECT_EQ(1u, context()->cache()->GetReportCountWithStatusForTesting(
ReportingReport::Status::QUEUED));
EXPECT_EQ(1u, context()->cache()->GetReportCountWithStatusForTesting(
ReportingReport::Status::PENDING));
// Now simulate the source being destroyed.
service()->SendReportsAndRemoveSource(*kReportingSource_);
// The report should still be queued, while the source should be marked as
// expired. (The original report is still pending.)
EXPECT_EQ(1u, context()->cache()->GetReportCountWithStatusForTesting(
ReportingReport::Status::QUEUED));
EXPECT_EQ(1u, context()->cache()->GetReportCountWithStatusForTesting(
ReportingReport::Status::PENDING));
ASSERT_TRUE(
context()->cache()->GetExpiredSources().contains(kReportingSource_));
}
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_ProcessReportingEndpointsHeaderPathAbsolute DISABLED_ProcessReportingEndpointsHeaderPathAbsolute
#else
#define MAYBE_ProcessReportingEndpointsHeaderPathAbsolute ProcessReportingEndpointsHeaderPathAbsolute
#endif
TEST_P(ReportingServiceTest, MAYBE_ProcessReportingEndpointsHeaderPathAbsolute) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(net::features::kDocumentReporting);
auto parsed_header = ParseReportingEndpoints(kGroup_ + "=\"/path-absolute\"");
ASSERT_TRUE(parsed_header.has_value());
service()->SetDocumentReportingEndpoints(*kReportingSource_, kOrigin_,
kIsolationInfo_, *parsed_header);
FinishLoading(true /* load_success */);
// Endpoint should not be part of the persistent store.
EXPECT_EQ(0u, context()->cache()->GetEndpointCount());
// Endpoint should be associated with the reporting source.
ReportingEndpoint endpoint =
context()->cache()->GetV1EndpointForTesting(*kReportingSource_, kGroup_);
EXPECT_TRUE(endpoint);
// Endpoint should have the correct path.
EXPECT_EQ(kUrl_.Resolve("/path-absolute"), endpoint.info.url);
}
TEST_P(ReportingServiceTest, ProcessReportToHeaderPathAbsolute) {
service()->ProcessReportToHeader(
kOrigin_, kNak_,
"{\"endpoints\":[{\"url\":\"/path-absolute\"}],"
"\"group\":\"" +
kGroup_ +
"\","
"\"max_age\":86400}");
FinishLoading(true /* load_success */);
EXPECT_EQ(1u, context()->cache()->GetEndpointCount());
}
TEST_P(ReportingServiceTest, ProcessReportToHeader_TooLong) {
const std::string header_too_long =
"{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
"\"}],"
"\"group\":\"" +
kGroup_ +
"\","
"\"max_age\":86400," +
"\"junk\":\"" + std::string(32 * 1024, 'a') + "\"}";
// This does not trigger an attempt to load from the store because the header
// is immediately rejected as invalid.
service()->ProcessReportToHeader(kOrigin_, kNak_, header_too_long);
EXPECT_EQ(0u, context()->cache()->GetEndpointCount());
}
TEST_P(ReportingServiceTest, ProcessReportToHeader_TooDeep) {
const std::string header_too_deep = "{\"endpoints\":[{\"url\":\"" +
kEndpoint_.spec() +
"\"}],"
"\"group\":\"" +
kGroup_ +
"\","
"\"max_age\":86400," +
"\"junk\":[[[[[[[[[[]]]]]]]]]]}";
// This does not trigger an attempt to load from the store because the header
// is immediately rejected as invalid.
service()->ProcessReportToHeader(kOrigin_, kNak_, header_too_deep);
EXPECT_EQ(0u, context()->cache()->GetEndpointCount());
}
TEST_P(ReportingServiceTest, ProcessReportToHeaderNetworkIsolationKeyDisabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndDisableFeature(
features::kPartitionNelAndReportingByNetworkIsolationKey);
// Re-create the store, so it reads the new feature value.
Init();
service()->ProcessReportToHeader(kOrigin_, kNak_,
"{\"endpoints\":[{\"url\":\"" +
kEndpoint_.spec() +
"\"}],"
"\"group\":\"" +
kGroup_ +
"\","
"\"max_age\":86400}");
FinishLoading(true /* load_success */);
EXPECT_EQ(1u, context()->cache()->GetEndpointCount());
EXPECT_FALSE(context()->cache()->GetEndpointForTesting(
ReportingEndpointGroupKey(kNak_, kOrigin_, kGroup_), kEndpoint_));
EXPECT_TRUE(context()->cache()->GetEndpointForTesting(
ReportingEndpointGroupKey(NetworkAnonymizationKey(), kOrigin_, kGroup_),
kEndpoint_));
}
TEST_P(ReportingServiceTest, WriteToStore) {
if (!store())
return;
MockPersistentReportingStore::CommandList expected_commands;
// This first call to any public method triggers a load. The load will block
// until we call FinishLoading.
service()->ProcessReportToHeader(kOrigin_, kNak_,
"{\"endpoints\":[{\"url\":\"" +
kEndpoint_.spec() +
"\"}],"
"\"group\":\"" +
kGroup_ +
"\","
"\"max_age\":86400}");
expected_commands.emplace_back(CommandType::LOAD_REPORTING_CLIENTS);
EXPECT_THAT(store()->GetAllCommands(),
testing::UnorderedElementsAreArray(expected_commands));
// Unblock the load. The will let the remaining calls to the service complete
// without blocking.
FinishLoading(true /* load_success */);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
EXPECT_THAT(store()->GetAllCommands(),
testing::UnorderedElementsAreArray(expected_commands));
service()->ProcessReportToHeader(kOrigin2_, kNak2_,
"{\"endpoints\":[{\"url\":\"" +
kEndpoint_.spec() +
"\"}],"
"\"group\":\"" +
kGroup_ +
"\","
"\"max_age\":86400}");
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey2_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey2_);
EXPECT_THAT(store()->GetAllCommands(),
testing::UnorderedElementsAreArray(expected_commands));
service()->QueueReport(kUrl_, kReportingSource_, kNak_, kUserAgent_, kGroup_,
kType_, base::Value::Dict(), 0);
expected_commands.emplace_back(
CommandType::UPDATE_REPORTING_ENDPOINT_GROUP_ACCESS_TIME, kGroupKey_);
EXPECT_THAT(store()->GetAllCommands(),
testing::UnorderedElementsAreArray(expected_commands));
service()->RemoveBrowsingData(
ReportingBrowsingDataRemover::DATA_TYPE_CLIENTS,
base::BindRepeating(
[](const url::Origin& origin) { return origin.host() == "origin"; }));
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
expected_commands.emplace_back(CommandType::FLUSH);
EXPECT_THAT(store()->GetAllCommands(),
testing::UnorderedElementsAreArray(expected_commands));
service()->RemoveAllBrowsingData(
ReportingBrowsingDataRemover::DATA_TYPE_CLIENTS);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey2_, kEndpoint_);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
kGroupKey2_);
expected_commands.emplace_back(CommandType::FLUSH);
EXPECT_THAT(store()->GetAllCommands(),
testing::UnorderedElementsAreArray(expected_commands));
}
TEST_P(ReportingServiceTest, WaitUntilLoadFinishesBeforeWritingToStore) {
if (!store())
return;
MockPersistentReportingStore::CommandList expected_commands;
// This first call to any public method triggers a load. The load will block
// until we call FinishLoading.
service()->ProcessReportToHeader(kOrigin_, kNak_,
"{\"endpoints\":[{\"url\":\"" +
kEndpoint_.spec() +
"\"}],"
"\"group\":\"" +
kGroup_ +
"\","
"\"max_age\":86400}");
expected_commands.emplace_back(CommandType::LOAD_REPORTING_CLIENTS);
EXPECT_THAT(store()->GetAllCommands(),
testing::UnorderedElementsAreArray(expected_commands));
service()->ProcessReportToHeader(kOrigin2_, kNak2_,
"{\"endpoints\":[{\"url\":\"" +
kEndpoint_.spec() +
"\"}],"
"\"group\":\"" +
kGroup_ +
"\","
"\"max_age\":86400}");
EXPECT_THAT(store()->GetAllCommands(),
testing::UnorderedElementsAreArray(expected_commands));
service()->QueueReport(kUrl_, kReportingSource_, kNak_, kUserAgent_, kGroup_,
kType_, base::Value::Dict(), 0);
EXPECT_THAT(store()->GetAllCommands(),
testing::UnorderedElementsAreArray(expected_commands));
service()->RemoveBrowsingData(
ReportingBrowsingDataRemover::DATA_TYPE_CLIENTS,
base::BindRepeating(
[](const url::Origin& origin) { return origin.host() == "origin"; }));
EXPECT_THAT(store()->GetAllCommands(),
testing::UnorderedElementsAreArray(expected_commands));
service()->RemoveAllBrowsingData(
ReportingBrowsingDataRemover::DATA_TYPE_CLIENTS);
EXPECT_THAT(store()->GetAllCommands(),
testing::UnorderedElementsAreArray(expected_commands));
// Unblock the load. The will let the remaining calls to the service complete
// without blocking.
FinishLoading(true /* load_success */);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
kGroupKey2_, kEndpoint_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
kGroupKey2_);
expected_commands.emplace_back(
CommandType::UPDATE_REPORTING_ENDPOINT_GROUP_ACCESS_TIME, kGroupKey_);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey_, kEndpoint_);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
kGroupKey_);
expected_commands.emplace_back(CommandType::FLUSH);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
kGroupKey2_, kEndpoint_);
expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
kGroupKey2_);
expected_commands.emplace_back(CommandType::FLUSH);
EXPECT_THAT(store()->GetAllCommands(),
testing::UnorderedElementsAreArray(expected_commands));
}
INSTANTIATE_TEST_SUITE_P(ReportingServiceStoreTest,
ReportingServiceTest,
::testing::Bool());
} // namespace
} // namespace net