Buffet: Implement fake HTTP transport to help write unit tests
Created fake Transport and Connection classes to help test
HTTP communications in Buffet. Now, when a fake transport
class is created a number of HTTP request handlers can be
registered, which will be called when a request to a web
server is made. These handlers can reply to the caller on
server's behalf and can provide response based on the
request data and parameters.
Removed 'static' from http::Request::range_value_omitted due
to a build break in debug (-O0) build. Static members should
be generally initialized in a .cc file, not header.
Fixed a bug in chromeos::url::GetQueryStringParameters() when
called on an empty string.
Finally, added 'bind_lamda.h' header file that adds the
ability to use lambdas in base::Bind() calls.
BUG=chromium:367377
TEST=Unit tests pass.
Change-Id: Ib4c070f676069f208b9df4da069ff3a29f8f656f
Reviewed-on: https://chromium-review.googlesource.com/197157
Reviewed-by: Christopher Wiley <wiley@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/bind_lambda.h b/buffet/bind_lambda.h
new file mode 100644
index 0000000..69d948c
--- /dev/null
+++ b/buffet/bind_lambda.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_BIND_LAMBDA_H_
+#define BUFFET_BIND_LAMBDA_H_
+
+#include <base/bind.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// This file is an extension to base/bind_internal.h and adds a RunnableAdapter
+// class specialization that wraps a functor (including lambda objects), so
+// they can be used in base::Callback/base::Bind constructs.
+// By including this file you will gain the ability to write expressions like:
+// base::Callback<int(int)> callback = base::Bind([](int value) {
+// return value * value;
+// });
+////////////////////////////////////////////////////////////////////////////////
+namespace base {
+namespace internal {
+
+// LambdaAdapter is a helper class that specializes on different function call
+// signatures and provides the RunType and Run() method required by
+// RunnableAdapter<> class.
+template <typename Lambda, typename Sig>
+class LambdaAdapter;
+
+// R(...)
+template <typename Lambda, typename R, typename... Args>
+class LambdaAdapter<Lambda, R(Lambda::*)(Args... args)> {
+public:
+ typedef R(RunType)(Args...);
+ LambdaAdapter(Lambda lambda) : lambda_(lambda) {}
+ R Run(Args... args) { return lambda_(args...); }
+
+private:
+ Lambda lambda_;
+};
+
+// R(...) const
+template <typename Lambda, typename R, typename... Args>
+class LambdaAdapter<Lambda, R(Lambda::*)(Args... args) const> {
+public:
+ typedef R(RunType)(Args...);
+ LambdaAdapter(Lambda lambda) : lambda_(lambda) {}
+ R Run(Args... args) { return lambda_(args...); }
+
+private:
+ Lambda lambda_;
+};
+
+template <typename Lambda>
+class RunnableAdapter : public LambdaAdapter<Lambda,
+ decltype(&Lambda::operator())> {
+public:
+ explicit RunnableAdapter(Lambda lambda) :
+ LambdaAdapter<Lambda, decltype(&Lambda::operator())>(lambda) {
+ }
+};
+
+
+} // namespace internal
+} // namespace base
+
+#endif // BUFFET_BIND_LAMBDA_H_
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp
index 06e87f5..2288162 100644
--- a/buffet/buffet.gyp
+++ b/buffet/buffet.gyp
@@ -73,10 +73,12 @@
],
'includes': ['../common-mk/common_test.gypi'],
'sources': [
+ 'async_event_sequencer_unittest.cc',
'buffet_testrunner.cc',
'data_encoding_unittest.cc',
'exported_property_set_unittest.cc',
- 'async_event_sequencer_unittest.cc',
+ 'http_connection_fake.cc',
+ 'http_transport_fake.cc',
'http_utils_unittest.cc',
'mime_utils_unittest.cc',
'string_utils_unittest.cc',
diff --git a/buffet/http_connection_fake.cc b/buffet/http_connection_fake.cc
new file mode 100644
index 0000000..6731aeb
--- /dev/null
+++ b/buffet/http_connection_fake.cc
@@ -0,0 +1,87 @@
+// Copyright 2014 The Chromium OS 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 "buffet/http_connection_fake.h"
+
+#include <base/logging.h>
+
+#include "buffet/http_request.h"
+#include "buffet/mime_utils.h"
+#include "buffet/string_utils.h"
+
+using namespace chromeos;
+using namespace chromeos::http::fake;
+
+Connection::Connection(const std::string& url, const std::string& method,
+ std::shared_ptr<http::Transport> transport) :
+ http::Connection(transport), request_(url, method) {
+ VLOG(1) << "fake::Connection created: " << method;
+}
+
+Connection::~Connection() {
+ VLOG(1) << "fake::Connection destroyed";
+}
+
+bool Connection::SendHeaders(const HeaderList& headers) {
+ request_.AddHeaders(headers);
+ return true;
+}
+
+bool Connection::WriteRequestData(const void* data, size_t size) {
+ request_.AddData(data, size);
+ return true;
+}
+
+bool Connection::FinishRequest() {
+ request_.AddHeaders({{request_header::kContentLength,
+ std::to_string(request_.GetData().size())}});
+ fake::Transport* transport = dynamic_cast<fake::Transport*>(transport_.get());
+ CHECK(transport) << "Expecting a fake transport";
+ auto handler = transport->GetHandler(request_.GetURL(), request_.GetMethod());
+ if (handler.is_null()) {
+ response_.ReplyText(status_code::NotFound,
+ "<html><body>Not found</body></html>",
+ mime::text::kHtml);
+ } else {
+ handler.Run(request_, &response_);
+ }
+ return true;
+}
+
+int Connection::GetResponseStatusCode() const {
+ return response_.GetStatusCode();
+}
+
+std::string Connection::GetResponseStatusText() const {
+ return response_.GetStatusText();
+}
+
+std::string Connection::GetProtocolVersion() const {
+ return response_.GetProtocolVersion();
+}
+
+std::string Connection::GetResponseHeader(
+ const std::string& header_name) const {
+ return response_.GetHeader(header_name);
+}
+
+uint64_t Connection::GetResponseDataSize() const {
+ return response_.GetData().size();
+}
+
+bool Connection::ReadResponseData(void* data, size_t buffer_size,
+ size_t* size_read) {
+ size_t size_to_read = GetResponseDataSize() - response_data_ptr_;
+ if (size_to_read > buffer_size)
+ size_to_read = buffer_size;
+ memcpy(data, response_.GetData().data() + response_data_ptr_, size_to_read);
+ if (size_read)
+ *size_read = size_to_read;
+ response_data_ptr_ += size_to_read;
+ return true;
+}
+
+std::string Connection::GetErrorMessage() const {
+ return std::string();
+}
diff --git a/buffet/http_connection_fake.h b/buffet/http_connection_fake.h
new file mode 100644
index 0000000..26ca307
--- /dev/null
+++ b/buffet/http_connection_fake.h
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_HTTP_CONNECTION_FAKE_H_
+#define BUFFET_HTTP_CONNECTION_FAKE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/basictypes.h>
+
+#include "buffet/http_connection.h"
+#include "buffet/http_transport_fake.h"
+
+namespace chromeos {
+namespace http {
+namespace fake {
+
+// This is a fake implementation of http::Connection for unit testing.
+class Connection : public chromeos::http::Connection {
+ public:
+ Connection(const std::string& url, const std::string& method,
+ std::shared_ptr<http::Transport> transport);
+ virtual ~Connection();
+
+ // Overrides from http::Connection.
+ // See http_connection.h for description of these methods.
+ virtual bool SendHeaders(const HeaderList& headers) override;
+ virtual bool WriteRequestData(const void* data, size_t size) override;
+ virtual bool FinishRequest() override;
+
+ virtual int GetResponseStatusCode() const override;
+ virtual std::string GetResponseStatusText() const override;
+ virtual std::string GetProtocolVersion() const override;
+ virtual std::string GetResponseHeader(
+ const std::string& header_name) const override;
+ virtual uint64_t GetResponseDataSize() const override;
+ virtual bool ReadResponseData(void* data, size_t buffer_size,
+ size_t* size_read) override;
+ virtual std::string GetErrorMessage() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Connection);
+
+ // Request and response objects passed to the user-provided request handler
+ // callback. The request object contains all the request information.
+ // The response object is the server response that is created by
+ // the handler in response to the request.
+ ServerRequest request_;
+ ServerResponse response_;
+
+ // Internal read data pointer needed for ReadResponseData() implementation.
+ size_t response_data_ptr_ = 0;
+};
+
+} // namespace fake
+} // namespace http
+} // namespace chromeos
+
+#endif // BUFFET_HTTP_CONNECTION_FAKE_H_
diff --git a/buffet/http_request.h b/buffet/http_request.h
index a93af94..62f4a01 100644
--- a/buffet/http_request.h
+++ b/buffet/http_request.h
@@ -305,7 +305,7 @@
// range_value_omitted is used in |ranges_| list to indicate omitted value.
// E.g. range (10,range_value_omitted) represents bytes from 10 to the end
// of the data stream.
- static const uint64_t range_value_omitted = (uint64_t)-1;
+ const uint64_t range_value_omitted = (uint64_t)-1;
// Error message in case request fails completely.
std::string error_;
diff --git a/buffet/http_transport_fake.cc b/buffet/http_transport_fake.cc
new file mode 100644
index 0000000..57278b7
--- /dev/null
+++ b/buffet/http_transport_fake.cc
@@ -0,0 +1,210 @@
+// Copyright 2014 The Chromium OS 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 "buffet/http_transport_fake.h"
+
+#include <base/json/json_writer.h>
+
+#include "buffet/http_connection_fake.h"
+#include "buffet/http_request.h"
+#include "buffet/mime_utils.h"
+#include "buffet/url_utils.h"
+
+using namespace chromeos;
+using namespace chromeos::http::fake;
+
+Transport::Transport() {
+ VLOG(1) << "fake::Transport created";
+}
+
+Transport::~Transport() {
+ VLOG(1) << "fake::Transport destroyed";
+}
+
+std::unique_ptr<http::Connection> Transport::CreateConnection(
+ std::shared_ptr<http::Transport> transport,
+ const std::string& url,
+ const std::string& method,
+ const HeaderList& headers,
+ const std::string& user_agent,
+ const std::string& referer,
+ std::string* error_msg) {
+ HeaderList headers_copy = headers;
+ if (!user_agent.empty()) {
+ headers_copy.push_back(std::make_pair(http::request_header::kUserAgent,
+ user_agent));
+ }
+ if (!referer.empty()) {
+ headers_copy.push_back(std::make_pair(http::request_header::kReferer,
+ referer));
+ }
+ std::unique_ptr<http::Connection> connection(
+ new http::fake::Connection(url, method, transport));
+ CHECK(connection) << "Unable to create Connection object";
+ if (!connection->SendHeaders(headers_copy)) {
+ connection.reset();
+ if (error_msg)
+ *error_msg = "Failed to send request headers";
+ }
+ return connection;
+}
+
+static inline std::string GetHandlerMapKey(const std::string& url,
+ const std::string& method) {
+ return method + ":" + url;
+}
+
+void Transport::AddHandler(const std::string& url, const std::string& method,
+ const HandlerCallback& handler) {
+ handlers_.insert(std::make_pair(GetHandlerMapKey(url, method), handler));
+}
+
+Transport::HandlerCallback Transport::GetHandler(
+ const std::string& url, const std::string& method) const {
+ // First try the exact combination of URL/Method
+ auto p = handlers_.find(GetHandlerMapKey(url, method));
+ if (p != handlers_.end())
+ return p->second;
+ // If not found, try URL/*
+ p = handlers_.find(GetHandlerMapKey(url, "*"));
+ if (p != handlers_.end())
+ return p->second;
+ // If still not found, try */method
+ p = handlers_.find(GetHandlerMapKey("*", method));
+ if (p != handlers_.end())
+ return p->second;
+ // Finally, try */*
+ p = handlers_.find(GetHandlerMapKey("*", "*"));
+ return (p != handlers_.end()) ? p->second : HandlerCallback();
+}
+
+void ServerRequestResponseBase::AddData(const void* data, size_t data_size) {
+ auto bytes = reinterpret_cast<const unsigned char*>(data);
+ data_.insert(data_.end(), bytes, bytes + data_size);
+}
+
+std::string ServerRequestResponseBase::GetDataAsString() const {
+ if (data_.empty())
+ return std::string();
+ auto chars = reinterpret_cast<const char*>(data_.data());
+ return std::string(chars, data_.size());
+}
+
+void ServerRequestResponseBase::AddHeaders(const HeaderList& headers) {
+ for (auto&& pair : headers) {
+ if (pair.second.empty())
+ headers_.erase(pair.first);
+ else
+ headers_.insert(pair);
+ }
+}
+
+std::string ServerRequestResponseBase::GetHeader(
+ const std::string& header_name) const {
+ auto p = headers_.find(header_name);
+ return p != headers_.end() ? p->second : std::string();
+}
+
+ServerRequest::ServerRequest(const std::string& url,
+ const std::string& method) : method_(method) {
+ auto params = url::GetQueryStringParameters(url);
+ url_ = url::RemoveQueryString(url, true);
+ form_fields_.insert(params.begin(), params.end());
+}
+
+std::string ServerRequest::GetFormField(const std::string& field_name) const {
+ if (!form_fields_parsed_) {
+ std::string mime_type = mime::RemoveParameters(
+ GetHeader(request_header::kContentType));
+ if (mime_type == mime::application::kWwwFormUrlEncoded &&
+ !GetData().empty()) {
+ auto fields = data_encoding::WebParamsDecode(GetDataAsString());
+ form_fields_.insert(fields.begin(), fields.end());
+ }
+ form_fields_parsed_ = true;
+ }
+ auto p = form_fields_.find(field_name);
+ return p != form_fields_.end() ? p->second : std::string();
+}
+
+void ServerResponse::Reply(int status_code, const void* data, size_t data_size,
+ const char* mime_type) {
+ data_.clear();
+ status_code_ = status_code;
+ AddData(data, data_size);
+ AddHeaders({
+ {response_header::kContentLength, std::to_string(data_size)},
+ {response_header::kContentType, mime_type}
+ });
+}
+
+void ServerResponse::ReplyText(int status_code, const std::string& text,
+ const char* mime_type) {
+ Reply(status_code, text.data(), text.size(), mime_type);
+}
+
+void ServerResponse::ReplyJson(int status_code, const base::Value* json) {
+ std::string text;
+ base::JSONWriter::WriteWithOptions(json,
+ base::JSONWriter::OPTIONS_PRETTY_PRINT,
+ &text);
+ ReplyText(status_code, text, mime::application::kJson);
+}
+
+std::string ServerResponse::GetStatusText() const {
+ static std::vector<std::pair<int, const char*>> status_text_map = {
+ {100, "Continue"},
+ {101, "Switching Protocols"},
+ {102, "Processing"},
+ {200, "OK"},
+ {201, "Created"},
+ {202, "Accepted"},
+ {203, "Non-Authoritative Information"},
+ {204, "No Content"},
+ {205, "Reset Content"},
+ {206, "Partial Content"},
+ {207, "Multi-Status"},
+ {208, "Already Reported"},
+ {226, "IM Used"},
+ {300, "Multiple Choices"},
+ {301, "Moved Permanently"},
+ {302, "Found"},
+ {303, "See Other"},
+ {304, "Not Modified"},
+ {305, "Use Proxy"},
+ {306, "Switch Proxy"},
+ {307, "Temporary Redirect"},
+ {308, "Permanent Redirect"},
+ {400, "Bad Request"},
+ {401, "Unauthorized"},
+ {402, "Payment Required"},
+ {403, "Forbidden"},
+ {404, "Not Found"},
+ {405, "Method Not Allowed"},
+ {406, "Not Acceptable"},
+ {407, "Proxy Authentication Required"},
+ {408, "Request Timeout"},
+ {409, "Conflict"},
+ {410, "Gone"},
+ {411, "Length Required"},
+ {412, "Precondition Failed"},
+ {413, "Request Entity Too Large"},
+ {414, "Request - URI Too Long"},
+ {415, "Unsupported Media Type"},
+ {429, "Too Many Requests"},
+ {431, "Request Header Fields Too Large"},
+ {500, "Internal Server Error"},
+ {501, "Not Implemented"},
+ {502, "Bad Gateway"},
+ {503, "Service Unavailable"},
+ {504, "Gateway Timeout"},
+ {505, "HTTP Version Not Supported"},
+ };
+
+ for (auto&& pair : status_text_map) {
+ if (pair.first == status_code_)
+ return pair.second;
+ }
+ return std::string();
+}
diff --git a/buffet/http_transport_fake.h b/buffet/http_transport_fake.h
new file mode 100644
index 0000000..c526770
--- /dev/null
+++ b/buffet/http_transport_fake.h
@@ -0,0 +1,198 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_HTTP_TRANSPORT_FAKE_H_
+#define BUFFET_HTTP_TRANSPORT_FAKE_H_
+
+#include <type_traits>
+
+#include <base/callback.h>
+#include <base/values.h>
+
+#include "buffet/http_transport.h"
+
+namespace chromeos {
+namespace http {
+namespace fake {
+
+class ServerRequest;
+class ServerResponse;
+class Connection;
+
+///////////////////////////////////////////////////////////////////////////////
+// A fake implementation of http::Transport that simulates HTTP communication
+// with a server.
+///////////////////////////////////////////////////////////////////////////////
+class Transport : public http::Transport {
+ public:
+ Transport();
+ virtual ~Transport();
+
+ // Server handler callback signature.
+ typedef base::Callback<void(const ServerRequest&, ServerResponse*)>
+ HandlerCallback;
+
+ // This method allows the test code to provide a callback to handle requests
+ // for specific URL/HTTP-verb combination. When a specific |method| request
+ // is made on the given |url|, the |handler| will be invoked and all the
+ // request data will be filled in the |ServerRequest| parameter. Any server
+ // response should be returned through the |ServerResponse| parameter.
+ // Either |method| or |url| (or both) can be specified as "*" to handle
+ // any requests. So, ("http://localhost","*") will handle any request type
+ // on that URL and ("*","GET") will handle any GET requests.
+ // The lookup starts with the most specific data pair to the catch-all (*,*).
+ void AddHandler(const std::string& url, const std::string& method,
+ const HandlerCallback& handler);
+ // Retrieve a handler for specific |url| and request |method|.
+ HandlerCallback GetHandler(const std::string& url,
+ const std::string& method) const;
+
+ // Overload from http::Transport
+ virtual std::unique_ptr<http::Connection> CreateConnection(
+ std::shared_ptr<http::Transport> transport,
+ const std::string& url,
+ const std::string& method,
+ const HeaderList& headers,
+ const std::string& user_agent,
+ const std::string& referer,
+ std::string* error_msg) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Transport);
+
+ // A list of user-supplied request handlers.
+ std::map<std::string, HandlerCallback> handlers_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// A base class for ServerRequest and ServerResponse. It provides common
+// functionality to work with request/response HTTP headers and data.
+///////////////////////////////////////////////////////////////////////////////
+class ServerRequestResponseBase {
+ public:
+ ServerRequestResponseBase() = default;
+
+ // Add/retrieve request/response body data.
+ void AddData(const void* data, size_t data_size);
+ const std::vector<unsigned char>& GetData() const { return data_; }
+ std::string GetDataAsString() const;
+
+ // Add/retrieve request/response HTTP headers.
+ void AddHeaders(const HeaderList& headers);
+ std::string GetHeader(const std::string& header_name) const;
+
+ protected:
+ // Data buffer.
+ std::vector<unsigned char> data_;
+ // Header map.
+ std::map<std::string, std::string> headers_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ServerRequestResponseBase);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// A container class that encapsulates all the HTTP server request information.
+///////////////////////////////////////////////////////////////////////////////
+class ServerRequest : public ServerRequestResponseBase {
+ public:
+ ServerRequest(const std::string& url, const std::string& method);
+
+ // Get the actual request URL. Does not include the query string or fragment.
+ const std::string& GetURL() const { return url_; }
+ // Get the request method.
+ const std::string& GetMethod() const { return method_; }
+ // Get the POST/GET request parameters. These are parsed query string
+ // parameters from the URL. In addition, for POST requests with
+ // application/x-www-form-urlencoded content type, the request body is also
+ // parsed and individual fields can be accessed through this method.
+ std::string GetFormField(const std::string& field_name) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ServerRequest);
+
+ // Request URL (without query string or URL fragment).
+ std::string url_;
+ // Request method
+ std::string method_;
+ // List of available request data form fields.
+ mutable std::map<std::string, std::string> form_fields_;
+ // Flag used on first request to GetFormField to parse the body of HTTP POST
+ // request with application/x-www-form-urlencoded content.
+ mutable bool form_fields_parsed_ = false;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// A container class that encapsulates all the HTTP server response information.
+// The request handler will use this class to provide a response to the caller.
+// Call the Reply() or the approriate ReplyNNN() specialization to provide
+// the response data. Additional calls to AddHeaders() can be made to provide
+// custom response headers. The Reply-methods will already provide the
+// followig response headers:
+// Content-Length
+// Content-Type
+///////////////////////////////////////////////////////////////////////////////
+class ServerResponse : public ServerRequestResponseBase {
+public:
+ ServerResponse() = default;
+
+ // Generic reply method.
+ void Reply(int status_code, const void* data, size_t data_size,
+ const char* mime_type);
+ // Reply with text body.
+ void ReplyText(int status_code, const std::string& text,
+ const char* mime_type);
+ // Reply with JSON object. The content type will be "application/json".
+ void ReplyJson(int status_code, const base::Value* json);
+
+ // Specialized overload to send the binary data as an array of simple
+ // data elements. Only trivial data types (scalars, POD structures, etc)
+ // can be used.
+ template<typename T>
+ void Reply(int status_code, const std::vector<T>& data,
+ const char* mime_type) {
+ // Make sure T doesn't have virtual functions, custom constructors, etc.
+ static_assert(std::is_trivial<T>::value, "Only simple data is supported");
+ Reply(status_code, data.data(), data.size() * sizeof(T), mime_type);
+ }
+
+ // Specialized overload to send the binary data.
+ // Only trivial data types (scalars, POD structures, etc) can be used.
+ template<typename T>
+ void Reply(int status_code, const T& data, const char* mime_type) {
+ // Make sure T doesn't have virtual functions, custom constructors, etc.
+ static_assert(std::is_trivial<T>::value, "Only simple data is supported");
+ Reply(status_code, &data, sizeof(T), mime_type);
+ }
+
+ // For handlers that want to simulate versions of HTTP protocol other
+ // than HTTP/1.1, call this method with the custom version string,
+ // for example "HTTP/1.0".
+ void SetProtocolVersion(const std::string& protocol_version) {
+ protocol_version_ = protocol_version;
+ }
+
+ protected:
+ // These methods are helpers to implement corresponding functionality
+ // of fake::Connection.
+ friend class Connection;
+ // Helper for fake::Connection::GetResponseStatusCode().
+ int GetStatusCode() const { return status_code_; }
+ // Helper for fake::Connection::GetResponseStatusText().
+ std::string GetStatusText() const;
+ // Helper for fake::Connection::GetProtocolVersion().
+ std::string GetProtocolVersion() const { return protocol_version_; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ServerResponse);
+
+ int status_code_ = 0;
+ std::string protocol_version_ = "HTTP/1.1";
+};
+
+} // namespace fake
+} // namespace http
+} // namespace chromeos
+
+#endif // BUFFET_HTTP_TRANSPORT_FAKE_H_
diff --git a/buffet/http_utils_unittest.cc b/buffet/http_utils_unittest.cc
index ab137a7..c04f498 100644
--- a/buffet/http_utils_unittest.cc
+++ b/buffet/http_utils_unittest.cc
@@ -2,12 +2,56 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "buffet/http_utils.h"
-
#include <gtest/gtest.h>
+#include "buffet/bind_lambda.h"
+#include "buffet/http_utils.h"
+#include "buffet/http_transport_fake.h"
+#include "buffet/mime_utils.h"
+#include "buffet/url_utils.h"
+
+using namespace chromeos;
using namespace chromeos::http;
-TEST(HttpUtils, SendRequest) {
- // TODO(avakulenko)
+static const char fake_url[] = "http://localhost";
+
+TEST(HttpUtils, PostText) {
+ std::string fake_data = "Some data";
+ auto PostHandler = [fake_data](const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ EXPECT_EQ(request_type::kPost, request.GetMethod());
+ EXPECT_EQ(fake_data.size(),
+ atoi(request.GetHeader(request_header::kContentLength).c_str()));
+ EXPECT_EQ(mime::text::kPlain,
+ request.GetHeader(request_header::kContentType));
+ response->Reply(status_code::Ok, request.GetData(), mime::text::kPlain);
+ };
+
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(fake_url, request_type::kPost, base::Bind(PostHandler));
+
+ auto response = http::PostText(fake_url, fake_data.c_str(),
+ mime::text::kPlain, transport);
+ EXPECT_TRUE(response->IsSuccessful());
+ EXPECT_EQ(mime::text::kPlain, response->GetContentType());
+ EXPECT_EQ(fake_data, response->GetDataAsString());
+}
+
+TEST(HttpUtils, Get) {
+ auto GetHandler = [](const fake::ServerRequest& request,
+ fake::ServerResponse* response) {
+ EXPECT_EQ(request_type::kGet, request.GetMethod());
+ EXPECT_EQ("0", request.GetHeader(request_header::kContentLength));
+ EXPECT_EQ("", request.GetHeader(request_header::kContentType));
+ response->ReplyText(status_code::Ok, request.GetFormField("test"),
+ mime::text::kPlain);
+ };
+
+ std::shared_ptr<fake::Transport> transport(new fake::Transport);
+ transport->AddHandler(fake_url, request_type::kGet, base::Bind(GetHandler));
+
+ for (std::string data : {"blah", "some data", ""}) {
+ std::string url = url::AppendQueryParam(fake_url, "test", data);
+ EXPECT_EQ(data, http::GetAsString(url, transport));
+ }
}
diff --git a/buffet/url_utils.cc b/buffet/url_utils.cc
index 08d78f7..aea0d9d 100644
--- a/buffet/url_utils.cc
+++ b/buffet/url_utils.cc
@@ -96,7 +96,9 @@
chromeos::data_encoding::WebParamList chromeos::url::GetQueryStringParameters(
const std::string& url) {
// Extract the query string and remove the leading '?'.
- std::string query_string = GetQueryString(url, true).substr(1);
+ std::string query_string = GetQueryString(url, true);
+ if (!query_string.empty() && query_string.front() == '?')
+ query_string.erase(query_string.begin());
return chromeos::data_encoding::WebParamsDecode(query_string);
}