blob: 1d98b3f10fe84fee9bee3948986e503fa0887029 [file] [log] [blame]
// Copyright 2014 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/child/web_url_loader_impl.h"
#include <string.h>
#include <vector>
#include "base/command_line.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/time/time.h"
#include "content/child/request_extra_data.h"
#include "content/child/request_info.h"
#include "content/child/resource_dispatcher.h"
#include "content/public/child/fixed_received_data.h"
#include "content/public/child/request_peer.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/resource_response_info.h"
#include "net/base/net_errors.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/url_request/redirect_info.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/platform/WebString.h"
#include "third_party/WebKit/public/platform/WebURLError.h"
#include "third_party/WebKit/public/platform/WebURLLoaderClient.h"
#include "third_party/WebKit/public/platform/WebURLRequest.h"
#include "third_party/WebKit/public/platform/WebURLResponse.h"
#include "url/gurl.h"
namespace content {
namespace {
const char kTestURL[] = "http://foo";
const char kTestData[] = "blah!";
const char kFtpDirMimeType[] = "text/vnd.chromium.ftp-dir";
// Simple FTP directory listing. Tests are not concerned with correct parsing,
// but rather correct cleanup when deleted while parsing. Important details of
// this list are that it contains more than one entry that are not "." or "..".
const char kFtpDirListing[] =
"drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 goat\n"
"drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 hat";
const char kMultipartResponseMimeType[] = "multipart/x-mixed-replace";
const char kMultipartResponseHeaders[] =
"HTTP/1.0 200 Peachy\r\n"
"Content-Type: multipart/x-mixed-replace; boundary=boundary\r\n\r\n";
// Simple multipart response. Imporant details for the tests are that it
// contains multiple chunks, and that it doesn't end with a boundary, so will
// send data in OnResponseComplete. Also, it will resolve to kTestData.
const char kMultipartResponse[] =
"--boundary\n"
"Content-type: text/html\n\n"
"bl"
"--boundary\n"
"Content-type: text/html\n\n"
"ah!";
class TestResourceDispatcher : public ResourceDispatcher {
public:
TestResourceDispatcher() :
ResourceDispatcher(nullptr, nullptr),
peer_(NULL),
canceled_(false) {
}
~TestResourceDispatcher() override {}
// TestDispatcher implementation:
int StartAsync(const RequestInfo& request_info,
ResourceRequestBody* request_body,
RequestPeer* peer) override {
EXPECT_FALSE(peer_);
peer_ = peer;
url_ = request_info.url;
return 1;
}
void Cancel(int request_id) override {
EXPECT_FALSE(canceled_);
canceled_ = true;
}
RequestPeer* peer() { return peer_; }
bool canceled() { return canceled_; }
const GURL& url() { return url_; }
private:
RequestPeer* peer_;
bool canceled_;
GURL url_;
DISALLOW_COPY_AND_ASSIGN(TestResourceDispatcher);
};
class TestWebURLLoaderClient : public blink::WebURLLoaderClient {
public:
TestWebURLLoaderClient(
ResourceDispatcher* dispatcher,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: loader_(new WebURLLoaderImpl(dispatcher, task_runner)),
expect_multipart_response_(false),
delete_on_receive_redirect_(false),
delete_on_receive_response_(false),
delete_on_receive_data_(false),
delete_on_finish_(false),
delete_on_fail_(false),
did_receive_redirect_(false),
did_receive_response_(false),
did_finish_(false) {}
~TestWebURLLoaderClient() override {}
// blink::WebURLLoaderClient implementation:
void willSendRequest(
blink::WebURLLoader* loader,
blink::WebURLRequest& newRequest,
const blink::WebURLResponse& redirectResponse) override {
EXPECT_TRUE(loader_);
EXPECT_EQ(loader_.get(), loader);
// No test currently simulates mutiple redirects.
EXPECT_FALSE(did_receive_redirect_);
did_receive_redirect_ = true;
if (delete_on_receive_redirect_)
loader_.reset();
}
void didSendData(blink::WebURLLoader* loader,
unsigned long long bytesSent,
unsigned long long totalBytesToBeSent) override {
EXPECT_TRUE(loader_);
EXPECT_EQ(loader_.get(), loader);
}
void didReceiveResponse(
blink::WebURLLoader* loader,
const blink::WebURLResponse& response) override {
EXPECT_TRUE(loader_);
EXPECT_EQ(loader_.get(), loader);
// Only multipart requests may receive multiple response headers.
EXPECT_TRUE(expect_multipart_response_ || !did_receive_response_);
did_receive_response_ = true;
response_ = response;
if (delete_on_receive_response_)
loader_.reset();
}
void didDownloadData(blink::WebURLLoader* loader,
int dataLength,
int encodedDataLength) override {
EXPECT_TRUE(loader_);
EXPECT_EQ(loader_.get(), loader);
}
void didReceiveData(blink::WebURLLoader* loader,
const char* data,
int dataLength,
int encodedDataLength) override {
EXPECT_TRUE(loader_);
EXPECT_EQ(loader_.get(), loader);
// The response should have started, but must not have finished, or failed.
EXPECT_TRUE(did_receive_response_);
EXPECT_FALSE(did_finish_);
EXPECT_EQ(net::OK, error_.reason);
EXPECT_EQ("", error_.domain.utf8());
received_data_.append(data, dataLength);
if (delete_on_receive_data_)
loader_.reset();
}
void didReceiveCachedMetadata(blink::WebURLLoader* loader,
const char* data,
int dataLength) override {
EXPECT_EQ(loader_.get(), loader);
}
void didFinishLoading(blink::WebURLLoader* loader,
double finishTime,
int64_t totalEncodedDataLength) override {
EXPECT_TRUE(loader_);
EXPECT_EQ(loader_.get(), loader);
EXPECT_TRUE(did_receive_response_);
EXPECT_FALSE(did_finish_);
did_finish_ = true;
if (delete_on_finish_)
loader_.reset();
}
void didFail(blink::WebURLLoader* loader,
const blink::WebURLError& error) override {
EXPECT_TRUE(loader_);
EXPECT_EQ(loader_.get(), loader);
EXPECT_FALSE(did_finish_);
error_ = error;
if (delete_on_fail_)
loader_.reset();
}
WebURLLoaderImpl* loader() { return loader_.get(); }
void DeleteLoader() {
loader_.reset();
}
void set_expect_multipart_response() { expect_multipart_response_ = true; }
void set_delete_on_receive_redirect() { delete_on_receive_redirect_ = true; }
void set_delete_on_receive_response() { delete_on_receive_response_ = true; }
void set_delete_on_receive_data() { delete_on_receive_data_ = true; }
void set_delete_on_finish() { delete_on_finish_ = true; }
void set_delete_on_fail() { delete_on_fail_ = true; }
bool did_receive_redirect() const { return did_receive_redirect_; }
bool did_receive_response() const { return did_receive_response_; }
const std::string& received_data() const { return received_data_; }
bool did_finish() const { return did_finish_; }
const blink::WebURLError& error() const { return error_; }
const blink::WebURLResponse& response() const { return response_; }
private:
scoped_ptr<WebURLLoaderImpl> loader_;
bool expect_multipart_response_;
bool delete_on_receive_redirect_;
bool delete_on_receive_response_;
bool delete_on_receive_data_;
bool delete_on_finish_;
bool delete_on_fail_;
bool did_receive_redirect_;
bool did_receive_response_;
std::string received_data_;
bool did_finish_;
blink::WebURLError error_;
blink::WebURLResponse response_;
DISALLOW_COPY_AND_ASSIGN(TestWebURLLoaderClient);
};
class WebURLLoaderImplTest : public testing::Test {
public:
explicit WebURLLoaderImplTest()
: client_(&dispatcher_, message_loop_.task_runner()) {}
~WebURLLoaderImplTest() override {}
void DoStartAsyncRequest() {
blink::WebURLRequest request;
request.initialize();
request.setURL(GURL(kTestURL));
client()->loader()->loadAsynchronously(request, client());
ASSERT_TRUE(peer());
}
void DoReceiveRedirect() {
EXPECT_FALSE(client()->did_receive_redirect());
net::RedirectInfo redirect_info;
redirect_info.status_code = 302;
redirect_info.new_method = "GET";
redirect_info.new_url = GURL(kTestURL);
redirect_info.new_first_party_for_cookies = GURL(kTestURL);
peer()->OnReceivedRedirect(redirect_info,
content::ResourceResponseInfo());
EXPECT_TRUE(client()->did_receive_redirect());
}
void DoReceiveResponse() {
EXPECT_FALSE(client()->did_receive_response());
peer()->OnReceivedResponse(content::ResourceResponseInfo());
EXPECT_TRUE(client()->did_receive_response());
}
// Assumes it is called only once for a request.
void DoReceiveData() {
EXPECT_EQ("", client()->received_data());
peer()->OnReceivedData(make_scoped_ptr(new FixedReceivedData(
kTestData, strlen(kTestData), strlen(kTestData))));
EXPECT_EQ(kTestData, client()->received_data());
}
void DoCompleteRequest() {
EXPECT_FALSE(client()->did_finish());
peer()->OnCompletedRequest(net::OK, false, false, "", base::TimeTicks(),
strlen(kTestData));
EXPECT_TRUE(client()->did_finish());
// There should be no error.
EXPECT_EQ(net::OK, client()->error().reason);
EXPECT_EQ("", client()->error().domain.utf8());
}
void DoReceiveCompletedResponse() {
EXPECT_FALSE(client()->did_receive_response());
EXPECT_EQ("", client()->received_data());
EXPECT_FALSE(client()->did_finish());
peer()->OnReceivedCompletedResponse(
content::ResourceResponseInfo(),
make_scoped_ptr(new FixedReceivedData(kTestData, strlen(kTestData),
strlen(kTestData))),
net::OK, false, false, "", base::TimeTicks(), strlen(kTestData));
EXPECT_TRUE(client()->did_receive_response());
EXPECT_EQ(kTestData, client()->received_data());
EXPECT_EQ(net::OK, client()->error().reason);
EXPECT_EQ("", client()->error().domain.utf8());
}
void DoFailRequest() {
EXPECT_FALSE(client()->did_finish());
peer()->OnCompletedRequest(net::ERR_FAILED, false, false, "",
base::TimeTicks(), strlen(kTestData));
EXPECT_FALSE(client()->did_finish());
EXPECT_EQ(net::ERR_FAILED, client()->error().reason);
EXPECT_EQ(net::kErrorDomain, client()->error().domain.utf8());
}
void DoReceiveResponseFtp() {
EXPECT_FALSE(client()->did_receive_response());
content::ResourceResponseInfo response_info;
response_info.mime_type = kFtpDirMimeType;
peer()->OnReceivedResponse(response_info);
EXPECT_TRUE(client()->did_receive_response());
}
void DoReceiveDataFtp() {
peer()->OnReceivedData(make_scoped_ptr(new FixedReceivedData(
kFtpDirListing, strlen(kFtpDirListing), strlen(kFtpDirListing))));
// The FTP delegate should modify the data the client sees.
EXPECT_NE(kFtpDirListing, client()->received_data());
}
void DoReceiveResponseMultipart() {
EXPECT_FALSE(client()->did_receive_response());
content::ResourceResponseInfo response_info;
response_info.headers = new net::HttpResponseHeaders(
net::HttpUtil::AssembleRawHeaders(kMultipartResponseHeaders,
strlen(kMultipartResponseHeaders)));
response_info.mime_type = kMultipartResponseMimeType;
peer()->OnReceivedResponse(response_info);
EXPECT_TRUE(client()->did_receive_response());
}
void DoReceiveDataMultipart() {
peer()->OnReceivedData(make_scoped_ptr(
new FixedReceivedData(kMultipartResponse, strlen(kMultipartResponse),
strlen(kMultipartResponse))));
// Multipart delegate should modify the data the client sees.
EXPECT_NE(kMultipartResponse, client()->received_data());
}
TestWebURLLoaderClient* client() { return &client_; }
TestResourceDispatcher* dispatcher() { return &dispatcher_; }
RequestPeer* peer() { return dispatcher()->peer(); }
base::MessageLoop* message_loop() { return &message_loop_; }
private:
base::MessageLoop message_loop_;
TestResourceDispatcher dispatcher_;
TestWebURLLoaderClient client_;
};
TEST_F(WebURLLoaderImplTest, Success) {
DoStartAsyncRequest();
DoReceiveResponse();
DoReceiveData();
DoCompleteRequest();
EXPECT_FALSE(dispatcher()->canceled());
EXPECT_EQ(kTestData, client()->received_data());
}
TEST_F(WebURLLoaderImplTest, Redirect) {
DoStartAsyncRequest();
DoReceiveRedirect();
DoReceiveResponse();
DoReceiveData();
DoCompleteRequest();
EXPECT_FALSE(dispatcher()->canceled());
EXPECT_EQ(kTestData, client()->received_data());
}
TEST_F(WebURLLoaderImplTest, Failure) {
DoStartAsyncRequest();
DoReceiveResponse();
DoReceiveData();
DoFailRequest();
EXPECT_FALSE(dispatcher()->canceled());
}
TEST_F(WebURLLoaderImplTest, ReceiveCompletedResponse) {
DoStartAsyncRequest();
DoReceiveCompletedResponse();
EXPECT_FALSE(dispatcher()->canceled());
EXPECT_EQ(kTestData, client()->received_data());
}
// The client may delete the WebURLLoader during any callback from the loader.
// These tests make sure that doesn't result in a crash.
TEST_F(WebURLLoaderImplTest, DeleteOnReceiveRedirect) {
client()->set_delete_on_receive_redirect();
DoStartAsyncRequest();
DoReceiveRedirect();
}
TEST_F(WebURLLoaderImplTest, DeleteOnReceiveResponse) {
client()->set_delete_on_receive_response();
DoStartAsyncRequest();
DoReceiveResponse();
}
TEST_F(WebURLLoaderImplTest, DeleteOnReceiveData) {
client()->set_delete_on_receive_data();
DoStartAsyncRequest();
DoReceiveResponse();
DoReceiveData();
}
TEST_F(WebURLLoaderImplTest, DeleteOnFinish) {
client()->set_delete_on_finish();
DoStartAsyncRequest();
DoReceiveResponse();
DoReceiveData();
DoCompleteRequest();
}
TEST_F(WebURLLoaderImplTest, DeleteOnFail) {
client()->set_delete_on_fail();
DoStartAsyncRequest();
DoReceiveResponse();
DoReceiveData();
DoFailRequest();
}
TEST_F(WebURLLoaderImplTest, DeleteBeforeResponseDataURL) {
blink::WebURLRequest request;
request.initialize();
request.setURL(GURL("data:text/html;charset=utf-8,blah!"));
client()->loader()->loadAsynchronously(request, client());
client()->DeleteLoader();
message_loop()->RunUntilIdle();
EXPECT_FALSE(client()->did_receive_response());
}
// Data URL tests.
TEST_F(WebURLLoaderImplTest, DataURL) {
blink::WebURLRequest request;
request.initialize();
request.setURL(GURL("data:text/html;charset=utf-8,blah!"));
client()->loader()->loadAsynchronously(request, client());
message_loop()->RunUntilIdle();
EXPECT_EQ("blah!", client()->received_data());
EXPECT_TRUE(client()->did_finish());
EXPECT_EQ(net::OK, client()->error().reason);
EXPECT_EQ("", client()->error().domain.utf8());
}
TEST_F(WebURLLoaderImplTest, DataURLDeleteOnReceiveResponse) {
blink::WebURLRequest request;
request.initialize();
request.setURL(GURL("data:text/html;charset=utf-8,blah!"));
client()->set_delete_on_receive_response();
client()->loader()->loadAsynchronously(request, client());
message_loop()->RunUntilIdle();
EXPECT_TRUE(client()->did_receive_response());
EXPECT_EQ("", client()->received_data());
EXPECT_FALSE(client()->did_finish());
}
TEST_F(WebURLLoaderImplTest, DataURLDeleteOnReceiveData) {
blink::WebURLRequest request;
request.initialize();
request.setURL(GURL("data:text/html;charset=utf-8,blah!"));
client()->set_delete_on_receive_data();
client()->loader()->loadAsynchronously(request, client());
message_loop()->RunUntilIdle();
EXPECT_TRUE(client()->did_receive_response());
EXPECT_EQ("blah!", client()->received_data());
EXPECT_FALSE(client()->did_finish());
}
TEST_F(WebURLLoaderImplTest, DataURLDeleteOnFinish) {
blink::WebURLRequest request;
request.initialize();
request.setURL(GURL("data:text/html;charset=utf-8,blah!"));
client()->set_delete_on_finish();
client()->loader()->loadAsynchronously(request, client());
message_loop()->RunUntilIdle();
EXPECT_TRUE(client()->did_receive_response());
EXPECT_EQ("blah!", client()->received_data());
EXPECT_TRUE(client()->did_finish());
}
TEST_F(WebURLLoaderImplTest, DataURLDefersLoading) {
blink::WebURLRequest request;
request.initialize();
request.setURL(GURL("data:text/html;charset=utf-8,blah!"));
client()->loader()->loadAsynchronously(request, client());
// setDefersLoading() might be called with either false or true in no
// specific order. The user of the API will not have sufficient information
// about the WebURLLoader's internal state, so the latter gracefully needs to
// handle calling setDefersLoading any number of times with any values from
// any point in time.
client()->loader()->setDefersLoading(false);
client()->loader()->setDefersLoading(true);
client()->loader()->setDefersLoading(true);
message_loop()->RunUntilIdle();
EXPECT_FALSE(client()->did_finish());
client()->loader()->setDefersLoading(false);
client()->loader()->setDefersLoading(true);
message_loop()->RunUntilIdle();
EXPECT_FALSE(client()->did_finish());
client()->loader()->setDefersLoading(false);
message_loop()->RunUntilIdle();
EXPECT_TRUE(client()->did_finish());
client()->loader()->setDefersLoading(true);
client()->loader()->setDefersLoading(false);
client()->loader()->setDefersLoading(false);
message_loop()->RunUntilIdle();
EXPECT_TRUE(client()->did_finish());
EXPECT_EQ("blah!", client()->received_data());
EXPECT_EQ(net::OK, client()->error().reason);
EXPECT_EQ("", client()->error().domain.utf8());
}
// FTP integration tests. These are focused more on safe deletion than correct
// parsing of FTP responses.
TEST_F(WebURLLoaderImplTest, Ftp) {
DoStartAsyncRequest();
DoReceiveResponseFtp();
DoReceiveDataFtp();
DoCompleteRequest();
EXPECT_FALSE(dispatcher()->canceled());
}
TEST_F(WebURLLoaderImplTest, FtpDeleteOnReceiveResponse) {
client()->set_delete_on_receive_response();
DoStartAsyncRequest();
DoReceiveResponseFtp();
// No data should have been received.
EXPECT_EQ("", client()->received_data());
}
TEST_F(WebURLLoaderImplTest, FtpDeleteOnReceiveFirstData) {
client()->set_delete_on_receive_data();
DoStartAsyncRequest();
DoReceiveResponseFtp();
EXPECT_NE("", client()->received_data());
}
TEST_F(WebURLLoaderImplTest, FtpDeleteOnReceiveMoreData) {
DoStartAsyncRequest();
DoReceiveResponseFtp();
DoReceiveDataFtp();
// Directory listings are only parsed once the request completes, so this will
// cancel in DoReceiveDataFtp, before the request finishes.
client()->set_delete_on_receive_data();
peer()->OnCompletedRequest(net::OK, false, false, "", base::TimeTicks(),
strlen(kTestData));
EXPECT_FALSE(client()->did_finish());
}
TEST_F(WebURLLoaderImplTest, FtpDeleteOnFinish) {
client()->set_delete_on_finish();
DoStartAsyncRequest();
DoReceiveResponseFtp();
DoReceiveDataFtp();
DoCompleteRequest();
}
TEST_F(WebURLLoaderImplTest, FtpDeleteOnFail) {
client()->set_delete_on_fail();
DoStartAsyncRequest();
DoReceiveResponseFtp();
DoReceiveDataFtp();
DoFailRequest();
}
// Multipart integration tests. These are focused more on safe deletion than
// correct parsing of Multipart responses.
TEST_F(WebURLLoaderImplTest, Multipart) {
client()->set_expect_multipart_response();
DoStartAsyncRequest();
DoReceiveResponseMultipart();
DoReceiveDataMultipart();
DoCompleteRequest();
EXPECT_EQ(kTestData, client()->received_data());
EXPECT_FALSE(dispatcher()->canceled());
}
TEST_F(WebURLLoaderImplTest, MultipartDeleteOnReceiveFirstResponse) {
client()->set_expect_multipart_response();
client()->set_delete_on_receive_response();
DoStartAsyncRequest();
DoReceiveResponseMultipart();
EXPECT_EQ("", client()->received_data());
}
TEST_F(WebURLLoaderImplTest, MultipartDeleteOnReceiveSecondResponse) {
client()->set_expect_multipart_response();
DoStartAsyncRequest();
DoReceiveResponseMultipart();
client()->set_delete_on_receive_response();
DoReceiveDataMultipart();
EXPECT_EQ("", client()->received_data());
}
TEST_F(WebURLLoaderImplTest, MultipartDeleteOnReceiveFirstData) {
client()->set_expect_multipart_response();
client()->set_delete_on_receive_data();
DoStartAsyncRequest();
DoReceiveResponseMultipart();
DoReceiveDataMultipart();
EXPECT_EQ("bl", client()->received_data());
}
TEST_F(WebURLLoaderImplTest, MultipartDeleteOnReceiveMoreData) {
client()->set_expect_multipart_response();
DoStartAsyncRequest();
DoReceiveResponseMultipart();
DoReceiveDataMultipart();
// For multipart responses, the delegate may send some data when notified
// of a request completing.
client()->set_delete_on_receive_data();
peer()->OnCompletedRequest(net::OK, false, false, "", base::TimeTicks(),
strlen(kTestData));
EXPECT_FALSE(client()->did_finish());
EXPECT_EQ(kTestData, client()->received_data());
}
TEST_F(WebURLLoaderImplTest, MultipartDeleteFinish) {
client()->set_expect_multipart_response();
client()->set_delete_on_finish();
DoStartAsyncRequest();
DoReceiveResponseMultipart();
DoReceiveDataMultipart();
DoCompleteRequest();
EXPECT_EQ(kTestData, client()->received_data());
}
TEST_F(WebURLLoaderImplTest, MultipartDeleteFail) {
client()->set_expect_multipart_response();
client()->set_delete_on_fail();
DoStartAsyncRequest();
DoReceiveResponseMultipart();
DoReceiveDataMultipart();
DoFailRequest();
}
// PlzNavigate: checks that the stream override parameters provided on
// navigation commit are properly applied.
TEST_F(WebURLLoaderImplTest, BrowserSideNavigationCommit) {
// Initialize the request and the stream override.
const GURL kStreamURL = GURL("http://bar");
const std::string kMimeType = "text/html";
blink::WebURLRequest request;
request.initialize();
request.setURL(GURL(kTestURL));
request.setFrameType(blink::WebURLRequest::FrameTypeTopLevel);
request.setRequestContext(blink::WebURLRequest::RequestContextFrame);
scoped_ptr<StreamOverrideParameters> stream_override(
new StreamOverrideParameters());
stream_override->stream_url = kStreamURL;
stream_override->response.mime_type = kMimeType;
RequestExtraData* extra_data = new RequestExtraData();
extra_data->set_stream_override(stream_override.Pass());
request.setExtraData(extra_data);
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kEnableBrowserSideNavigation);
client()->loader()->loadAsynchronously(request, client());
// The stream url should have been requestead instead of the request url.
ASSERT_TRUE(peer());
EXPECT_EQ(kStreamURL, dispatcher()->url());
EXPECT_FALSE(client()->did_receive_response());
peer()->OnReceivedResponse(content::ResourceResponseInfo());
EXPECT_TRUE(client()->did_receive_response());
// The response info should have been overriden.
ASSERT_FALSE(client()->response().isNull());
EXPECT_EQ(kMimeType, client()->response().mimeType().latin1());
DoReceiveData();
DoCompleteRequest();
EXPECT_FALSE(dispatcher()->canceled());
EXPECT_EQ(kTestData, client()->received_data());
}
} // namespace
} // namespace content