// Copyright 2016 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 "content/browser/loader/mojo_async_resource_handler.h"

#include <string.h>

#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/test/gtest_util.h"
#include "base/test/test_simple_task_runner.h"
#include "base/timer/mock_timer.h"
#include "content/browser/loader/mock_resource_loader.h"
#include "content/browser/loader/resource_controller.h"
#include "content/browser/loader/resource_dispatcher_host_impl.h"
#include "content/browser/loader/resource_request_info_impl.h"
#include "content/public/browser/appcache_service.h"
#include "content/public/browser/navigation_data.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/resource_dispatcher_host_delegate.h"
#include "content/public/browser/resource_throttle.h"
#include "content/public/browser/stream_info.h"
#include "content/public/common/previews_state.h"
#include "content/public/common/resource_type.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "mojo/public/c/system/data_pipe.h"
#include "mojo/public/c/system/types.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "net/base/auth.h"
#include "net/base/net_errors.h"
#include "net/cert/cert_status_flags.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_response_info.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "net/ssl/ssl_info.h"
#include "net/test/cert_test_util.h"
#include "net/test/url_request/url_request_mock_data_job.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_status.h"
#include "net/url_request/url_request_test_job.h"
#include "net/url_request/url_request_test_util.h"
#include "services/network/public/cpp/resource_response.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/resource_scheduler.h"
#include "services/network/test/test_url_loader_client.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/page_transition_types.h"

namespace content {
namespace {

constexpr int kSizeMimeSnifferRequiresForFirstOnWillRead = 2048;

class DummyUploadDataStream : public net::UploadDataStream {
 public:
  DummyUploadDataStream() : UploadDataStream(false, 0) {}

  int InitInternal(const net::NetLogWithSource& net_log) override {
    NOTREACHED();
    return 0;
  }
  int ReadInternal(net::IOBuffer* buf, int buf_len) override {
    NOTREACHED();
    return 0;
  }
  void ResetInternal() override { NOTREACHED(); }

 private:
  DISALLOW_COPY_AND_ASSIGN(DummyUploadDataStream);
};

class FakeUploadProgressTracker : public network::UploadProgressTracker {
 public:
  using network::UploadProgressTracker::UploadProgressTracker;

  net::UploadProgress GetUploadProgress() const override {
    return upload_progress_;
  }
  base::TimeTicks GetCurrentTime() const override { return current_time_; }

  net::UploadProgress upload_progress_;
  base::TimeTicks current_time_;

 private:
  DISALLOW_COPY_AND_ASSIGN(FakeUploadProgressTracker);
};

class TestResourceDispatcherHostDelegate final
    : public ResourceDispatcherHostDelegate {
 public:
  TestResourceDispatcherHostDelegate() = default;
  ~TestResourceDispatcherHostDelegate() override = default;

  void RequestBeginning(
      net::URLRequest* request,
      ResourceContext* resource_context,
      AppCacheService* appcache_service,
      ResourceType resource_type,
      std::vector<std::unique_ptr<ResourceThrottle>>* throttles) override {
    ADD_FAILURE() << "RequestBeginning should not be called.";
  }

  void DownloadStarting(
      net::URLRequest* request,
      ResourceContext* resource_context,
      bool is_content_initiated,
      bool must_download,
      bool is_new_request,
      std::vector<std::unique_ptr<ResourceThrottle>>* throttles) override {
    ADD_FAILURE() << "DownloadStarting should not be called.";
  }

  bool ShouldInterceptResourceAsStream(net::URLRequest* request,
                                       const std::string& mime_type,
                                       GURL* origin,
                                       std::string* payload) override {
    ADD_FAILURE() << "ShouldInterceptResourceAsStream should not be called.";
    return false;
  }

  void OnStreamCreated(net::URLRequest* request,
                       std::unique_ptr<content::StreamInfo> stream) override {
    ADD_FAILURE() << "OnStreamCreated should not be called.";
  }

  void OnResponseStarted(net::URLRequest* request,
                         ResourceContext* resource_context,
                         network::ResourceResponse* response) override {}

  void OnRequestRedirected(const GURL& redirect_url,
                           net::URLRequest* request,
                           ResourceContext* resource_context,
                           network::ResourceResponse* response) override {
    ADD_FAILURE() << "OnRequestRedirected should not be called.";
  }

  void RequestComplete(net::URLRequest* url_request) override {
    ADD_FAILURE() << "RequestComplete should not be called.";
  }

  NavigationData* GetNavigationData(net::URLRequest* request) const override {
    ADD_FAILURE() << "GetNavigationData should not be called.";
    return nullptr;
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(TestResourceDispatcherHostDelegate);
};

class MojoAsyncResourceHandlerWithStubOperations
    : public MojoAsyncResourceHandler {
 public:
  MojoAsyncResourceHandlerWithStubOperations(
      net::URLRequest* request,
      ResourceDispatcherHostImpl* rdh,
      network::mojom::URLLoaderRequest mojo_request,
      network::mojom::URLLoaderClientPtr url_loader_client,
      uint32_t options)
      : MojoAsyncResourceHandler(request,
                                 rdh,
                                 std::move(mojo_request),
                                 std::move(url_loader_client),
                                 RESOURCE_TYPE_MAIN_FRAME,
                                 options),
        task_runner_(new base::TestSimpleTaskRunner) {}
  ~MojoAsyncResourceHandlerWithStubOperations() override {}

  void ResetBeginWriteExpectation() { is_begin_write_expectation_set_ = false; }

  void set_begin_write_expectation(MojoResult begin_write_expectation) {
    is_begin_write_expectation_set_ = true;
    begin_write_expectation_ = begin_write_expectation;
  }
  void set_end_write_expectation(MojoResult end_write_expectation) {
    is_end_write_expectation_set_ = true;
    end_write_expectation_ = end_write_expectation;
  }
  bool has_received_bad_message() const { return has_received_bad_message_; }
  void SetMetadata(scoped_refptr<net::IOBufferWithSize> metadata) {
    metadata_ = std::move(metadata);
  }

  FakeUploadProgressTracker* upload_progress_tracker() const {
    return upload_progress_tracker_;
  }

  void PollUploadProgress() {
    task_runner_->RunPendingTasks();
    base::RunLoop().RunUntilIdle();
  }

 private:
  MojoResult BeginWrite(void** data, uint32_t* available) override {
    if (is_begin_write_expectation_set_)
      return begin_write_expectation_;
    return MojoAsyncResourceHandler::BeginWrite(data, available);
  }
  MojoResult EndWrite(uint32_t written) override {
    if (is_end_write_expectation_set_)
      return end_write_expectation_;
    return MojoAsyncResourceHandler::EndWrite(written);
  }
  net::IOBufferWithSize* GetResponseMetadata(
      net::URLRequest* request) override {
    return metadata_.get();
  }

  void ReportBadMessage(const std::string& error) override {
    has_received_bad_message_ = true;
  }

  std::unique_ptr<network::UploadProgressTracker> CreateUploadProgressTracker(
      const base::Location& from_here,
      network::UploadProgressTracker::UploadProgressReportCallback callback)
      override {
    DCHECK(!upload_progress_tracker_);

    auto upload_progress_tracker = std::make_unique<FakeUploadProgressTracker>(
        from_here, std::move(callback), request(), task_runner_);
    upload_progress_tracker_ = upload_progress_tracker.get();
    return std::move(upload_progress_tracker);
  }

  bool is_begin_write_expectation_set_ = false;
  bool is_end_write_expectation_set_ = false;
  bool has_received_bad_message_ = false;
  MojoResult begin_write_expectation_ = MOJO_RESULT_UNKNOWN;
  MojoResult end_write_expectation_ = MOJO_RESULT_UNKNOWN;
  scoped_refptr<net::IOBufferWithSize> metadata_;

  FakeUploadProgressTracker* upload_progress_tracker_ = nullptr;
  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;

  DISALLOW_COPY_AND_ASSIGN(MojoAsyncResourceHandlerWithStubOperations);
};

class TestURLLoaderFactory final : public network::mojom::URLLoaderFactory {
 public:
  TestURLLoaderFactory() {}
  ~TestURLLoaderFactory() override {}

  void CreateLoaderAndStart(network::mojom::URLLoaderRequest request,
                            int32_t routing_id,
                            int32_t request_id,
                            uint32_t options,
                            const network::ResourceRequest& url_request,
                            network::mojom::URLLoaderClientPtr client_ptr,
                            const net::MutableNetworkTrafficAnnotationTag&
                                traffic_annotation) override {
    loader_request_ = std::move(request);
    client_ptr_ = std::move(client_ptr);
  }

  network::mojom::URLLoaderRequest PassLoaderRequest() {
    return std::move(loader_request_);
  }

  network::mojom::URLLoaderClientPtr PassClientPtr() {
    return std::move(client_ptr_);
  }

  void Clone(network::mojom::URLLoaderFactoryRequest request) override {
    NOTREACHED();
  }

 private:
  network::mojom::URLLoaderRequest loader_request_;
  network::mojom::URLLoaderClientPtr client_ptr_;

  DISALLOW_COPY_AND_ASSIGN(TestURLLoaderFactory);
};

class MojoAsyncResourceHandlerTestBase {
 public:
  MojoAsyncResourceHandlerTestBase(
      std::unique_ptr<net::UploadDataStream> upload_stream,
      uint32_t options)
      : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP),
        browser_context_(new TestBrowserContext()) {
    MojoAsyncResourceHandler::SetAllocationSizeForTesting(32 * 1024);
    rdh_.SetDelegate(&rdh_delegate_);

    // Create and initialize |request_|.  None of this matters, for these tests,
    // just need something non-NULL.
    request_context_ =
        browser_context_->GetRequestContext()->GetURLRequestContext();
    request_ = request_context_->CreateRequest(
        GURL("http://foo/"), net::DEFAULT_PRIORITY, &url_request_delegate_,
        TRAFFIC_ANNOTATION_FOR_TESTS);
    request_->set_upload(std::move(upload_stream));
    ResourceRequestInfo::AllocateForTesting(
        request_.get(),                          // request
        RESOURCE_TYPE_XHR,                       // resource_type
        browser_context_->GetResourceContext(),  // context
        kChildId,                                // render_process_id
        kRouteId,                                // render_view_id
        0,                                       // render_frame_id
        true,                                    // is_main_frame
        false,                                   // allow_download
        true,                                    // is_async
        PREVIEWS_OFF,                            // previews_state
        nullptr);                                // navigation_ui_data

    network::ResourceRequest request;
    base::WeakPtr<mojo::StrongBinding<network::mojom::URLLoaderFactory>>
        weak_binding =
            mojo::MakeStrongBinding(std::make_unique<TestURLLoaderFactory>(),
                                    mojo::MakeRequest(&url_loader_factory_));

    url_loader_factory_->CreateLoaderAndStart(
        mojo::MakeRequest(&url_loader_proxy_), kRouteId, kRequestId,
        network::mojom::kURLLoadOptionNone, request,
        url_loader_client_.CreateInterfacePtr(),
        net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));

    url_loader_factory_.FlushForTesting();
    DCHECK(weak_binding);
    TestURLLoaderFactory* factory_impl =
        static_cast<TestURLLoaderFactory*>(weak_binding->impl());

    handler_.reset(new MojoAsyncResourceHandlerWithStubOperations(
        request_.get(), &rdh_, factory_impl->PassLoaderRequest(),
        factory_impl->PassClientPtr(), options));
    mock_loader_.reset(new MockResourceLoader(handler_.get()));
  }

