blob: 0589b6d89c91b1ed86831ffcdff233dc461dcdc7 [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 "components/feedback/feedback_uploader.h"
#include <string>
#include "base/files/scoped_temp_dir.h"
#include "base/metrics/field_trial.h"
#include "base/run_loop.h"
#include "base/task/task_traits.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "components/variations/net/variations_http_headers.h"
#include "components/variations/scoped_variations_ids_provider.h"
#include "components/variations/variations_associated_data.h"
#include "components/variations/variations_ids_provider.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace feedback {
namespace {
constexpr base::TimeDelta kTestRetryDelay = base::Milliseconds(1);
constexpr char kFeedbackPostUrl[] =
"https://www.google.com/tools/feedback/chrome/__submit";
void QueueReport(FeedbackUploader* uploader,
const std::string& report_data,
bool has_email = true) {
uploader->QueueReport(std::make_unique<std::string>(report_data), has_email);
}
// Stand-in for the FeedbackUploaderChrome class that adds a fake bearer token
// to the request.
class MockFeedbackUploaderChrome : public FeedbackUploader {
public:
MockFeedbackUploaderChrome(
bool is_off_the_record,
const base::FilePath& state_path,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: FeedbackUploader(is_off_the_record, state_path, url_loader_factory) {}
void AppendExtraHeadersToUploadRequest(
network::ResourceRequest* resource_request) override {
resource_request->headers.SetHeader(net::HttpRequestHeaders::kAuthorization,
"Bearer abcdefg");
}
};
} // namespace
class FeedbackUploaderDispatchTest : public ::testing::Test {
protected:
FeedbackUploaderDispatchTest()
: shared_url_loader_factory_(
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_)) {
EXPECT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
}
FeedbackUploaderDispatchTest(const FeedbackUploaderDispatchTest&) = delete;
FeedbackUploaderDispatchTest& operator=(const FeedbackUploaderDispatchTest&) =
delete;
~FeedbackUploaderDispatchTest() override {
// Clean up registered ids.
variations::testing::ClearAllVariationIDs();
}
// Registers a field trial with the specified name and group and an associated
// google web property variation id.
void CreateFieldTrialWithId(const std::string& trial_name,
const std::string& group_name,
int variation_id) {
variations::AssociateGoogleVariationID(
variations::GOOGLE_WEB_PROPERTIES_ANY_CONTEXT, trial_name, group_name,
static_cast<variations::VariationID>(variation_id));
base::FieldTrialList::CreateFieldTrial(trial_name, group_name)->Activate();
}
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory() {
return shared_url_loader_factory_;
}
network::TestURLLoaderFactory* test_url_loader_factory() {
return &test_url_loader_factory_;
}
const base::FilePath& state_path() { return scoped_temp_dir_.GetPath(); }
private:
base::test::TaskEnvironment task_environment_;
base::ScopedTempDir scoped_temp_dir_;
variations::ScopedVariationsIdsProvider scoped_variations_ids_provider_{
variations::VariationsIdsProvider::Mode::kUseSignedInState};
network::TestURLLoaderFactory test_url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
};
TEST_F(FeedbackUploaderDispatchTest, VariationHeaders) {
// Register a trial and variation id, so that there is data in variations
// headers.
CreateFieldTrialWithId("Test", "Group1", 123);
FeedbackUploader uploader(/*is_off_the_record=*/false, state_path(),
shared_url_loader_factory());
net::HttpRequestHeaders headers;
test_url_loader_factory()->SetInterceptor(
base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
EXPECT_TRUE(variations::HasVariationsHeader(request));
}));
QueueReport(&uploader, "test");
base::RunLoop().RunUntilIdle();
}
// Test that the bearer token is present if there is an email address present in
// the report.
TEST_F(FeedbackUploaderDispatchTest, BearerTokenWithEmail) {
MockFeedbackUploaderChrome uploader(/*is_off_the_record=*/false, state_path(),
shared_url_loader_factory());
test_url_loader_factory()->SetInterceptor(
base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
EXPECT_TRUE(request.headers.HasHeader("Authorization"));
}));
QueueReport(&uploader, "test", /*has_email=*/true);
base::RunLoop().RunUntilIdle();
}
TEST_F(FeedbackUploaderDispatchTest, BearerTokenNoEmail) {
MockFeedbackUploaderChrome uploader(/*is_off_the_record=*/false, state_path(),
shared_url_loader_factory());
test_url_loader_factory()->SetInterceptor(
base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
EXPECT_FALSE(request.headers.HasHeader("Authorization"));
}));
QueueReport(&uploader, "test", /*has_email=*/false);
base::RunLoop().RunUntilIdle();
}
TEST_F(FeedbackUploaderDispatchTest, 204Response) {
FeedbackUploader::SetMinimumRetryDelayForTesting(kTestRetryDelay);
FeedbackUploader uploader(/*is_off_the_record=*/false, state_path(),
shared_url_loader_factory());
EXPECT_EQ(kTestRetryDelay, uploader.retry_delay());
// Successful reports should not introduce any retries, and should not
// increase the backoff delay.
auto head = network::mojom::URLResponseHead::New();
std::string headers("HTTP/1.1 204 No Content\n\n");
head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
net::HttpUtil::AssembleRawHeaders(headers));
network::URLLoaderCompletionStatus status;
test_url_loader_factory()->AddResponse(GURL(kFeedbackPostUrl),
std::move(head), "", status);
QueueReport(&uploader, "Successful report");
base::RunLoop().RunUntilIdle();
EXPECT_EQ(kTestRetryDelay, uploader.retry_delay());
EXPECT_TRUE(uploader.QueueEmpty());
}
TEST_F(FeedbackUploaderDispatchTest, 400Response) {
FeedbackUploader::SetMinimumRetryDelayForTesting(kTestRetryDelay);
FeedbackUploader uploader(/*is_off_the_record=*/false, state_path(),
shared_url_loader_factory());
EXPECT_EQ(kTestRetryDelay, uploader.retry_delay());
// Failed reports due to client errors are not retried. No backoff delay
// should be doubled.
auto head = network::mojom::URLResponseHead::New();
std::string headers("HTTP/1.1 400 Bad Request\n\n");
head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
net::HttpUtil::AssembleRawHeaders(headers));
network::URLLoaderCompletionStatus status;
test_url_loader_factory()->AddResponse(GURL(kFeedbackPostUrl),
std::move(head), "", status);
QueueReport(&uploader, "Client error failed report");
base::RunLoop().RunUntilIdle();
EXPECT_EQ(kTestRetryDelay, uploader.retry_delay());
EXPECT_TRUE(uploader.QueueEmpty());
}
TEST_F(FeedbackUploaderDispatchTest, 500Response) {
FeedbackUploader::SetMinimumRetryDelayForTesting(kTestRetryDelay);
FeedbackUploader uploader(/*is_off_the_record=*/false, state_path(),
shared_url_loader_factory());
EXPECT_EQ(kTestRetryDelay, uploader.retry_delay());
// Failed reports due to server errors are retried.
auto head = network::mojom::URLResponseHead::New();
std::string headers("HTTP/1.1 500 Server Error\n\n");
head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
net::HttpUtil::AssembleRawHeaders(headers));
network::URLLoaderCompletionStatus status;
test_url_loader_factory()->AddResponse(GURL(kFeedbackPostUrl),
std::move(head), "", status);
QueueReport(&uploader, "Server error failed report");
base::RunLoop().RunUntilIdle();
EXPECT_LT(kTestRetryDelay, uploader.retry_delay());
EXPECT_FALSE(uploader.QueueEmpty());
}
} // namespace feedback