// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/web_package/signed_exchange_loader.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/web_package/mock_signed_exchange_handler.h"
#include "content/browser/web_package/signed_exchange_devtools_proxy.h"
#include "content/browser/web_package/signed_exchange_reporter.h"
#include "mojo/public/cpp/system/data_pipe_producer.h"
#include "mojo/public/cpp/system/string_data_source.h"
#include "net/base/network_anonymization_key.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/early_hints.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::_;

namespace content {

class SignedExchangeLoaderTest : public testing::Test {
 public:
  SignedExchangeLoaderTest() = default;
  SignedExchangeLoaderTest(const SignedExchangeLoaderTest&) = delete;
  SignedExchangeLoaderTest& operator=(const SignedExchangeLoaderTest&) = delete;

  ~SignedExchangeLoaderTest() override = default;

 protected:
  class MockURLLoaderClient final : public network::mojom::URLLoaderClient {
   public:
    explicit MockURLLoaderClient(
        mojo::PendingReceiver<network::mojom::URLLoaderClient> receiver)
        : loader_client_receiver_(this, std::move(receiver)) {}

    MockURLLoaderClient(const MockURLLoaderClient&) = delete;
    MockURLLoaderClient& operator=(const MockURLLoaderClient&) = delete;

    ~MockURLLoaderClient() override {}

    // network::mojom::URLLoaderClient overrides:
    MOCK_METHOD1(OnReceiveEarlyHints,
                 void(const network::mojom::EarlyHintsPtr));
    MOCK_METHOD3(OnReceiveResponse,
                 void(const network::mojom::URLResponseHeadPtr,
                      mojo::ScopedDataPipeConsumerHandle,
                      std::optional<mojo_base::BigBuffer>));
    MOCK_METHOD2(OnReceiveRedirect,
                 void(const net::RedirectInfo&,
                      network::mojom::URLResponseHeadPtr));
    MOCK_METHOD3(OnUploadProgress,
                 void(int64_t, int64_t, base::OnceCallback<void()> callback));
    MOCK_METHOD1(OnTransferSizeUpdated, void(int32_t));
    MOCK_METHOD1(OnComplete, void(const network::URLLoaderCompletionStatus&));

   private:
    mojo::Receiver<network::mojom::URLLoaderClient> loader_client_receiver_;
  };

  class MockURLLoader final : public network::mojom::URLLoader {
   public:
    explicit MockURLLoader(
        mojo::PendingReceiver<network::mojom::URLLoader> url_loader_receiver)
        : receiver_(this, std::move(url_loader_receiver)) {}

    MockURLLoader(const MockURLLoader&) = delete;
    MockURLLoader& operator=(const MockURLLoader&) = delete;

    ~MockURLLoader() override = default;

    // network::mojom::URLLoader overrides:
    MOCK_METHOD2(FollowRedirect,
                 void(network::HttpRequestHeadersUpdateParams,
                      const std::optional<GURL>&));
    MOCK_METHOD2(SetPriority,
                 void(net::RequestPriority priority,
                      int32_t intra_priority_value));

   private:
    mojo::Receiver<network::mojom::URLLoader> receiver_;
  };

 private:
  base::test::TaskEnvironment task_environment_;
};

TEST_F(SignedExchangeLoaderTest, Simple) {
  mojo::PendingRemote<network::mojom::URLLoader> loader;
  mojo::Remote<network::mojom::URLLoaderClient> loader_client;
  MockURLLoader mock_loader(loader.InitWithNewPipeAndPassReceiver());
  network::mojom::URLLoaderClientEndpointsPtr endpoints =
      network::mojom::URLLoaderClientEndpoints::New(
          std::move(loader), loader_client.BindNewPipeAndPassReceiver());

  mojo::PendingRemote<network::mojom::URLLoaderClient> client;
  MockURLLoaderClient mock_client(client.InitWithNewPipeAndPassReceiver());

  network::ResourceRequest resource_request;
  resource_request.url = GURL("https://example.com/test.sxg");
  auto origin = url::Origin::Create(resource_request.url);
  resource_request.trusted_params = network::ResourceRequest::TrustedParams();
  resource_request.trusted_params->isolation_info = net::IsolationInfo::Create(
      net::IsolationInfo::RequestType::kOther, origin, origin,
      net::SiteForCookies::FromOrigin(origin));

  auto response = network::mojom::URLResponseHead::New();
  std::string headers("HTTP/1.1 200 OK\nnContent-type: foo/bar\n\n");
  response->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
      net::HttpUtil::AssembleRawHeaders(headers));