  virtual ~MojoAsyncResourceHandlerTestBase() {
    MojoAsyncResourceHandler::SetAllocationSizeForTesting(
        MojoAsyncResourceHandler::kDefaultAllocationSize);
    base::RunLoop().RunUntilIdle();
  }

  // Returns false if something bad happens.
  bool CallOnWillStart() {
    MockResourceLoader::Status result =
        mock_loader_->OnWillStart(request_->url());
    EXPECT_EQ(MockResourceLoader::Status::IDLE, result);
    return result == MockResourceLoader::Status::IDLE;
  }

  // Returns false if something bad happens.
  bool CallOnResponseStarted() {
    MockResourceLoader::Status result = mock_loader_->OnResponseStarted(
        base::MakeRefCounted<network::ResourceResponse>());
    EXPECT_EQ(MockResourceLoader::Status::IDLE, result);
    if (result != MockResourceLoader::Status::IDLE)
      return false;

    if (url_loader_client_.has_received_response()) {
      ADD_FAILURE() << "URLLoaderClient unexpectedly gets a response.";
      return false;
    }
    url_loader_client_.RunUntilResponseReceived();
    return true;
  }

  // Returns false if something bad happens.
  bool CallOnWillStartAndOnResponseStarted() {
    return CallOnWillStart() && CallOnResponseStarted();
  }

  void set_upload_progress(const net::UploadProgress& upload_progress) {
    handler_->upload_progress_tracker()->upload_progress_ = upload_progress;
  }
  void AdvanceCurrentTime(const base::TimeDelta& delta) {
    handler_->upload_progress_tracker()->current_time_ += delta;
  }

  void SetupRequestSSLInfo() {
    net::CertificateList certs;
    ASSERT_TRUE(net::LoadCertificateFiles({"multi-root-B-by-C.pem"}, &certs));
    ASSERT_EQ(1U, certs.size());
    const_cast<net::SSLInfo&>(request_->ssl_info()).cert = certs[0];
  }

  TestBrowserThreadBundle thread_bundle_;
  TestResourceDispatcherHostDelegate rdh_delegate_;
  ResourceDispatcherHostImpl rdh_;
  network::mojom::URLLoaderFactoryPtr url_loader_factory_;
  network::mojom::URLLoaderPtr url_loader_proxy_;
  network::TestURLLoaderClient url_loader_client_;
  std::unique_ptr<TestBrowserContext> browser_context_;
  net::TestDelegate url_request_delegate_;
  net::URLRequestContext* request_context_;
  std::unique_ptr<net::URLRequest> request_;
  std::unique_ptr<MojoAsyncResourceHandlerWithStubOperations> handler_;
  std::unique_ptr<MockResourceLoader> mock_loader_;

  static constexpr int kChildId = 25;
  static constexpr int kRouteId = 12;
  static constexpr int kRequestId = 41;

