diff options
Diffstat (limited to 'brillo/http/http_utils_unittest.cc')
-rw-r--r-- | brillo/http/http_utils_unittest.cc | 497 |
1 files changed, 497 insertions, 0 deletions
diff --git a/brillo/http/http_utils_unittest.cc b/brillo/http/http_utils_unittest.cc new file mode 100644 index 0000000..ee408b8 --- /dev/null +++ b/brillo/http/http_utils_unittest.cc @@ -0,0 +1,497 @@ +// 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 <numeric> +#include <string> +#include <vector> + +#include <base/values.h> +#include <brillo/bind_lambda.h> +#include <brillo/http/http_transport_fake.h> +#include <brillo/http/http_utils.h> +#include <brillo/mime_utils.h> +#include <brillo/strings/string_utils.h> +#include <brillo/url_utils.h> +#include <gtest/gtest.h> + +namespace brillo { +namespace http { + +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)); +} + +// 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(), brillo::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<uint8_t> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F}; + auto response = + http::SendRequestAndBlock(request_type::kPost, + kEchoUrl, + custom_data.data(), + custom_data.size(), + brillo::mime::application::kOctet_stream, + {}, + transport, + nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::application::kOctet_stream, + response->GetContentType()); + EXPECT_EQ(custom_data, response->ExtractData()); +} + +TEST(HttpUtils, SendRequestAsync_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<uint8_t> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F}; + auto success_callback = + [&custom_data](RequestID id, std::unique_ptr<http::Response> response) { + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::application::kOctet_stream, + response->GetContentType()); + EXPECT_EQ(custom_data, response->ExtractData()); + }; + auto error_callback = [](RequestID id, const Error* error) { + FAIL() << "This callback shouldn't have been called"; + }; + http::SendRequest(request_type::kPost, + kEchoUrl, + custom_data.data(), + custom_data.size(), + brillo::mime::application::kOctet_stream, + {}, + transport, + base::Bind(success_callback), + base::Bind(error_callback)); +} + +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<uint8_t> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F}; + + // Check the correct HTTP method used. + auto response = + http::SendRequestAndBlock(request_type::kPost, + kMethodEchoUrl, + custom_data.data(), + custom_data.size(), + brillo::mime::application::kOctet_stream, + {}, + transport, + nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType()); + EXPECT_EQ(request_type::kPost, response->ExtractDataAsString()); +} + +TEST(HttpUtils, SendRequest_Get) { + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); + + auto response = http::SendRequestAndBlock(request_type::kGet, + kMethodEchoUrl, + nullptr, + 0, + std::string{}, + {}, + transport, + nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType()); + EXPECT_EQ(request_type::kGet, response->ExtractDataAsString()); +} + +TEST(HttpUtils, SendRequest_Put) { + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); + + auto response = http::SendRequestAndBlock(request_type::kPut, + kMethodEchoUrl, + nullptr, + 0, + std::string{}, + {}, + transport, + nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType()); + EXPECT_EQ(request_type::kPut, response->ExtractDataAsString()); +} + +TEST(HttpUtils, SendRequest_NotFound) { + std::shared_ptr<fake::Transport> transport(new fake::Transport); + // Test failed response (URL not found). + auto response = http::SendRequestWithNoDataAndBlock( + request_type::kGet, "http://blah.com", {}, transport, nullptr); + EXPECT_FALSE(response->IsSuccessful()); + EXPECT_EQ(status_code::NotFound, response->GetStatusCode()); +} + +TEST(HttpUtils, SendRequestAsync_NotFound) { + std::shared_ptr<fake::Transport> transport(new fake::Transport); + // Test failed response (URL not found). + auto success_callback = + [](RequestID request_id, std::unique_ptr<http::Response> response) { + EXPECT_FALSE(response->IsSuccessful()); + EXPECT_EQ(status_code::NotFound, response->GetStatusCode()); + }; + auto error_callback = [](RequestID request_id, const Error* error) { + FAIL() << "This callback shouldn't have been called"; + }; + http::SendRequestWithNoData(request_type::kGet, + "http://blah.com", + {}, + transport, + base::Bind(success_callback), + base::Bind(error_callback)); +} + +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 (const 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::SendRequestAndBlock( + request_type::kPost, json_echo_url, "abcd", 4, + brillo::mime::application::kOctet_stream, { + {request_header::kCookie, "flavor=vanilla"}, + {request_header::kIfMatch, "*"}, + }, transport, nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::application::kJson, + brillo::mime::RemoveParameters(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(brillo::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"), + brillo::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() actually does the GET request + auto response = http::GetAndBlock(kMethodEchoUrl, {}, transport, nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType()); + EXPECT_EQ(request_type::kGet, response->ExtractDataAsString()); + + for (std::string data : {"blah", "some data", ""}) { + std::string url = brillo::url::AppendQueryParam(kFakeUrl, "test", data); + response = http::GetAndBlock(url, {}, transport, nullptr); + EXPECT_EQ(data, response->ExtractDataAsString()); + } +} + +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", brillo::mime::text::kPlain); + }; + + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler(kFakeUrl, request_type::kHead, base::Bind(HeadHandler)); + + auto response = http::HeadAndBlock(kFakeUrl, transport, nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType()); + EXPECT_EQ("", response->ExtractDataAsString()); // 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(brillo::mime::application::kOctet_stream, + request.GetHeader(request_header::kContentType)); + const 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, "", brillo::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<uint8_t> data(256); + std::iota(data.begin(), data.end(), 0); + + auto response = http::PostBinaryAndBlock(kFakeUrl, + data.data(), + data.size(), + mime::application::kOctet_stream, + {}, + transport, + nullptr); + EXPECT_TRUE(response->IsSuccessful()); +} + +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(), + std::stoul(request.GetHeader(request_header::kContentLength))); + EXPECT_EQ(brillo::mime::text::kPlain, + request.GetHeader(request_header::kContentType)); + response->ReplyText(status_code::Ok, + request.GetDataAsString(), + brillo::mime::text::kPlain); + }; + + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(PostHandler)); + + auto response = http::PostTextAndBlock(kFakeUrl, + fake_data, + brillo::mime::text::kPlain, + {}, + transport, + nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType()); + EXPECT_EQ(fake_data, response->ExtractDataAsString()); +} + +TEST(HttpUtils, PostFormData) { + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler( + kFakeUrl, request_type::kPost, base::Bind(EchoDataHandler)); + + auto response = http::PostFormDataAndBlock( + kFakeUrl, { + {"key", "value"}, + {"field", "field value"}, + }, {}, transport, nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(brillo::mime::application::kWwwFormUrlEncoded, + response->GetContentType()); + EXPECT_EQ("key=value&field=field+value", response->ExtractDataAsString()); +} + +TEST(HttpUtils, PostMultipartFormData) { + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler( + kFakeUrl, request_type::kPost, base::Bind(EchoDataHandler)); + + std::unique_ptr<FormData> form_data{new FormData{"boundary123"}}; + form_data->AddTextField("key1", "value1"); + form_data->AddTextField("key2", "value2"); + std::string expected_content_type = form_data->GetContentType(); + auto response = http::PostFormDataAndBlock( + kFakeUrl, std::move(form_data), {}, transport, nullptr); + EXPECT_TRUE(response->IsSuccessful()); + EXPECT_EQ(expected_content_type, response->GetContentType()); + const char expected_value[] = + "--boundary123\r\n" + "Content-Disposition: form-data; name=\"key1\"\r\n" + "\r\n" + "value1\r\n" + "--boundary123\r\n" + "Content-Disposition: form-data; name=\"key2\"\r\n" + "\r\n" + "value2\r\n" + "--boundary123--"; + EXPECT_EQ(expected_value, response->ExtractDataAsString()); +} + +TEST(HttpUtils, PostPatchJson) { + auto JsonHandler = + [](const fake::ServerRequest& request, fake::ServerResponse* response) { + auto mime_type = brillo::mime::RemoveParameters( + request.GetHeader(request_header::kContentType)); + EXPECT_EQ(brillo::mime::application::kJson, mime_type); + response->ReplyJson( + status_code::Ok, + { + {"method", request.GetMethod()}, {"data", request.GetDataAsString()}, + }); + }; + 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::PostJsonAndBlock(kFakeUrl, &json, {}, transport, nullptr); + 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::PatchJsonAndBlock(kFakeUrl, &json, {}, transport, nullptr); + 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) { + int status_code = std::stoi(request.GetFormField("code")); + response->ReplyJson(status_code, {{"data", request.GetFormField("value")}}); + }; + 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 = brillo::string_utils::SplitAtFirst(item, ";"); + auto response = http::PostFormDataAndBlock( + kFakeUrl, { + {"code", pair.first}, + {"value", pair.second}, + }, {}, transport, nullptr); + 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, brillo::string_utils::ToString(code)); + EXPECT_EQ(pair.second, value); + } + + // Test invalid (non-JSON) response. + auto response = http::GetAndBlock("http://bad.url", {}, transport, nullptr); + EXPECT_EQ(status_code::NotFound, response->GetStatusCode()); + EXPECT_EQ(brillo::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); +} + +TEST(HttpUtils, SendRequest_Failure) { + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); + ErrorPtr error; + Error::AddTo(&error, FROM_HERE, "test_domain", "test_code", "Test message"); + transport->SetCreateConnectionError(std::move(error)); + error.reset(); // Just to make sure it is empty... + auto response = http::SendRequestWithNoDataAndBlock( + request_type::kGet, "http://blah.com", {}, transport, &error); + EXPECT_EQ(nullptr, response.get()); + EXPECT_EQ("test_domain", error->GetDomain()); + EXPECT_EQ("test_code", error->GetCode()); + EXPECT_EQ("Test message", error->GetMessage()); +} + +TEST(HttpUtils, SendRequestAsync_Failure) { + std::shared_ptr<fake::Transport> transport(new fake::Transport); + transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler)); + ErrorPtr error; + Error::AddTo(&error, FROM_HERE, "test_domain", "test_code", "Test message"); + transport->SetCreateConnectionError(std::move(error)); + auto success_callback = + [](RequestID request_id, std::unique_ptr<http::Response> response) { + FAIL() << "This callback shouldn't have been called"; + }; + auto error_callback = [](RequestID request_id, const Error* error) { + EXPECT_EQ("test_domain", error->GetDomain()); + EXPECT_EQ("test_code", error->GetCode()); + EXPECT_EQ("Test message", error->GetMessage()); + }; + http::SendRequestWithNoData(request_type::kGet, + "http://blah.com", + {}, + transport, + base::Bind(success_callback), + base::Bind(error_callback)); +} + +TEST(HttpUtils, GetCanonicalHeaderName) { + EXPECT_EQ("Foo", GetCanonicalHeaderName("foo")); + EXPECT_EQ("Bar", GetCanonicalHeaderName("BaR")); + EXPECT_EQ("Baz", GetCanonicalHeaderName("BAZ")); + EXPECT_EQ("Foo-Bar", GetCanonicalHeaderName("foo-bar")); + EXPECT_EQ("Foo-Bar-Baz", GetCanonicalHeaderName("foo-Bar-BAZ")); + EXPECT_EQ("Foo-Bar-Baz", GetCanonicalHeaderName("FOO-BAR-BAZ")); + EXPECT_EQ("Foo-Bar-", GetCanonicalHeaderName("fOO-bAR-")); + EXPECT_EQ("-Bar", GetCanonicalHeaderName("-bAR")); + EXPECT_EQ("", GetCanonicalHeaderName("")); + EXPECT_EQ("A-B-C", GetCanonicalHeaderName("a-B-c")); +} + +} // namespace http +} // namespace brillo |