buffet: add unit tests for http_utils Added more unit tests for utility functions in http_utils.h BUG=chromium:367379 TEST=Old and new unit tests pass. Change-Id: I04e28691bc4db3980783c0033f5691087a8c6ab3 Reviewed-on: https://chromium-review.googlesource.com/197312 Reviewed-by: Alex Vakulenko <avakulenko@chromium.org> Tested-by: Alex Vakulenko <avakulenko@chromium.org> Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/http_connection_fake.cc b/buffet/http_connection_fake.cc index 6731aeb..0507c0b 100644 --- a/buffet/http_connection_fake.cc +++ b/buffet/http_connection_fake.cc
@@ -67,7 +67,9 @@ } uint64_t Connection::GetResponseDataSize() const { - return response_.GetData().size(); + // HEAD requests must not return body. + return (request_.GetMethod() != request_type::kHead) ? + response_.GetData().size() : 0; } bool Connection::ReadResponseData(void* data, size_t buffer_size, @@ -75,7 +77,8 @@ 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_to_read > 0) + 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;
diff --git a/buffet/http_transport_fake.h b/buffet/http_transport_fake.h index c526770..b95599f 100644 --- a/buffet/http_transport_fake.h +++ b/buffet/http_transport_fake.h
@@ -81,6 +81,9 @@ // Add/retrieve request/response HTTP headers. void AddHeaders(const HeaderList& headers); std::string GetHeader(const std::string& header_name) const; + const std::map<std::string, std::string>& GetHeaders() const { + return headers_; + } protected: // Data buffer.
diff --git a/buffet/http_utils_unittest.cc b/buffet/http_utils_unittest.cc index c04f498..1453b86 100644 --- a/buffet/http_utils_unittest.cc +++ b/buffet/http_utils_unittest.cc
@@ -2,18 +2,224 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <base/values.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/string_utils.h" #include "buffet/url_utils.h" using namespace chromeos; using namespace chromeos::http; -static const char fake_url[] = "http://localhost"; +static const char kFakeUrl[] = "http://localhost"; +static const char kEchoUrl[] = "http://localhost/echo"; +static const char kMethodEchoUrl[] = "http://localhost/echo/method"; + +///////////////////// Generic helper request handlers ///////////////////////// +// Returns the request data back with the same content type. +static void EchoDataHandler(const fake::ServerRequest& request, + fake::ServerResponse* response) { + response->Reply(status_code::Ok, request.GetData(), + request.GetHeader(request_header::kContentType).c_str()); +}; + +// Returns the request method as a plain text response. +static void EchoMethodHandler(const fake::ServerRequest& request, + fake::ServerResponse* response) { + response->ReplyText(status_code::Ok, request.GetMethod(), mime::text::kPlain); +}; + +/////////////////////////////////////////////////////////////////////////////// +TEST(HttpUtils, SendRequest_BinaryData) { + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler(kEchoUrl, request_type::kPost, + base::Bind(EchoDataHandler)); + + // Test binary data round-tripping. + std::vector<unsigned char> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F}; + auto response = http::SendRequest(request_type::kPost, kEchoUrl, + custom_data.data(), custom_data.size(), + mime::application::kOctet_stream, + HeaderList(), transport); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(mime::application::kOctet_stream, response->GetContentType()); + EXPECT_EQ(custom_data.size(), response->GetData().size()); + EXPECT_EQ(custom_data, response->GetData()); +} + +TEST(HttpUtils, SendRequest_Post) { + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); + + // Test binary data round-tripping. + std::vector<unsigned char> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F}; + + // Check the correct HTTP method used. + auto response = http::SendRequest(request_type::kPost, kMethodEchoUrl, + custom_data.data(), custom_data.size(), + mime::application::kOctet_stream, + HeaderList(), transport); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(mime::text::kPlain, response->GetContentType()); + EXPECT_EQ(request_type::kPost, response->GetDataAsString()); +} + +TEST(HttpUtils, SendRequest_Get) { + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); + + auto response = http::SendRequest(request_type::kGet, kMethodEchoUrl, + nullptr, 0, nullptr, + HeaderList(), transport); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(mime::text::kPlain, response->GetContentType()); + EXPECT_EQ(request_type::kGet, response->GetDataAsString()); +} + +TEST(HttpUtils, SendRequest_Put) { + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); + + auto response = http::SendRequest(request_type::kPut, kMethodEchoUrl, + nullptr, 0, nullptr, + HeaderList(), transport); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(mime::text::kPlain, response->GetContentType()); + EXPECT_EQ(request_type::kPut, response->GetDataAsString()); +} + +TEST(HttpUtils, SendRequest_NotFound) { + std::shared_ptr<fake::Transport> transport(new fake::Transport); + // Test failed response (URL not found). + auto response = http::SendRequest(request_type::kGet, "http://blah.com", + nullptr, 0, nullptr, + HeaderList(), transport); + EXPECT_FALSE(response->IsSuccessful()); + EXPECT_EQ(status_code::NotFound, response->GetStatusCode()); +} + +TEST(HttpUtils, SendRequest_Headers) { + std::shared_ptr<fake::Transport> transport(new fake::Transport); + + static const char json_echo_url[] = "http://localhost/echo/json"; + auto JsonEchoHandler = [](const fake::ServerRequest& request, + fake::ServerResponse* response) { + base::DictionaryValue json; + json.SetString("method", request.GetMethod()); + json.SetString("data", request.GetDataAsString()); + for (auto&& pair : request.GetHeaders()) { + json.SetString("header." + pair.first, pair.second); + } + response->ReplyJson(status_code::Ok, &json); + }; + transport->AddHandler(json_echo_url, "*", + base::Bind(JsonEchoHandler)); + auto response = http::SendRequest( + request_type::kPost, json_echo_url, "abcd", 4, + mime::application::kOctet_stream, { + {request_header::kCookie, "flavor=vanilla"}, + {request_header::kIfMatch, "*"}, + }, transport); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(mime::application::kJson, response->GetContentType()); + auto json = ParseJsonResponse(response.get(), nullptr, nullptr); + std::string value; + EXPECT_TRUE(json->GetString("method", &value)); + EXPECT_EQ(request_type::kPost, value); + EXPECT_TRUE(json->GetString("data", &value)); + EXPECT_EQ("abcd", value); + EXPECT_TRUE(json->GetString("header.Cookie", &value)); + EXPECT_EQ("flavor=vanilla", value); + EXPECT_TRUE(json->GetString("header.Content-Type", &value)); + EXPECT_EQ(mime::application::kOctet_stream, value); + EXPECT_TRUE(json->GetString("header.Content-Length", &value)); + EXPECT_EQ("4", value); + EXPECT_TRUE(json->GetString("header.If-Match", &value)); + EXPECT_EQ("*", value); +} + +TEST(HttpUtils, Get) { + // Sends back the "?test=..." portion of URL. + // So if we do GET "http://localhost?test=blah", this handler responds + // with "blah" as text/plain. + 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(kFakeUrl, request_type::kGet, base::Bind(GetHandler)); + transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); + + // Make sure Get/GetAsString actually do the GET request + auto response = http::Get(kMethodEchoUrl, transport); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(mime::text::kPlain, response->GetContentType()); + EXPECT_EQ(request_type::kGet, response->GetDataAsString()); + EXPECT_EQ(request_type::kGet, http::GetAsString(kMethodEchoUrl, transport)); + + for (std::string data : {"blah", "some data", ""}) { + std::string url = url::AppendQueryParam(kFakeUrl, "test", data); + EXPECT_EQ(data, http::GetAsString(url, transport)); + } +} + +TEST(HttpUtils, Head) { + auto HeadHandler = [](const fake::ServerRequest& request, + fake::ServerResponse* response) { + EXPECT_EQ(request_type::kHead, request.GetMethod()); + EXPECT_EQ("0", request.GetHeader(request_header::kContentLength)); + EXPECT_EQ("", request.GetHeader(request_header::kContentType)); + response->ReplyText(status_code::Ok, "blah", + mime::text::kPlain); + }; + + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler(kFakeUrl, request_type::kHead, base::Bind(HeadHandler)); + + auto response = http::Head(kFakeUrl, transport); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(mime::text::kPlain, response->GetContentType()); + EXPECT_EQ("", response->GetDataAsString()); // Must not have actual body. + EXPECT_EQ("4", response->GetHeader(request_header::kContentLength)); +} + +TEST(HttpUtils, PostBinary) { + auto Handler = [](const fake::ServerRequest& request, + fake::ServerResponse* response) { + EXPECT_EQ(request_type::kPost, request.GetMethod()); + EXPECT_EQ("256", request.GetHeader(request_header::kContentLength)); + EXPECT_EQ(mime::application::kOctet_stream, + request.GetHeader(request_header::kContentType)); + auto&& data = request.GetData(); + EXPECT_EQ(256, data.size()); + + // Sum up all the bytes. + int sum = std::accumulate(data.begin(), data.end(), 0); + EXPECT_EQ(32640, sum); // sum(i, i => [0, 255]) = 32640. + response->ReplyText(status_code::Ok, "", mime::text::kPlain); + }; + + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(Handler)); + + /// Fill the data buffer with bytes from 0x00 to 0xFF. + std::vector<unsigned char> data(256); + unsigned char counter = 0xFF; + std::generate(data.begin(), data.end(), [&counter]() { return ++counter; }); + + auto response = http::PostBinary(kFakeUrl, data.data(), data.size(), + transport); + EXPECT_TRUE(response->IsSuccessful()); +} TEST(HttpUtils, PostText) { std::string fake_data = "Some data"; @@ -21,37 +227,108 @@ fake::ServerResponse* response) { EXPECT_EQ(request_type::kPost, request.GetMethod()); EXPECT_EQ(fake_data.size(), - atoi(request.GetHeader(request_header::kContentLength).c_str())); + std::stoul(request.GetHeader(request_header::kContentLength))); EXPECT_EQ(mime::text::kPlain, request.GetHeader(request_header::kContentType)); - response->Reply(status_code::Ok, request.GetData(), mime::text::kPlain); + response->ReplyText(status_code::Ok, request.GetDataAsString(), + mime::text::kPlain); }; std::shared_ptr<fake::Transport> transport(new fake::Transport); - transport->AddHandler(fake_url, request_type::kPost, base::Bind(PostHandler)); + transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(PostHandler)); - auto response = http::PostText(fake_url, fake_data.c_str(), + auto response = http::PostText(kFakeUrl, 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); - }; - +TEST(HttpUtils, PostFormData) { std::shared_ptr<fake::Transport> transport(new fake::Transport); - transport->AddHandler(fake_url, request_type::kGet, base::Bind(GetHandler)); + transport->AddHandler(kFakeUrl, request_type::kPost, + base::Bind(EchoDataHandler)); - for (std::string data : {"blah", "some data", ""}) { - std::string url = url::AppendQueryParam(fake_url, "test", data); - EXPECT_EQ(data, http::GetAsString(url, transport)); - } + auto response = http::PostFormData(kFakeUrl, { + {"key", "value"}, + {"field", "field value"}, + }, transport); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(mime::application::kWwwFormUrlEncoded, response->GetContentType()); + EXPECT_EQ("key=value&field=field+value", response->GetDataAsString()); } + +TEST(HttpUtils, PostPatchJson) { + auto JsonHandler = [](const fake::ServerRequest& request, + fake::ServerResponse* response) { + EXPECT_EQ(mime::application::kJson, + request.GetHeader(request_header::kContentType)); + base::DictionaryValue json; + json.SetString("method", request.GetMethod()); + json.SetString("data", request.GetDataAsString()); + response->ReplyJson(status_code::Ok, &json); + }; + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler(kFakeUrl, "*", base::Bind(JsonHandler)); + + base::DictionaryValue json; + json.SetString("key1", "val1"); + json.SetString("key2", "val2"); + std::string value; + + // Test POST + auto response = http::PostJson(kFakeUrl, &json, transport); + auto resp_json = http::ParseJsonResponse(response.get(), nullptr, nullptr); + EXPECT_NE(nullptr, resp_json.get()); + EXPECT_TRUE(resp_json->GetString("method", &value)); + EXPECT_EQ(request_type::kPost, value); + EXPECT_TRUE(resp_json->GetString("data", &value)); + EXPECT_EQ("{\"key1\":\"val1\",\"key2\":\"val2\"}", value); + + // Test PATCH + response = http::PatchJson(kFakeUrl, &json, transport); + resp_json = http::ParseJsonResponse(response.get(), nullptr, nullptr); + EXPECT_NE(nullptr, resp_json.get()); + EXPECT_TRUE(resp_json->GetString("method", &value)); + EXPECT_EQ(request_type::kPatch, value); + EXPECT_TRUE(resp_json->GetString("data", &value)); + EXPECT_EQ("{\"key1\":\"val1\",\"key2\":\"val2\"}", value); +} + +TEST(HttpUtils, ParseJsonResponse) { + auto JsonHandler = [](const fake::ServerRequest& request, + fake::ServerResponse* response) { + base::DictionaryValue json; + json.SetString("data", request.GetFormField("value")); + int status_code = std::stoi(request.GetFormField("code")); + response->ReplyJson(status_code, &json); + }; + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(JsonHandler)); + + // Test valid JSON responses (with success or error codes). + for (auto&& item : {"200;data", "400;wrong", "500;Internal Server error"}) { + auto pair = string_utils::SplitAtFirst(item, ';'); + auto response = http::PostFormData(kFakeUrl, { + {"code", pair.first}, + {"value", pair.second}, + }, transport); + int code = 0; + auto json = http::ParseJsonResponse(response.get(), &code, nullptr); + EXPECT_NE(nullptr, json.get()); + std::string value; + EXPECT_TRUE(json->GetString("data", &value)); + EXPECT_EQ(pair.first, std::to_string(code)); + EXPECT_EQ(pair.second, value); + } + + // Test invalid (non-JSON) reponse. + auto response = http::Get("http://bad.url", transport); + EXPECT_EQ(status_code::NotFound, response->GetStatusCode()); + EXPECT_EQ(mime::text::kHtml, response->GetContentType()); + int code = 0; + auto json = http::ParseJsonResponse(response.get(), &code, nullptr); + EXPECT_EQ(nullptr, json.get()); + EXPECT_EQ(status_code::NotFound, code); +} +