  DISALLOW_COPY_AND_ASSIGN(MojoAsyncResourceHandlerTestBase);
};

class MojoAsyncResourceHandlerTest : public MojoAsyncResourceHandlerTestBase,
                                     public ::testing::Test {
 protected:
  MojoAsyncResourceHandlerTest()
      : MojoAsyncResourceHandlerTestBase(nullptr,
                                         network::mojom::kURLLoadOptionNone) {}
};

class MojoAsyncResourceHandlerDeferOnResponseStartedTest
    : public MojoAsyncResourceHandlerTestBase,
      public ::testing::Test {
 protected:
  MojoAsyncResourceHandlerDeferOnResponseStartedTest()
      : MojoAsyncResourceHandlerTestBase(
            nullptr,
            network::mojom::kURLLoadOptionPauseOnResponseStarted) {}
};

class MojoAsyncResourceHandlerSendSSLInfoWithResponseTest
    : public MojoAsyncResourceHandlerTestBase,
      public ::testing::Test {
 protected:
  MojoAsyncResourceHandlerSendSSLInfoWithResponseTest()
      : MojoAsyncResourceHandlerTestBase(
            nullptr,
            network::mojom::kURLLoadOptionSendSSLInfoWithResponse) {}
};

class MojoAsyncResourceHandlerSendSSLInfoForCertificateError
    : public MojoAsyncResourceHandlerTestBase,
      public ::testing::Test {
 protected:
  MojoAsyncResourceHandlerSendSSLInfoForCertificateError()
      : MojoAsyncResourceHandlerTestBase(
            nullptr,
            network::mojom::kURLLoadOptionSendSSLInfoForCertificateError) {}
};

// This test class is parameterized with MojoAsyncResourceHandler's allocation
// size.
class MojoAsyncResourceHandlerWithAllocationSizeTest
    : public MojoAsyncResourceHandlerTestBase,
      public ::testing::TestWithParam<size_t> {
 protected:
  MojoAsyncResourceHandlerWithAllocationSizeTest()
      : MojoAsyncResourceHandlerTestBase(nullptr,
                                         network::mojom::kURLLoadOptionNone) {
    MojoAsyncResourceHandler::SetAllocationSizeForTesting(GetParam());
  }
};

class MojoAsyncResourceHandlerUploadTest
    : public MojoAsyncResourceHandlerTestBase,
      public ::testing::Test {
 protected:
  MojoAsyncResourceHandlerUploadTest()
      : MojoAsyncResourceHandlerTestBase(
            std::make_unique<DummyUploadDataStream>(),
            network::mojom::kURLLoadOptionNone) {}
};

TEST_F(MojoAsyncResourceHandlerTest, InFlightRequests) {
  EXPECT_EQ(0, rdh_.num_in_flight_requests_for_testing());
  handler_ = nullptr;
  EXPECT_EQ(0, rdh_.num_in_flight_requests_for_testing());
}

TEST_F(MojoAsyncResourceHandlerTest, OnWillStart) {
  EXPECT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnWillStart(request_->url()));
}

TEST_F(MojoAsyncResourceHandlerTest, OnResponseStarted) {
  scoped_refptr<net::IOBufferWithSize> metadata =
      base::MakeRefCounted<net::IOBufferWithSize>(5);
  memcpy(metadata->data(), "hello", 5);
  handler_->SetMetadata(metadata);

  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnWillStart(request_->url()));

  scoped_refptr<network::ResourceResponse> response =
      new network::ResourceResponse();
  response->head.content_length = 99;
  response->head.request_start =
      base::TimeTicks::UnixEpoch() + base::TimeDelta::FromDays(14);
  response->head.response_start =
      base::TimeTicks::UnixEpoch() + base::TimeDelta::FromDays(28);

  base::TimeTicks now1 = base::TimeTicks::Now();
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseStarted(response));
  base::TimeTicks now2 = base::TimeTicks::Now();

  EXPECT_EQ(request_->creation_time(), response->head.request_start);
  EXPECT_LE(now1, response->head.response_start);
  EXPECT_LE(response->head.response_start, now2);

  url_loader_client_.RunUntilResponseReceived();
  EXPECT_EQ(response->head.request_start,
            url_loader_client_.response_head().request_start);
  EXPECT_EQ(response->head.response_start,
            url_loader_client_.response_head().response_start);
  EXPECT_EQ(99, url_loader_client_.response_head().content_length);

  url_loader_client_.RunUntilCachedMetadataReceived();
  EXPECT_EQ("hello", url_loader_client_.cached_metadata());

  EXPECT_FALSE(url_loader_client_.has_received_upload_progress());
}

TEST_F(MojoAsyncResourceHandlerTest, OnWillReadAndInFlightRequests) {
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  EXPECT_EQ(0, rdh_.num_in_flight_requests_for_testing());
  ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());
  EXPECT_EQ(1, rdh_.num_in_flight_requests_for_testing());
  handler_ = nullptr;
  EXPECT_EQ(0, rdh_.num_in_flight_requests_for_testing());
}

TEST_F(MojoAsyncResourceHandlerTest, OnWillReadWithInsufficientResource) {
  rdh_.set_max_num_in_flight_requests_per_process(0);
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());

  ASSERT_EQ(MockResourceLoader::Status::CANCELED, mock_loader_->OnWillRead());
  EXPECT_EQ(net::ERR_INSUFFICIENT_RESOURCES, mock_loader_->error_code());
  EXPECT_EQ(1, rdh_.num_in_flight_requests_for_testing());
  handler_ = nullptr;
  EXPECT_EQ(0, rdh_.num_in_flight_requests_for_testing());
}

TEST_F(MojoAsyncResourceHandlerTest, OnWillReadAndOnReadCompleted) {
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());
  // The buffer size that the mime sniffer requires implicitly.
  ASSERT_GE(mock_loader_->io_buffer_size(),
            kSizeMimeSnifferRequiresForFirstOnWillRead);

  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnReadCompleted("AB"));

  url_loader_client_.RunUntilResponseBodyArrived();
  ASSERT_TRUE(url_loader_client_.response_body().is_valid());

  std::string contents;
  while (contents.size() < 2) {
    char buffer[16];
    uint32_t read_size = sizeof(buffer);
    MojoResult result = url_loader_client_.response_body().ReadData(
        buffer, &read_size, MOJO_READ_DATA_FLAG_NONE);
    if (result == MOJO_RESULT_SHOULD_WAIT) {
      base::RunLoop().RunUntilIdle();
      continue;
    }
    contents.append(buffer, read_size);
  }
  EXPECT_EQ("AB", contents);
}

TEST_F(MojoAsyncResourceHandlerTest,
       OnWillReadAndOnReadCompletedWithInsufficientInitialCapacity) {
  MojoAsyncResourceHandler::SetAllocationSizeForTesting(2);

  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());
  // The buffer size that the mime sniffer requires implicitly.
  ASSERT_GE(mock_loader_->io_buffer_size(),
            kSizeMimeSnifferRequiresForFirstOnWillRead);

  const std::string data("abcdefgh");
  ASSERT_EQ(MockResourceLoader::Status::CALLBACK_PENDING,
            mock_loader_->OnReadCompleted(data));

  url_loader_client_.RunUntilResponseBodyArrived();
  ASSERT_TRUE(url_loader_client_.response_body().is_valid());

  std::string contents;
  while (contents.size() < data.size()) {
    // This is needed for Resume to be called.
    base::RunLoop().RunUntilIdle();
    char buffer[16];
    uint32_t read_size = sizeof(buffer);
    MojoResult result = url_loader_client_.response_body().ReadData(
        buffer, &read_size, MOJO_READ_DATA_FLAG_NONE);
    if (result == MOJO_RESULT_SHOULD_WAIT)
      continue;
    ASSERT_EQ(MOJO_RESULT_OK, result);
    contents.append(buffer, read_size);
  }
  EXPECT_EQ(data, contents);
  EXPECT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->status());
}

TEST_F(MojoAsyncResourceHandlerTest,
       IOBufferFromOnWillReadShouldRemainValidEvenIfHandlerIsGone) {
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());
  // The io_buffer size that the mime sniffer requires implicitly.
  ASSERT_GE(mock_loader_->io_buffer_size(),
            kSizeMimeSnifferRequiresForFirstOnWillRead);

  handler_ = nullptr;
  url_loader_client_.Unbind();
  base::RunLoop().RunUntilIdle();

  // Hopefully ASAN checks this operation's validity.
  mock_loader_->io_buffer()->data()[0] = 'A';
}

TEST_F(MojoAsyncResourceHandlerTest, OnResponseCompleted) {
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());

  net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, net::OK);

  base::TimeTicks now1 = base::TimeTicks::Now();
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseCompleted(status));
  base::TimeTicks now2 = base::TimeTicks::Now();

  url_loader_client_.RunUntilComplete();
  EXPECT_TRUE(url_loader_client_.has_received_completion());
  EXPECT_EQ(net::OK, url_loader_client_.completion_status().error_code);
  EXPECT_LE(now1, url_loader_client_.completion_status().completion_time);
  EXPECT_LE(url_loader_client_.completion_status().completion_time, now2);
  EXPECT_EQ(request_->GetTotalReceivedBytes(),
            url_loader_client_.completion_status().encoded_data_length);
}