  MockSignedExchangeHandlerFactory factory({MockSignedExchangeHandlerParams(
      resource_request.url, SignedExchangeLoadResult::kSuccess, net::OK,
      GURL("https://publisher.example.com/"), "text/html", {},
      net::SHA256HashValue({{0x00}}))});

  SignedExchangeLoader::SetSignedExchangeHandlerFactoryForTest(&factory);

  mojo::ScopedDataPipeProducerHandle producer_handle;
  mojo::ScopedDataPipeConsumerHandle consumer_handle;
  MojoResult rv =
      mojo::CreateDataPipe(nullptr, producer_handle, consumer_handle);
  ASSERT_EQ(MOJO_RESULT_OK, rv);
  std::unique_ptr<SignedExchangeLoader> signed_exchange_loader =
      std::make_unique<SignedExchangeLoader>(
          resource_request, std::move(response), std::move(consumer_handle),
          std::move(client), std::move(endpoints),
          network::mojom::kURLLoadOptionNone,
          false /* should_redirect_to_fallback */, nullptr /* devtools_proxy */,
          nullptr /* reporter */, nullptr /* url_loader_factory */,
          SignedExchangeLoader::URLLoaderThrottlesGetter(), FrameTreeNodeId(),
          std::string() /* accept_langs */,
          false /* keep_entry_for_prefetch_cache */);

  constexpr int kIntraPriority = 5;
  EXPECT_CALL(mock_loader,
              SetPriority(net::RequestPriority::HIGHEST, kIntraPriority));
  signed_exchange_loader->SetPriority(net::RequestPriority::HIGHEST,
                                      kIntraPriority);

  EXPECT_CALL(mock_client, OnReceiveRedirect(_, _));
  base::RunLoop().RunUntilIdle();

  std::unique_ptr<mojo::DataPipeProducer> producer =
      std::make_unique<mojo::DataPipeProducer>(std::move(producer_handle));
  mojo::DataPipeProducer* raw_producer = producer.get();
  base::RunLoop run_loop;
  raw_producer->Write(
      std::make_unique<mojo::StringDataSource>(
          MockSignedExchangeHandler::kMockSxgPrefix + "Hello, world!",
          mojo::StringDataSource::AsyncWritingMode::
              STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION),
      base::BindOnce([](std::unique_ptr<mojo::DataPipeProducer> producer,
                        base::OnceClosure quit_closure,
                        MojoResult result) { std::move(quit_closure).Run(); },
                     std::move(producer), run_loop.QuitClosure()));

  network::URLLoaderCompletionStatus status;
  loader_client->OnComplete(network::URLLoaderCompletionStatus(net::OK));
  base::RunLoop().RunUntilIdle();

  mojo::PendingRemote<network::mojom::URLLoaderClient> client_after_redirect;
  MockURLLoaderClient mock_client_after_redirect(
      client_after_redirect.InitWithNewPipeAndPassReceiver());
  EXPECT_CALL(mock_client_after_redirect, OnReceiveResponse(_, _, _));

  run_loop.Run();
  EXPECT_CALL(mock_client_after_redirect, OnComplete(_));

  signed_exchange_loader->ConnectToClient(std::move(client_after_redirect));
  base::RunLoop().RunUntilIdle();
}

}  // namespace content
