// 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 "components/feedback/feedback_uploader.h"

#include <string>

#include "base/macros.h"
#include "base/metrics/field_trial.h"
#include "base/run_loop.h"
#include "base/task_scheduler/post_task.h"
#include "base/task_scheduler/task_traits.h"
#include "base/test/bind_test_util.h"
#include "components/feedback/feedback_uploader_factory.h"
#include "components/variations/variations_associated_data.h"
#include "components/variations/variations_http_header_provider.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.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/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace feedback {

namespace {

constexpr base::TimeDelta kTestRetryDelay =
    base::TimeDelta::FromMilliseconds(1);

constexpr char kFeedbackPostUrl[] =
    "https://www.google.com/tools/feedback/chrome/__submit";

void QueueReport(FeedbackUploader* uploader, const std::string& report_data) {
  uploader->QueueReport(std::make_unique<std::string>(report_data));
}

}  // namespace

class FeedbackUploaderDispatchTest : public ::testing::Test {
 protected:
  FeedbackUploaderDispatchTest()
      : browser_thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
        shared_url_loader_factory_(
            base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
                &test_url_loader_factory_)) {}

  ~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, trial_name, group_name,
        static_cast<variations::VariationID>(variation_id));
    base::FieldTrialList::CreateFieldTrial(trial_name, group_name)->group();
  }

  scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory() {
    return shared_url_loader_factory_;
  }

  network::TestURLLoaderFactory* test_url_loader_factory() {
    return &test_url_loader_factory_;
  }

  content::BrowserContext* context() { return &context_; }

 private:
  content::TestBrowserThreadBundle browser_thread_bundle_;
  content::TestBrowserContext context_;
  network::TestURLLoaderFactory test_url_loader_factory_;
  scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;

  DISALLOW_COPY_AND_ASSIGN(FeedbackUploaderDispatchTest);
};

TEST_F(FeedbackUploaderDispatchTest, VariationHeaders) {
  // Register a trial and variation id, so that there is data in variations
  // headers. Also, the variations header provider may have been registered to
  // observe some other field trial list, so reset it.
  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
  base::FieldTrialList field_trial_list_(nullptr);
  CreateFieldTrialWithId("Test", "Group1", 123);

  FeedbackUploader uploader(
      shared_url_loader_factory(), context(),
      FeedbackUploaderFactory::CreateUploaderTaskRunner());

  net::HttpRequestHeaders headers;
  test_url_loader_factory()->SetInterceptor(
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        headers = request.headers;
      }));

  QueueReport(&uploader, "test");
  base::RunLoop().RunUntilIdle();

  std::string value;
  EXPECT_TRUE(headers.GetHeader("X-Client-Data", &value));
  EXPECT_FALSE(value.empty());

  variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
}

TEST_F(FeedbackUploaderDispatchTest, 204Response) {
  FeedbackUploader::SetMinimumRetryDelayForTesting(kTestRetryDelay);
  FeedbackUploader uploader(
      shared_url_loader_factory(), context(),
      FeedbackUploaderFactory::CreateUploaderTaskRunner());

  EXPECT_EQ(kTestRetryDelay, uploader.retry_delay());
  // Successful reports should not introduce any retries, and should not
  // increase the backoff delay.
  network::ResourceResponseHead head;
  std::string headers("HTTP/1.1 204 No Content\n\n");
  head.headers = base::MakeRefCounted<net::HttpResponseHeaders>(
      net::HttpUtil::AssembleRawHeaders(headers.c_str(), headers.size()));
  network::URLLoaderCompletionStatus status;
  test_url_loader_factory()->AddResponse(GURL(kFeedbackPostUrl), 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(
      shared_url_loader_factory(), context(),
      FeedbackUploaderFactory::CreateUploaderTaskRunner());

  EXPECT_EQ(kTestRetryDelay, uploader.retry_delay());
  // Failed reports due to client errors are not retried. No backoff delay
  // should be doubled.
  network::ResourceResponseHead head;
  std::string headers("HTTP/1.1 400 Bad Request\n\n");
  head.headers = base::MakeRefCounted<net::HttpResponseHeaders>(
      net::HttpUtil::AssembleRawHeaders(headers.c_str(), headers.size()));
  network::URLLoaderCompletionStatus status;
  test_url_loader_factory()->AddResponse(GURL(kFeedbackPostUrl), 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(
      shared_url_loader_factory(), context(),
      FeedbackUploaderFactory::CreateUploaderTaskRunner());

  EXPECT_EQ(kTestRetryDelay, uploader.retry_delay());
  // Failed reports due to server errors are retried.
  network::ResourceResponseHead head;
  std::string headers("HTTP/1.1 500 Server Error\n\n");
  head.headers = base::MakeRefCounted<net::HttpResponseHeaders>(
      net::HttpUtil::AssembleRawHeaders(headers.c_str(), headers.size()));
  network::URLLoaderCompletionStatus status;
  test_url_loader_factory()->AddResponse(GURL(kFeedbackPostUrl), head, "",
                                         status);
  QueueReport(&uploader, "Server error failed report");
  base::RunLoop().RunUntilIdle();

  EXPECT_LT(kTestRetryDelay, uploader.retry_delay());
  EXPECT_FALSE(uploader.QueueEmpty());
}

}  // namespace feedback