// This test case sets different status values from OnResponseCompleted.
TEST_F(MojoAsyncResourceHandlerTest, OnResponseCompleted2) {
  rdh_.SetDelegate(nullptr);
  // Don't use CallOnWillStartAndOnResponseStarted as this test case manually
  // sets the null delegate.
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnWillStart(request_->url()));
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseStarted(
                base::MakeRefCounted<network::ResourceResponse>()));
  ASSERT_FALSE(url_loader_client_.has_received_response());
  url_loader_client_.RunUntilResponseReceived();

  net::URLRequestStatus status(net::URLRequestStatus::CANCELED,
                               net::ERR_ABORTED);

  base::TimeTicks now1 = base::TimeTicks::Now();
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseCompleted(status));
  base::TimeTicks now2 = base::TimeTicks::Now();

  url_loader_client_.RunUntilComplete();
  EXPECT_TRUE(url_loader_client_.has_received_completion());
  EXPECT_EQ(net::ERR_ABORTED,
            url_loader_client_.completion_status().error_code);
  EXPECT_LE(now1, url_loader_client_.completion_status().completion_time);
  EXPECT_LE(url_loader_client_.completion_status().completion_time, now2);
  EXPECT_EQ(request_->GetTotalReceivedBytes(),
            url_loader_client_.completion_status().encoded_data_length);
}

TEST_F(MojoAsyncResourceHandlerTest, OnResponseCompletedWithCanceledTimedOut) {
  net::URLRequestStatus status(net::URLRequestStatus::CANCELED,
                               net::ERR_TIMED_OUT);

  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseCompleted(status));

  url_loader_client_.RunUntilComplete();
  EXPECT_TRUE(url_loader_client_.has_received_completion());
  EXPECT_EQ(net::ERR_TIMED_OUT,
            url_loader_client_.completion_status().error_code);
}

TEST_F(MojoAsyncResourceHandlerTest, OnResponseCompletedWithFailedTimedOut) {
  net::URLRequestStatus status(net::URLRequestStatus::FAILED,
                               net::ERR_TIMED_OUT);

  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseCompleted(status));

  url_loader_client_.RunUntilComplete();
  EXPECT_TRUE(url_loader_client_.has_received_completion());
  EXPECT_EQ(net::ERR_TIMED_OUT,
            url_loader_client_.completion_status().error_code);
}

TEST_F(MojoAsyncResourceHandlerTest, ResponseCompletionShouldCloseDataPipe) {
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());

  ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnReadCompleted("AB"));
  url_loader_client_.RunUntilResponseBodyArrived();
  ASSERT_TRUE(url_loader_client_.response_body().is_valid());

  net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, net::OK);
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseCompleted(status));

  url_loader_client_.RunUntilComplete();
  EXPECT_TRUE(url_loader_client_.has_received_completion());
  EXPECT_EQ(net::OK, url_loader_client_.completion_status().error_code);

  while (true) {
    char buffer[16];
    uint32_t read_size = sizeof(buffer);
    MojoResult result = url_loader_client_.response_body().ReadData(
        buffer, &read_size, MOJO_READ_DATA_FLAG_NONE);
    if (result == MOJO_RESULT_FAILED_PRECONDITION)
      break;
    ASSERT_TRUE(result == MOJO_RESULT_SHOULD_WAIT || result == MOJO_RESULT_OK);
  }
}

TEST_F(MojoAsyncResourceHandlerTest, OutOfBandCancelDuringBodyTransmission) {
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());

  ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());
  std::string data(mock_loader_->io_buffer_size(), 'a');
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnReadCompleted(data));
  ASSERT_EQ(MockResourceLoader::Status::CALLBACK_PENDING,
            mock_loader_->OnWillRead());
  url_loader_client_.RunUntilResponseBodyArrived();
  ASSERT_TRUE(url_loader_client_.response_body().is_valid());

  net::URLRequestStatus status(net::URLRequestStatus::FAILED, net::ERR_FAILED);
  ASSERT_EQ(
      MockResourceLoader::Status::IDLE,
      mock_loader_->OnResponseCompletedFromExternalOutOfBandCancel(status));

  url_loader_client_.RunUntilComplete();
  EXPECT_TRUE(url_loader_client_.has_received_completion());
  EXPECT_EQ(net::ERR_FAILED, url_loader_client_.completion_status().error_code);

  std::string actual;
  while (true) {
    char buf[16];
    uint32_t read_size = sizeof(buf);
    MojoResult result = url_loader_client_.response_body().ReadData(
        buf, &read_size, MOJO_READ_DATA_FLAG_NONE);
    if (result == MOJO_RESULT_FAILED_PRECONDITION)
      break;
    if (result == MOJO_RESULT_SHOULD_WAIT) {
      base::RunLoop().RunUntilIdle();
      continue;
    }
    EXPECT_EQ(MOJO_RESULT_OK, result);
    actual.append(buf, read_size);
  }
  EXPECT_EQ(data, actual);
}

TEST_F(MojoAsyncResourceHandlerTest, BeginWriteFailsOnWillRead) {
  handler_->set_begin_write_expectation(MOJO_RESULT_UNKNOWN);
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  ASSERT_EQ(MockResourceLoader::Status::CANCELED, mock_loader_->OnWillRead());
}

TEST_F(MojoAsyncResourceHandlerTest, BeginWriteReturnsShouldWaitOnWillRead) {
  handler_->set_begin_write_expectation(MOJO_RESULT_SHOULD_WAIT);

  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  EXPECT_EQ(0, rdh_.num_in_flight_requests_for_testing());

  // Bytes are read one-at-a-time, and each OnWillRead() call completes
  // asynchronously. Note that this loop runs 4 times (once for the terminal
  // '\0').
  const char kReadData[] = "ABC";
  for (const char read_char : kReadData) {
    handler_->set_begin_write_expectation(MOJO_RESULT_SHOULD_WAIT);
    ASSERT_EQ(MockResourceLoader::Status::CALLBACK_PENDING,
              mock_loader_->OnWillRead());
    EXPECT_EQ(1, rdh_.num_in_flight_requests_for_testing());

    handler_->ResetBeginWriteExpectation();
    handler_->OnWritableForTesting();
    EXPECT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->status());

    ASSERT_EQ(MockResourceLoader::Status::IDLE,
              mock_loader_->OnReadCompleted(std::string(1, read_char)));
    url_loader_client_.RunUntilResponseBodyArrived();

    // Keep on trying to read the data until it succeeds.
    while (true) {
      char buffer[16];
      uint32_t read_size = sizeof(buffer);
      MojoResult result = url_loader_client_.response_body().ReadData(
          buffer, &read_size, MOJO_READ_DATA_FLAG_NONE);
      if (result != MOJO_RESULT_SHOULD_WAIT) {
        ASSERT_EQ(MOJO_RESULT_OK, result);
        ASSERT_EQ(1u, read_size);
        EXPECT_EQ(read_char, buffer[0]);
        break;
      }

      base::RunLoop().RunUntilIdle();
    }
  }

  // Should only count as one in-flight request.
  EXPECT_EQ(1, rdh_.num_in_flight_requests_for_testing());

  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseCompleted(
                net::URLRequestStatus::FromError(net::OK)));

  url_loader_client_.RunUntilComplete();
  EXPECT_TRUE(url_loader_client_.has_received_completion());

  handler_.reset();
  EXPECT_EQ(0, rdh_.num_in_flight_requests_for_testing());
}

// Same as above, but after the first OnWriteable() call, BeginWrite() indicates
// should wait again. Unclear if this can happen in practice, but seems best to
// support it.
TEST_F(MojoAsyncResourceHandlerTest,
       BeginWriteReturnsShouldWaitTwiceOnWillRead) {
  handler_->set_begin_write_expectation(MOJO_RESULT_SHOULD_WAIT);

  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  EXPECT_EQ(0, rdh_.num_in_flight_requests_for_testing());

  // Bytes are read one-at-a-time, and each OnWillRead() call completes
  // asynchronously. Note that this loop runs 4 times (once for the terminal
  // '\0').
  const char kReadData[] = "ABC";
  for (const char read_char : kReadData) {
    handler_->set_begin_write_expectation(MOJO_RESULT_SHOULD_WAIT);
    ASSERT_EQ(MockResourceLoader::Status::CALLBACK_PENDING,
              mock_loader_->OnWillRead());
    EXPECT_EQ(1, rdh_.num_in_flight_requests_for_testing());

    handler_->OnWritableForTesting();
    EXPECT_EQ(MockResourceLoader::Status::CALLBACK_PENDING,
              mock_loader_->status());

    handler_->ResetBeginWriteExpectation();
    handler_->OnWritableForTesting();
    EXPECT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->status());

    ASSERT_EQ(MockResourceLoader::Status::IDLE,
              mock_loader_->OnReadCompleted(std::string(1, read_char)));
    url_loader_client_.RunUntilResponseBodyArrived();

    // Keep on trying to read the data until it succeeds.
    while (true) {
      char buffer[16];
      uint32_t read_size = sizeof(buffer);
      MojoResult result = url_loader_client_.response_body().ReadData(
          buffer, &read_size, MOJO_READ_DATA_FLAG_NONE);
      if (result != MOJO_RESULT_SHOULD_WAIT) {
        ASSERT_EQ(MOJO_RESULT_OK, result);
        ASSERT_EQ(1u, read_size);
        EXPECT_EQ(read_char, buffer[0]);
        break;
      }

      base::RunLoop().RunUntilIdle();
    }
  }

  // Should only count as one in-flight request.
  EXPECT_EQ(1, rdh_.num_in_flight_requests_for_testing());

  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseCompleted(
                net::URLRequestStatus::FromError(net::OK)));

  url_loader_client_.RunUntilComplete();
  EXPECT_TRUE(url_loader_client_.has_received_completion());

  handler_.reset();
  EXPECT_EQ(0, rdh_.num_in_flight_requests_for_testing());
}

TEST_F(MojoAsyncResourceHandlerTest,
       EndWriteFailsOnWillReadWithInsufficientInitialCapacity) {
  MojoAsyncResourceHandler::SetAllocationSizeForTesting(2);
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  handler_->set_end_write_expectation(MOJO_RESULT_UNKNOWN);
  ASSERT_EQ(MockResourceLoader::Status::CANCELED, mock_loader_->OnWillRead());
}

TEST_F(MojoAsyncResourceHandlerTest, EndWriteFailsOnReadCompleted) {
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());

  handler_->set_end_write_expectation(MOJO_RESULT_SHOULD_WAIT);
  ASSERT_EQ(MockResourceLoader::Status::CANCELED,
            mock_loader_->OnReadCompleted(
                std::string(mock_loader_->io_buffer_size(), 'w')));
}

TEST_F(MojoAsyncResourceHandlerTest,
       EndWriteFailsOnReadCompletedWithInsufficientInitialCapacity) {
  MojoAsyncResourceHandler::SetAllocationSizeForTesting(2);
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());

  handler_->set_end_write_expectation(MOJO_RESULT_SHOULD_WAIT);
  ASSERT_EQ(MockResourceLoader::Status::CANCELED,
            mock_loader_->OnReadCompleted(
                std::string(mock_loader_->io_buffer_size(), 'w')));
}

TEST_F(MojoAsyncResourceHandlerTest,
       EndWriteFailsOnResumeWithInsufficientInitialCapacity) {
  MojoAsyncResourceHandler::SetAllocationSizeForTesting(8);
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());

  while (true) {
    MockResourceLoader::Status result = mock_loader_->OnReadCompleted(
        std::string(mock_loader_->io_buffer_size(), 'A'));
    if (result == MockResourceLoader::Status::CALLBACK_PENDING)
      break;
    ASSERT_EQ(MockResourceLoader::Status::IDLE, result);

    ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());
  }

  url_loader_client_.RunUntilResponseBodyArrived();
  ASSERT_TRUE(url_loader_client_.response_body().is_valid());

  while (true) {
    char buf[16];
    uint32_t read_size = sizeof(buf);
    MojoResult result = url_loader_client_.response_body().ReadData(
        buf, &read_size, MOJO_READ_DATA_FLAG_NONE);
    if (result == MOJO_RESULT_SHOULD_WAIT)
      break;
    ASSERT_EQ(MOJO_RESULT_OK, result);
  }

  handler_->set_end_write_expectation(MOJO_RESULT_SHOULD_WAIT);
  mock_loader_->WaitUntilIdleOrCanceled();
  EXPECT_FALSE(url_loader_client_.has_received_completion());
  EXPECT_EQ(MockResourceLoader::Status::CANCELED, mock_loader_->status());
  EXPECT_EQ(net::ERR_INSUFFICIENT_RESOURCES, mock_loader_->error_code());
}

TEST_F(MojoAsyncResourceHandlerUploadTest, UploadProgressHandling) {
  ASSERT_TRUE(CallOnWillStart());

  // Expect no report for no progress.
  set_upload_progress(net::UploadProgress(0, 1000));
  handler_->PollUploadProgress();
  EXPECT_FALSE(url_loader_client_.has_received_upload_progress());
  EXPECT_EQ(0, url_loader_client_.current_upload_position());
  EXPECT_EQ(0, url_loader_client_.total_upload_size());

  // Expect a upload progress report for a good amount of progress.
  url_loader_client_.reset_has_received_upload_progress();
  set_upload_progress(net::UploadProgress(100, 1000));
  handler_->PollUploadProgress();
  EXPECT_TRUE(url_loader_client_.has_received_upload_progress());
  EXPECT_EQ(100, url_loader_client_.current_upload_position());
  EXPECT_EQ(1000, url_loader_client_.total_upload_size());

  // Expect a upload progress report for the passed time.
  url_loader_client_.reset_has_received_upload_progress();
  set_upload_progress(net::UploadProgress(101, 1000));
  AdvanceCurrentTime(base::TimeDelta::FromSeconds(5));
  handler_->PollUploadProgress();
  EXPECT_TRUE(url_loader_client_.has_received_upload_progress());
  EXPECT_EQ(101, url_loader_client_.current_upload_position());
  EXPECT_EQ(1000, url_loader_client_.total_upload_size());

  // A redirect rewinds the upload progress. Expect no report for the rewound
  // progress.
  url_loader_client_.reset_has_received_upload_progress();
  set_upload_progress(net::UploadProgress(0, 1000));
  AdvanceCurrentTime(base::TimeDelta::FromSeconds(5));
  handler_->PollUploadProgress();
  EXPECT_FALSE(url_loader_client_.has_received_upload_progress());

  // Set the progress to almost-finished state to prepare for the completion
  // report below.
  url_loader_client_.reset_has_received_upload_progress();
  set_upload_progress(net::UploadProgress(999, 1000));
  handler_->PollUploadProgress();
  EXPECT_TRUE(url_loader_client_.has_received_upload_progress());
  EXPECT_EQ(999, url_loader_client_.current_upload_position());
  EXPECT_EQ(1000, url_loader_client_.total_upload_size());

  // Expect a upload progress report for the upload completion.
  url_loader_client_.reset_has_received_upload_progress();
  set_upload_progress(net::UploadProgress(1000, 1000));
  ASSERT_TRUE(CallOnResponseStarted());
  EXPECT_TRUE(url_loader_client_.has_received_upload_progress());
  EXPECT_EQ(1000, url_loader_client_.current_upload_position());
  EXPECT_EQ(1000, url_loader_client_.total_upload_size());
}

TEST_F(MojoAsyncResourceHandlerTest, SetPriority) {
  constexpr int kIntraPriority = 5;
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  auto throttle =
      ResourceDispatcherHostImpl::Get()->scheduler()->ScheduleRequest(
          kChildId, kRouteId, false, request_.get());

  EXPECT_EQ(net::LOWEST, request_->priority());

  handler_->SetPriority(net::RequestPriority::HIGHEST, kIntraPriority);

  EXPECT_EQ(net::HIGHEST, request_->priority());
}

TEST_P(MojoAsyncResourceHandlerWithAllocationSizeTest,
       OnWillReadWithLongContents) {
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());

  std::string expected;
  for (int i = 0; i < 3 * mock_loader_->io_buffer_size() + 2; ++i)
    expected += ('A' + i % 26);

  size_t written = 0;
  std::string actual;
  while (actual.size() < expected.size()) {
    while (written < expected.size() &&
           mock_loader_->status() == MockResourceLoader::Status::IDLE) {
      ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());
      size_t to_be_written =
          std::min(static_cast<size_t>(mock_loader_->io_buffer_size()),
                   expected.size() - written);

      // Request should be resumed or paused.
      ASSERT_NE(MockResourceLoader::Status::CANCELED,
                mock_loader_->OnReadCompleted(
                    expected.substr(written, to_be_written)));

      written += to_be_written;
    }
    if (!url_loader_client_.response_body().is_valid()) {
      url_loader_client_.RunUntilResponseBodyArrived();
      ASSERT_TRUE(url_loader_client_.response_body().is_valid());
    }

    char buf[16];
    uint32_t read_size = sizeof(buf);
    MojoResult result = url_loader_client_.response_body().ReadData(
        buf, &read_size, MOJO_READ_DATA_FLAG_NONE);
    if (result != MOJO_RESULT_SHOULD_WAIT) {
      ASSERT_EQ(MOJO_RESULT_OK, result);
      actual.append(buf, read_size);
    }

    // Give mojo a chance pass data back and forth, and to request more data
    // from the handler.
    base::RunLoop().RunUntilIdle();
  }
  EXPECT_EQ(expected, actual);
}

TEST_P(MojoAsyncResourceHandlerWithAllocationSizeTest,
       BeginWriteFailsOnReadCompleted) {
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());

  // Whether the next OnReadCompleted call or OnWillRead returns the error
  // depends on whether or not an intermediary buffer is being used by the
  // MojoAsyncResourceHandler.
  handler_->set_begin_write_expectation(MOJO_RESULT_UNKNOWN);
  MockResourceLoader::Status result = mock_loader_->OnReadCompleted(
      std::string(mock_loader_->io_buffer_size(), 'A'));
  if (result == MockResourceLoader::Status::CANCELED)
    return;
  ASSERT_EQ(MockResourceLoader::Status::IDLE, result);
  ASSERT_EQ(MockResourceLoader::Status::CANCELED, mock_loader_->OnWillRead());
}

TEST_P(MojoAsyncResourceHandlerWithAllocationSizeTest,
       BeginWriteReturnsShouldWaitOnReadCompleted) {
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());

  // Whether the next OnReadCompleted call or OnWillRead call completes
  // asynchronously depends on whether or not an intermediary buffer is being
  // used by the MojoAsyncResourceHandler.
  handler_->set_begin_write_expectation(MOJO_RESULT_SHOULD_WAIT);
  MockResourceLoader::Status result = mock_loader_->OnReadCompleted(
      std::string(mock_loader_->io_buffer_size() - 1, 'A'));
  if (result == MockResourceLoader::Status::CALLBACK_PENDING)
    return;

  ASSERT_EQ(MockResourceLoader::Status::CALLBACK_PENDING,
            mock_loader_->OnWillRead());
}

TEST_P(MojoAsyncResourceHandlerWithAllocationSizeTest,
       BeginWriteFailsOnResume) {
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());

  while (true) {
    // Whether the next OnReadCompleted call or OnWillRead call completes
    // asynchronously depends on whether or not an intermediary buffer is being
    // used by the MojoAsyncResourceHandler.
    MockResourceLoader::Status result = mock_loader_->OnWillRead();
    if (result == MockResourceLoader::Status::CALLBACK_PENDING)
      break;
    ASSERT_EQ(MockResourceLoader::Status::IDLE, result);
    result = mock_loader_->OnReadCompleted(
        std::string(mock_loader_->io_buffer_size(), 'A'));
    if (result == MockResourceLoader::Status::CALLBACK_PENDING)
      break;
    ASSERT_EQ(MockResourceLoader::Status::IDLE, result);
  }
  url_loader_client_.RunUntilResponseBodyArrived();
  ASSERT_TRUE(url_loader_client_.response_body().is_valid());

  handler_->set_begin_write_expectation(MOJO_RESULT_UNKNOWN);

  while (mock_loader_->status() != MockResourceLoader::Status::CANCELED) {
    char buf[256];
    uint32_t read_size = sizeof(buf);
    MojoResult result = url_loader_client_.response_body().ReadData(
        buf, &read_size, MOJO_READ_DATA_FLAG_NONE);
    ASSERT_TRUE(result == MOJO_RESULT_OK || result == MOJO_RESULT_SHOULD_WAIT);
    base::RunLoop().RunUntilIdle();
  }

  if (mock_loader_->status() == MockResourceLoader::Status::IDLE)
    EXPECT_EQ(MockResourceLoader::Status::CANCELED, mock_loader_->OnWillRead());

  EXPECT_FALSE(url_loader_client_.has_received_completion());
  EXPECT_EQ(net::ERR_INSUFFICIENT_RESOURCES, mock_loader_->error_code());
}

TEST_P(MojoAsyncResourceHandlerWithAllocationSizeTest, CancelWhileWaiting) {
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());

  while (true) {
    // Whether the next OnReadCompleted call or OnWillRead call completes
    // asynchronously depends on whether or not an intermediary buffer is being
    // used by the MojoAsyncResourceHandler.
    MockResourceLoader::Status result = mock_loader_->OnWillRead();
    if (result == MockResourceLoader::Status::CALLBACK_PENDING)
      break;
    ASSERT_EQ(MockResourceLoader::Status::IDLE, result);
    result = mock_loader_->OnReadCompleted(
        std::string(mock_loader_->io_buffer_size(), 'A'));
    if (result == MockResourceLoader::Status::CALLBACK_PENDING)
      break;
    ASSERT_EQ(MockResourceLoader::Status::IDLE, result);
  }

  url_loader_client_.RunUntilResponseBodyArrived();
  ASSERT_TRUE(url_loader_client_.response_body().is_valid());

  net::URLRequestStatus status(net::URLRequestStatus::CANCELED,
                               net::ERR_ABORTED);
  ASSERT_EQ(
      MockResourceLoader::Status::IDLE,
      mock_loader_->OnResponseCompletedFromExternalOutOfBandCancel(status));

  ASSERT_FALSE(url_loader_client_.has_received_completion());
  url_loader_client_.RunUntilComplete();
  EXPECT_EQ(net::ERR_ABORTED,
            url_loader_client_.completion_status().error_code);

  while (true) {
    char buffer[16];
    uint32_t read_size = sizeof(buffer);
    MojoResult result = url_loader_client_.response_body().ReadData(
        buffer, &read_size, MOJO_READ_DATA_FLAG_NONE);
    if (result == MOJO_RESULT_FAILED_PRECONDITION)
      break;
    base::RunLoop().RunUntilIdle();
    DCHECK(result == MOJO_RESULT_SHOULD_WAIT || result == MOJO_RESULT_OK);
  }

  base::RunLoop().RunUntilIdle();
}

TEST_P(MojoAsyncResourceHandlerWithAllocationSizeTest, RedirectHandling) {
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnWillStart(request_->url()));

  net::RedirectInfo redirect_info;
  redirect_info.status_code = 301;
  ASSERT_EQ(
      MockResourceLoader::Status::CALLBACK_PENDING,
      mock_loader_->OnRequestRedirected(
          redirect_info, base::MakeRefCounted<network::ResourceResponse>()));

  ASSERT_FALSE(url_loader_client_.has_received_response());
  ASSERT_FALSE(url_loader_client_.has_received_redirect());
  url_loader_client_.RunUntilRedirectReceived();

  ASSERT_FALSE(url_loader_client_.has_received_response());
  ASSERT_TRUE(url_loader_client_.has_received_redirect());
  EXPECT_EQ(301, url_loader_client_.redirect_info().status_code);

  ASSERT_EQ(MockResourceLoader::Status::CALLBACK_PENDING,
            mock_loader_->status());
  handler_->FollowRedirect({}, {}, base::nullopt);
  ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->status());

  url_loader_client_.ClearHasReceivedRedirect();
  // Redirect once more.
  redirect_info.status_code = 302;
  ASSERT_EQ(
      MockResourceLoader::Status::CALLBACK_PENDING,
      mock_loader_->OnRequestRedirected(
          redirect_info, base::MakeRefCounted<network::ResourceResponse>()));

  ASSERT_FALSE(url_loader_client_.has_received_response());
  ASSERT_FALSE(url_loader_client_.has_received_redirect());
  url_loader_client_.RunUntilRedirectReceived();

  ASSERT_FALSE(url_loader_client_.has_received_response());
  ASSERT_TRUE(url_loader_client_.has_received_redirect());
  EXPECT_EQ(302, url_loader_client_.redirect_info().status_code);

  ASSERT_EQ(MockResourceLoader::Status::CALLBACK_PENDING,
            mock_loader_->status());
  handler_->FollowRedirect({}, {}, base::nullopt);
  ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->status());

  // Give the final response.
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseStarted(
                base::MakeRefCounted<network::ResourceResponse>()));

  net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, net::OK);
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseCompleted(status));

  ASSERT_FALSE(url_loader_client_.has_received_completion());
  url_loader_client_.RunUntilComplete();

  ASSERT_TRUE(url_loader_client_.has_received_response());
  ASSERT_TRUE(url_loader_client_.has_received_completion());
  EXPECT_EQ(net::OK, url_loader_client_.completion_status().error_code);
}

// Test the case where th other process tells the ResourceHandler to follow a
// redirect, despite the fact that no redirect has been received yet.
TEST_P(MojoAsyncResourceHandlerWithAllocationSizeTest,
       MalformedFollowRedirectRequest) {
  handler_->FollowRedirect({}, {}, base::nullopt);

  EXPECT_TRUE(handler_->has_received_bad_message());
}

// Typically ResourceHandler methods are called in this order.
TEST_P(
    MojoAsyncResourceHandlerWithAllocationSizeTest,
    OnWillStartThenOnResponseStartedThenOnWillReadThenOnReadCompletedThenOnResponseCompleted) {

  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnWillStart(request_->url()));
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseStarted(
                base::MakeRefCounted<network::ResourceResponse>()));

  ASSERT_FALSE(url_loader_client_.has_received_response());
  url_loader_client_.RunUntilResponseBodyArrived();

  ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());

  ASSERT_TRUE(url_loader_client_.response_body().is_valid());

  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnReadCompleted("A"));
  url_loader_client_.RunUntilResponseBodyArrived();
  ASSERT_TRUE(url_loader_client_.response_body().is_valid());

  net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, net::OK);
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseCompleted(status));

  ASSERT_FALSE(url_loader_client_.has_received_completion());
  url_loader_client_.RunUntilComplete();
  EXPECT_EQ(net::OK, url_loader_client_.completion_status().error_code);

  std::string body;
  while (true) {
    char buffer[16];
    uint32_t read_size = sizeof(buffer);
    MojoResult result = url_loader_client_.response_body().ReadData(
        buffer, &read_size, MOJO_READ_DATA_FLAG_NONE);
    if (result == MOJO_RESULT_FAILED_PRECONDITION)
      break;
    if (result == MOJO_RESULT_SHOULD_WAIT) {
      base::RunLoop().RunUntilIdle();
    } else {
      ASSERT_EQ(result, MOJO_RESULT_OK);
      body.append(buffer, read_size);
    }
  }
  EXPECT_EQ("A", body);
}

// MimeResourceHandler calls delegated ResourceHandler's methods in this order.
TEST_P(
    MojoAsyncResourceHandlerWithAllocationSizeTest,
    OnWillStartThenOnWillReadThenOnResponseStartedThenOnReadCompletedThenOnResponseCompleted) {

  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnWillStart(request_->url()));

  ASSERT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());

  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseStarted(
                base::MakeRefCounted<network::ResourceResponse>()));

  ASSERT_FALSE(url_loader_client_.has_received_response());
  url_loader_client_.RunUntilResponseReceived();

  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnReadCompleted("B"));

  ASSERT_TRUE(url_loader_client_.has_received_response());
  url_loader_client_.RunUntilResponseBodyArrived();
  ASSERT_TRUE(url_loader_client_.response_body().is_valid());

  net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, net::OK);
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseCompleted(status));

  ASSERT_FALSE(url_loader_client_.has_received_completion());
  url_loader_client_.RunUntilComplete();
  EXPECT_EQ(net::OK, url_loader_client_.completion_status().error_code);

  std::string body;
  while (true) {
    char buffer[16];
    uint32_t read_size = sizeof(buffer);
    MojoResult result = url_loader_client_.response_body().ReadData(
        buffer, &read_size, MOJO_READ_DATA_FLAG_NONE);
    if (result == MOJO_RESULT_FAILED_PRECONDITION)
      break;
    if (result == MOJO_RESULT_SHOULD_WAIT) {
      base::RunLoop().RunUntilIdle();
    } else {
      ASSERT_EQ(result, MOJO_RESULT_OK);
      body.append(buffer, read_size);
    }
  }
  EXPECT_EQ("B", body);
}

TEST_F(MojoAsyncResourceHandlerDeferOnResponseStartedTest,
       ProceedWithResponse) {
  EXPECT_TRUE(CallOnWillStart());

  // On response started, the MojoAsyncResourceHandler should stop loading,
  // since |defer_on_response_started| is true.
  {
    MockResourceLoader::Status result = mock_loader_->OnResponseStarted(
        base::MakeRefCounted<network::ResourceResponse>());
    EXPECT_EQ(MockResourceLoader::Status::CALLBACK_PENDING, result);
    std::unique_ptr<base::Value> request_state = request_->GetStateAsValue();
    base::Value* delegate_blocked_by =
        request_state->FindKey("delegate_blocked_by");
    EXPECT_TRUE(delegate_blocked_by);
    EXPECT_EQ("MojoAsyncResourceHandler", delegate_blocked_by->GetString());
  }

  // When ProceedWithResponse() is called, the MojoAsyncResourceHandler should
  // resume its controller.
  {
    handler_->ProceedWithResponse();
    mock_loader_->WaitUntilIdleOrCanceled();
    EXPECT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->status());
    std::unique_ptr<base::Value> request_state = request_->GetStateAsValue();
    base::Value* delegate_blocked_by =
        request_state->FindKey("delegate_blocked_by");
    EXPECT_FALSE(delegate_blocked_by);
  }
}

// Test that SSLInfo is not attached to OnResponseStarted when there is no
// kURLLoadOptionsSendSSLInfoWithResponse option.
TEST_F(MojoAsyncResourceHandlerTest, SSLInfoOnResponseStarted) {
  SetupRequestSSLInfo();
  EXPECT_TRUE(CallOnWillStartAndOnResponseStarted());
  EXPECT_FALSE(url_loader_client_.ssl_info());
}

// Test that SSLInfo is attached to OnResponseStarted when there is a
// kURLLoadOptionsSendSSLInfoWithResponse option.
TEST_F(MojoAsyncResourceHandlerSendSSLInfoWithResponseTest,
       SSLInfoOnResponseStarted) {
  SetupRequestSSLInfo();
  EXPECT_TRUE(CallOnWillStartAndOnResponseStarted());
  EXPECT_TRUE(url_loader_client_.ssl_info());
}

// Test that SSLInfo is not attached to OnResponseComplete when there is no
// kURLLoadOptionsSendSSLInfoForCertificateError option.
TEST_F(MojoAsyncResourceHandlerTest, SSLInfoOnComplete) {
  EXPECT_TRUE(CallOnWillStart());

  // Simulates the request getting a major SSL error.
  const_cast<net::SSLInfo&>(request_->ssl_info()).cert_status =
      net::CERT_STATUS_AUTHORITY_INVALID;
  ASSERT_EQ(
      MockResourceLoader::Status::IDLE,
      mock_loader_->OnResponseCompleted(net::URLRequestStatus(
          net::URLRequestStatus::CANCELED, net::ERR_CERT_AUTHORITY_INVALID)));

  url_loader_client_.RunUntilComplete();
  EXPECT_FALSE(url_loader_client_.completion_status().ssl_info);
};

// Test that SSLInfo is attached to OnResponseComplete when there is the
// kURLLoadOptionsSendSSLInfoForCertificateError option.
TEST_F(MojoAsyncResourceHandlerSendSSLInfoForCertificateError,
       SSLInfoOnCompleteMajorError) {
  EXPECT_TRUE(CallOnWillStart());

  // Simulates the request getting a major SSL error.
  const_cast<net::SSLInfo&>(request_->ssl_info()).cert_status =
      net::CERT_STATUS_AUTHORITY_INVALID;
  ASSERT_EQ(
      MockResourceLoader::Status::IDLE,
      mock_loader_->OnResponseCompleted(net::URLRequestStatus(
          net::URLRequestStatus::CANCELED, net::ERR_CERT_AUTHORITY_INVALID)));

  url_loader_client_.RunUntilComplete();
  EXPECT_TRUE(url_loader_client_.completion_status().ssl_info);
};

// Test that SSLInfo is not attached to OnResponseComplete when there is the
// kURLLoadOptionsSendSSLInfoForCertificateError option and a minor SSL error.
TEST_F(MojoAsyncResourceHandlerSendSSLInfoForCertificateError,
       SSLInfoOnCompleteMinorError) {
  EXPECT_TRUE(CallOnWillStart());

  // Simulates the request getting a minor SSL error.
  const_cast<net::SSLInfo&>(request_->ssl_info()).cert_status =
      net::CERT_STATUS_UNABLE_TO_CHECK_REVOCATION;

  EXPECT_TRUE(CallOnResponseStarted());
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseCompleted(net::URLRequestStatus()));
  url_loader_client_.RunUntilComplete();
  EXPECT_FALSE(url_loader_client_.completion_status().ssl_info);
};

TEST_F(MojoAsyncResourceHandlerTest,
       TransferSizeUpdateCalledForNonBlockedResponse) {
  net::URLRequestJobFactoryImpl test_job_factory_;
  auto test_job = std::make_unique<net::URLRequestTestJob>(
      request_.get(), request_context_->network_delegate(), "response headers",
      "response body", true);
  auto test_job_interceptor = std::make_unique<net::TestJobInterceptor>();
  net::TestJobInterceptor* raw_test_job_interceptor =
      test_job_interceptor.get();
  EXPECT_TRUE(test_job_factory_.SetProtocolHandler(
      url::kHttpScheme, std::move(test_job_interceptor)));

  request_context_->set_job_factory(&test_job_factory_);
  raw_test_job_interceptor->set_main_intercept_job(std::move(test_job));
  request_->Start();

  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  url_request_delegate_.RunUntilComplete();

  net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, net::OK);
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseCompleted(status));
  url_loader_client_.RunUntilComplete();
  EXPECT_LT(0, url_loader_client_.body_transfer_size());
  EXPECT_EQ(request_->GetTotalReceivedBytes(),
            url_loader_client_.body_transfer_size());
}

TEST_F(MojoAsyncResourceHandlerTest,
       TransferSizeUpdateNotCalledForBlockedResponse) {
  net::URLRequestJobFactoryImpl test_job_factory_;
  auto test_job = std::make_unique<net::URLRequestTestJob>(
      request_.get(), request_context_->network_delegate(), "response headers",
      "response body", true);
  auto test_job_interceptor = std::make_unique<net::TestJobInterceptor>();
  net::TestJobInterceptor* raw_test_job_interceptor =
      test_job_interceptor.get();
  EXPECT_TRUE(test_job_factory_.SetProtocolHandler(
      url::kHttpScheme, std::move(test_job_interceptor)));

  request_context_->set_job_factory(&test_job_factory_);
  raw_test_job_interceptor->set_main_intercept_job(std::move(test_job));
  request_->Start();

  // Block the response to reach renderer.
  ResourceRequestInfoImpl::ForRequest(request_.get())
      ->set_blocked_response_from_reaching_renderer(true);

  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());

  net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, net::OK);
  ASSERT_EQ(MockResourceLoader::Status::IDLE,
            mock_loader_->OnResponseCompleted(status));
  url_request_delegate_.RunUntilComplete();
  EXPECT_TRUE(ResourceRequestInfoImpl::ForRequest(request_.get())
                  ->blocked_response_from_reaching_renderer());
  EXPECT_EQ(0, url_loader_client_.body_transfer_size());
  EXPECT_LT(0, request_->GetTotalReceivedBytes());
}

TEST_F(MojoAsyncResourceHandlerTest,
       TransferSizeUpdateCalledWithoutResponseComplete) {
  const char kResponseHeaders[] = "response headers";
  const char kResponseData[] = "response data";
  // Create a mock timer to control when the final transfersizeupdate is sent.
  auto timer = std::make_unique<base::MockOneShotTimer>();
  auto* raw_timer = timer.get();
  handler_->set_report_transfer_size_async_timer_for_testing(std::move(timer));

  // Create a test job so the underlying URLRequest will receive bytes.
  net::URLRequestJobFactoryImpl test_job_factory_;
  auto test_job = std::make_unique<net::URLRequestTestJob>(
      request_.get(), request_context_->network_delegate(), kResponseHeaders,
      kResponseData, true);
  auto test_job_interceptor = std::make_unique<net::TestJobInterceptor>();
  net::TestJobInterceptor* raw_test_job_interceptor =
      test_job_interceptor.get();
  EXPECT_TRUE(test_job_factory_.SetProtocolHandler(
      url::kHttpScheme, std::move(test_job_interceptor)));
  request_context_->set_job_factory(&test_job_factory_);
  raw_test_job_interceptor->set_main_intercept_job(std::move(test_job));
  request_->Start();

  // Prepare for loader read complete.
  ASSERT_TRUE(CallOnWillStartAndOnResponseStarted());
  EXPECT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());
  // Only headers are read by the time the response is started.
  mock_loader_->OnReadCompleted(kResponseHeaders);

  // Make the loader process another read of the rest of the URLTestJob data.
  EXPECT_EQ(MockResourceLoader::Status::IDLE, mock_loader_->OnWillRead());
  mock_loader_->OnReadCompleted(kResponseData);

  // Process the entire URL request.
  url_request_delegate_.RunUntilComplete();

  // All data received by the request.
  EXPECT_EQ(
      request_->GetTotalReceivedBytes(),
      static_cast<int64_t>(strlen(kResponseHeaders) + strlen(kResponseData)));

  // Wait for a transfer size update to be received.
  url_loader_client_.RunUntilTransferSizeUpdated();
  // Only the first read caused a transfer size update.
  EXPECT_EQ(static_cast<int64_t>(strlen(kResponseHeaders)),
            url_loader_client_.body_transfer_size());
  // Firing the timer will cause the rest of the bytes to be reported.
  // Without timer fire no transfer size updates would be received.
  raw_timer->Fire();
  url_loader_client_.RunUntilTransferSizeUpdated();
  EXPECT_EQ(request_->GetTotalReceivedBytes(),
            url_loader_client_.body_transfer_size());
}

INSTANTIATE_TEST_CASE_P(MojoAsyncResourceHandlerWithAllocationSizeTest,
                        MojoAsyncResourceHandlerWithAllocationSizeTest,
                        ::testing::Values(8, 32 * 2014));
}  // namespace
}  // namespace content
