diff options
-rw-r--r-- | chrome/chrome_tests.gypi | 4 | ||||
-rw-r--r-- | chrome/test/webdriver/dispatch.cc | 221 | ||||
-rw-r--r-- | chrome/test/webdriver/dispatch.h | 13 | ||||
-rw-r--r-- | chrome/test/webdriver/dispatch_unittest.cc | 154 | ||||
-rw-r--r-- | chrome/test/webdriver/http_response.cc | 135 | ||||
-rw-r--r-- | chrome/test/webdriver/http_response.h | 80 | ||||
-rw-r--r-- | chrome/test/webdriver/http_response_unittest.cc | 89 |
7 files changed, 563 insertions, 133 deletions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index fc7eea0..4d8f57d 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -733,6 +733,8 @@ 'test/webdriver/dispatch.h', 'test/webdriver/dispatch.cc', 'test/webdriver/error_codes.h', + 'test/webdriver/http_response.h', + 'test/webdriver/http_response.cc', 'test/webdriver/keymap.h', 'test/webdriver/keymap.cc', 'test/webdriver/session.h', @@ -851,6 +853,8 @@ ], 'sources': [ '../base/test/run_all_unittests.cc', + 'test/webdriver/dispatch_unittest.cc', + 'test/webdriver/http_response_unittest.cc', 'test/webdriver/utility_functions_unittest.cc', 'test/webdriver/webdriver_key_converter_unittest.cc', ], diff --git a/chrome/test/webdriver/dispatch.cc b/chrome/test/webdriver/dispatch.cc index aa83a2b..28a94da 100644 --- a/chrome/test/webdriver/dispatch.cc +++ b/chrome/test/webdriver/dispatch.cc @@ -14,6 +14,7 @@ #include "base/string_util.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" +#include "chrome/test/webdriver/http_response.h" #include "chrome/test/webdriver/commands/command.h" #include "chrome/test/webdriver/session_manager.h" #include "chrome/test/webdriver/utility_functions.h" @@ -22,119 +23,12 @@ namespace webdriver { namespace { -// The standard HTTP Status codes are implemented below. Chrome uses -// OK, See Other, Not Found, Method Not Allowed, and Internal Error. -// Internal Error, HTTP 500, is used as a catch all for any issue -// not covered in the JSON protocol. -void SendHttpOk(struct mg_connection* const connection, - const struct mg_request_info* const request_info, - const Response& response) { - const std::string json = response.ToJSON(); - std::ostringstream out; - out << "HTTP/1.1 200 OK\r\n" - << "Content-Length: " << strlen(json.c_str()) << "\r\n" - << "Content-Type: application/json; charset=UTF-8\r\n" - << "Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept\r\n" - << "Accept-Ranges: bytes\r\n" - << "Connection: close\r\n\r\n"; - if (strcmp(request_info->request_method, "HEAD") != 0) - out << json << "\r\n"; - VLOG(1) << out.str(); - mg_printf(connection, "%s", out.str().c_str()); -} - -void SendHttpSeeOther(struct mg_connection* const connection, - const struct mg_request_info* const request_info, - const Response& response) { - const Value* value = response.value(); - CheckValueType(Value::TYPE_STRING, value); - std::string location; - if (!value->GetAsString(&location)) { - NOTREACHED(); - } - - std::ostringstream out; - out << "HTTP/1.1 303 See Other\r\n" - << "Location: " << location << "\r\n" - << "Content-Type: text/html\r\n" - << "Content-Length: 0\r\n\r\n"; - VLOG(1) << out.str(); - mg_printf(connection, "%s", out.str().c_str()); -} - -void SendHttpBadRequest(struct mg_connection* const connection, - const struct mg_request_info* const request_info, - const Response& response) { - const std::string json = response.ToJSON(); - std::ostringstream out; - out << "HTTP/1.1 400 Bad Request\r\n" - << "Content-Length: " << strlen(json.c_str()) << "\r\n" - << "Content-Type: application/json; charset=UTF-8\r\n" - << "Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept\r\n" - << "Accept-Ranges: bytes\r\n" - << "Connection: close\r\n\r\n"; - if (strcmp(request_info->request_method, "HEAD") != 0) - out << json << "\r\n"; - VLOG(1) << out.str(); - mg_printf(connection, "%s", out.str().c_str()); -} - -void SendHttpNotFound(struct mg_connection* const connection, - const struct mg_request_info* const request_info, - const Response& response) { - const std::string json = response.ToJSON(); - std::ostringstream out; - out << "HTTP/1.1 404 Not Found\r\n" - << "Content-Length: " << strlen(json.c_str()) << "\r\n" - << "Content-Type: application/json; charset=UTF-8\r\n" - << "Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept\r\n" - << "Accept-Ranges: bytes\r\n" - << "Connection: close\r\n\r\n"; - if (strcmp(request_info->request_method, "HEAD") != 0) - out << json << "\r\n"; - VLOG(1) << out.str(); - mg_printf(connection, "%s", out.str().c_str()); -} - -void SendHttpMethodNotAllowed(struct mg_connection* const connection, - const struct mg_request_info* const request_info, - const Response& response) { - const Value* value = response.value(); - CheckValueType(Value::TYPE_LIST, value); - - std::vector<std::string> allowed_methods; - const ListValue* list_value = static_cast<const ListValue*>(value); - for (size_t i = 0; i < list_value->GetSize(); ++i) { - std::string method; - LOG_IF(WARNING, list_value->GetString(i, &method)) - << "Ignoring non-string value at index " << i; - allowed_methods.push_back(method); - } - - std::ostringstream out; - out << "HTTP/1.1 405 Method Not Allowed\r\n" - << "Content-Type: text/html\r\n" - << "Content-Length: 0\r\n" - << "Allow: " << JoinString(allowed_methods, ',') << "\r\n\r\n"; - VLOG(1) << out.str(); - mg_printf(connection, "%s", out.str().c_str()); -} - -void SendHttpInternalError(struct mg_connection* const connection, - const struct mg_request_info* const request_info, - const Response& response) { - const std::string json = response.ToJSON(); - std::ostringstream out; - out << "HTTP/1.1 500 Internal Server Error\r\n" - << "Content-Length: " << strlen(json.c_str()) << "\r\n" - << "Content-Type: application/json; charset=UTF-8\r\n" - << "Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept\r\n" - << "Accept-Ranges: bytes\r\n" - << "Connection: close\r\n\r\n"; - if (strcmp(request_info->request_method, "HEAD") != 0) - out << json << "\r\n"; - VLOG(1) << out.str(); - mg_printf(connection, "%s", out.str().c_str()); +bool ForbidsMessageBody(const std::string& request_method, + const HttpResponse& response) { + return request_method == "HEAD" || + response.status() == HttpResponse::kNoContent || + response.status() == HttpResponse::kNotModified || + (response.status() >= 100 && response.status() < 200); } void DispatchCommand(Command* const command, @@ -158,37 +52,102 @@ void DispatchCommand(Command* const command, namespace internal { -void SendResponse(struct mg_connection* const connection, - const struct mg_request_info* const request_info, - const Response& response) { - switch (response.status()) { +void PrepareHttpResponse(const Response& command_response, + HttpResponse* const http_response) { + switch (command_response.status()) { case kSuccess: - SendHttpOk(connection, request_info, response); + http_response->set_status(HttpResponse::kOk); break; - case kSeeOther: - SendHttpSeeOther(connection, request_info, response); + // TODO(jleyba): kSeeOther, kBadRequest, kSessionNotFound, + // and kMethodNotAllowed should be detected before creating + // a command_response, and should thus not need conversion. + case kSeeOther: { + const Value* const value = command_response.value(); + std::string location; + if (!value->GetAsString(&location)) { + // This should never happen. + http_response->set_status(HttpResponse::kInternalServerError); + http_response->SetBody("Unable to set 'Location' header: response " + "value is not a string: " + + command_response.ToJSON()); + return; + } + http_response->AddHeader("Location", location); + http_response->set_status(HttpResponse::kSeeOther); break; + } case kBadRequest: - SendHttpBadRequest(connection, request_info, response); - break; - case kSessionNotFound: - SendHttpNotFound(connection, request_info, response); + http_response->set_status(command_response.status()); break; - case kMethodNotAllowed: - SendHttpMethodNotAllowed(connection, request_info, response); + case kMethodNotAllowed: { + const Value* const value = command_response.value(); + if (!value->IsType(Value::TYPE_LIST)) { + // This should never happen. + http_response->set_status(HttpResponse::kInternalServerError); + http_response->SetBody( + "Unable to set 'Allow' header: response value was " + "not a list of strings: " + command_response.ToJSON()); + return; + } + + const ListValue* const list_value = + static_cast<const ListValue* const>(value); + std::vector<std::string> allowed_methods; + for (size_t i = 0; i < list_value->GetSize(); ++i) { + std::string method; + if (list_value->GetString(i, &method)) { + allowed_methods.push_back(method); + } else { + // This should never happen. + http_response->set_status(HttpResponse::kInternalServerError); + http_response->SetBody( + "Unable to set 'Allow' header: response value was " + "not a list of strings: " + command_response.ToJSON()); + return; + } + } + http_response->AddHeader("Allow", JoinString(allowed_methods, ',')); + http_response->set_status(HttpResponse::kMethodNotAllowed); break; + } - // All other errors should be treated as generic 500s. The client will be - // responsible for inspecting the message body for details. - case kInternalServerError: - default: - SendHttpInternalError(connection, request_info, response); + // All other errors should be treated as generic 500s. The client + // will be responsible for inspecting the message body for details. + case kInternalServerError: + default: + http_response->set_status(HttpResponse::kInternalServerError); break; } + + http_response->SetMimeType("application/json; charset=utf-8"); + http_response->SetBody(command_response.ToJSON()); +} + +void SendResponse(struct mg_connection* const connection, + const std::string& request_method, + const Response& response) { + HttpResponse http_response; + PrepareHttpResponse(response, &http_response); + + std::string message_header = base::StringPrintf("HTTP/1.1 %d %s\r\n", + http_response.status(), http_response.GetReasonPhrase().c_str()); + + typedef HttpResponse::HeaderMap::const_iterator HeaderIter; + for (HeaderIter header = http_response.headers()->begin(); + header != http_response.headers()->end(); + ++header) { + message_header.append(base::StringPrintf("%s:%s\r\n", + header->first.c_str(), header->second.c_str())); + } + message_header.append("\r\n"); + + mg_write(connection, message_header.data(), message_header.length()); + if (!ForbidsMessageBody(request_method, http_response)) + mg_write(connection, http_response.data(), http_response.length()); } bool ParseRequestInfo(const struct mg_request_info* const request_info, diff --git a/chrome/test/webdriver/dispatch.h b/chrome/test/webdriver/dispatch.h index 817dd7c..c119e9c 100644 --- a/chrome/test/webdriver/dispatch.h +++ b/chrome/test/webdriver/dispatch.h @@ -16,14 +16,20 @@ class DictionaryValue; namespace webdriver { class Command; +class HttpResponse; namespace internal { +// Converts a |Response| into a |HttpResponse| to be returned to the client. +// This function is exposed for testing. +void PrepareHttpResponse(const Response& command_response, + HttpResponse* const http_response); + // Sends a |response| to a WebDriver command back to the client. // |connection| is the communication pipe to the HTTP server and // |request_info| contains any data sent by the user. void SendResponse(struct mg_connection* const connection, - const struct mg_request_info* const request_info, + const std::string& request_method, const Response& response); // Parses the request info and returns whether parsing was successful. If not, @@ -62,7 +68,10 @@ void Dispatch(struct mg_connection* connection, method, &response); } - internal::SendResponse(connection, request_info, response); + + internal::SendResponse(connection, + request_info->request_method, + response); } } // namespace webdriver diff --git a/chrome/test/webdriver/dispatch_unittest.cc b/chrome/test/webdriver/dispatch_unittest.cc new file mode 100644 index 0000000..c724041 --- /dev/null +++ b/chrome/test/webdriver/dispatch_unittest.cc @@ -0,0 +1,154 @@ +// Copyright (c) 2011 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 "base/format_macros.h" +#include "base/scoped_ptr.h" +#include "base/stringprintf.h" +#include "base/values.h" +#include "base/json/json_reader.h" +#include "chrome/test/webdriver/dispatch.h" +#include "chrome/test/webdriver/http_response.h" +#include "chrome/test/webdriver/commands/response.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace webdriver { + +namespace { + +void ExpectHeaderValue(const HttpResponse& response, const std::string& name, + const std::string& expected_value) { + std::string actual_value; + EXPECT_TRUE(response.GetHeader(name, &actual_value)); + EXPECT_EQ(expected_value, actual_value); +} + +void ExpectHttpStatus(int expected_status, + const Response& command_response, + HttpResponse* const http_response) { + internal::PrepareHttpResponse(command_response, http_response); + EXPECT_EQ(expected_status, http_response->status()); +} + +void ExpectInternalError(ErrorCode command_status, + Response* command_response, + HttpResponse* const http_response) { + command_response->set_status(command_status); + http_response->set_status(HttpResponse::kOk); // Reset to detect changes. + ExpectHttpStatus(HttpResponse::kInternalServerError, + *command_response, http_response); +} + +} // namespace + +TEST(DispatchTest, CorrectlyConvertsResponseCodesToHttpStatusCodes) { + HttpResponse http_response; + + Response command_response; + command_response.set_value(Value::CreateStringValue("foobar")); + + command_response.set_status(kSuccess); + ExpectHttpStatus(HttpResponse::kOk, command_response, &http_response); + + command_response.set_status(kSeeOther); + ExpectHttpStatus(HttpResponse::kSeeOther, command_response, &http_response); + ExpectHeaderValue(http_response, "location", "foobar"); + http_response.ClearHeaders(); + + command_response.set_status(kBadRequest); + ExpectHttpStatus(HttpResponse::kBadRequest, command_response, + &http_response); + + command_response.set_status(kSessionNotFound); + ExpectHttpStatus(HttpResponse::kNotFound, command_response, + &http_response); + + ListValue* methods = new ListValue; + methods->Append(Value::CreateStringValue("POST")); + methods->Append(Value::CreateStringValue("GET")); + command_response.set_value(methods); + command_response.set_status(kMethodNotAllowed); + ExpectHttpStatus(HttpResponse::kMethodNotAllowed, command_response, + &http_response); + ExpectHeaderValue(http_response, "allow", "POST,GET"); + http_response.ClearHeaders(); + + ExpectInternalError(kNoSuchElement, &command_response, &http_response); + ExpectInternalError(kNoSuchFrame, &command_response, &http_response); + ExpectInternalError(kUnknownCommand, &command_response, &http_response); + ExpectInternalError(kStaleElementReference, &command_response, + &http_response); + ExpectInternalError(kInvalidElementState, &command_response, &http_response); + ExpectInternalError(kUnknownError, &command_response, &http_response); + ExpectInternalError(kElementNotSelectable, &command_response, + &http_response); + ExpectInternalError(kXPathLookupError, &command_response, &http_response); + ExpectInternalError(kNoSuchWindow, &command_response, &http_response); + ExpectInternalError(kInvalidCookieDomain, &command_response, &http_response); + ExpectInternalError(kUnableToSetCookie, &command_response, &http_response); + ExpectInternalError(kInternalServerError, &command_response, &http_response); +} + +TEST(DispatchTest, + ReturnsAnErrorOnNonStringMethodsListedOnAMethodNotAllowedResponse) { + ListValue* methods = new ListValue; + methods->Append(Value::CreateStringValue("POST")); + methods->Append(new DictionaryValue); + methods->Append(Value::CreateStringValue("GET")); + methods->Append(new DictionaryValue); + methods->Append(Value::CreateStringValue("DELETE")); + + Response command_response; + command_response.set_status(kMethodNotAllowed); + command_response.set_value(methods); + + HttpResponse http_response; + ExpectHttpStatus(HttpResponse::kInternalServerError, command_response, + &http_response); +} + +TEST(DispatchTest, ReturnsCommandResponseAsJson) { + const std::string kExpectedData = "{\"status\":0,\"value\":\"foobar\"}"; + + Response command_response; + command_response.set_status(kSuccess); + command_response.set_value(Value::CreateStringValue("foobar")); + + HttpResponse http_response; + internal::PrepareHttpResponse(command_response, &http_response); + EXPECT_EQ(HttpResponse::kOk, http_response.status()); + ExpectHeaderValue(http_response, "content-type", + "application/json; charset=utf-8"); + ExpectHeaderValue(http_response, "content-length", + base::StringPrintf("%"PRIuS, kExpectedData.length())); + + // We do not know whether the response status or value will be + // encoded first, so we have to parse the response body to + // verify it is correct. + std::string actual_data(http_response.data(), + http_response.length()); + + int error_code; + std::string error_message; + scoped_ptr<Value> parsed_response(base::JSONReader::ReadAndReturnError( + actual_data, false, &error_code, &error_message)); + + ASSERT_TRUE(parsed_response.get() != NULL) << error_message; + ASSERT_TRUE(parsed_response->IsType(Value::TYPE_DICTIONARY)) + << "Response should be a dictionary: " << actual_data; + + DictionaryValue* dict = static_cast<DictionaryValue*>(parsed_response.get()); + EXPECT_EQ(2u, dict->size()); + EXPECT_TRUE(dict->HasKey("status")); + EXPECT_TRUE(dict->HasKey("value")); + + int status = -1; + EXPECT_TRUE(dict->GetInteger("status", &status)); + EXPECT_EQ(kSuccess, static_cast<ErrorCode>(status)); + + std::string value; + EXPECT_TRUE(dict->GetStringASCII("value", &value)); + EXPECT_EQ("foobar", value); +} + +} // namespace webdriver diff --git a/chrome/test/webdriver/http_response.cc b/chrome/test/webdriver/http_response.cc new file mode 100644 index 0000000..7757e52 --- /dev/null +++ b/chrome/test/webdriver/http_response.cc @@ -0,0 +1,135 @@ +// Copyright (c) 2011 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 "chrome/test/webdriver/http_response.h" + +#include "base/format_macros.h" +#include "base/string_util.h" +#include "base/stringprintf.h" + +namespace webdriver { + +const int HttpResponse::kOk = 200; +const int HttpResponse::kNoContent = 204; +const int HttpResponse::kSeeOther = 303; +const int HttpResponse::kNotModified = 304; +const int HttpResponse::kBadRequest = 400; +const int HttpResponse::kNotFound = 404; +const int HttpResponse::kMethodNotAllowed = 405; +const int HttpResponse::kInternalServerError = 500; + +HttpResponse::HttpResponse() + : status_(kOk) { +} + +HttpResponse::~HttpResponse() { +} + +void HttpResponse::AddHeader(const std::string& name, + const std::string& value) { + std::string lower_case_name(StringToLowerASCII(name)); + HeaderMap::iterator header = headers_.find(lower_case_name); + if (header == headers_.end()) { + headers_[lower_case_name] = value; + } else { + header->second.append("," + value); + } +} + +bool HttpResponse::GetHeader(const std::string& name, + std::string* value) const { + std::string lower_case_name(StringToLowerASCII(name)); + HeaderMap::const_iterator header = headers_.find(lower_case_name); + + if (header == headers_.end()) { + return false; + } + + if (value) { + *value = header->second; + } + + return true; +} + +bool HttpResponse::RemoveHeader(const std::string& name) { + std::string lower_case_name(StringToLowerASCII(name)); + HeaderMap::iterator header = headers_.find(lower_case_name); + + if (header == headers_.end()) { + return false; + } + + headers_.erase(header); + return true; +} + +void HttpResponse::ClearHeaders() { + headers_.clear(); +} + +void HttpResponse::UpdateHeader(const std::string& name, + const std::string& new_value) { + RemoveHeader(name); + AddHeader(name, new_value); +} + +void HttpResponse::SetMimeType(const std::string& mime_type) { + UpdateHeader("Content-Type", mime_type); +} + +void HttpResponse::SetBody(const std::string& data) { + SetBody(data.data(), data.length()); +} + +void HttpResponse::SetBody(const char* const data, size_t length) { + data_ = std::string(data, length); + UpdateHeader("Content-Length", + base::StringPrintf("%"PRIuS"", data_.length())); +} + +std::string HttpResponse::GetReasonPhrase() const { + switch (status_) { + case kOk: + return "OK"; + case kNoContent: + return "No Content"; + case kSeeOther: + return "See Other"; + case kNotModified: + return "Not Modified"; + case kBadRequest: + return "Bad Request"; + case kNotFound: + return "Not Found"; + case kMethodNotAllowed: + return "Method Not Allowed"; + case kInternalServerError: + return "Internal Server Error"; + default: + return "Unknown"; + } +} + +int HttpResponse::status() const { + return status_; +} + +void HttpResponse::set_status(int status) { + status_ = status; +} + +const HttpResponse::HeaderMap* const HttpResponse::headers() const { + return &headers_; +} + +const char* const HttpResponse::data() const { + return data_.data(); +} + +size_t HttpResponse::length() const { + return data_.length(); +} + +} // namespace webdriver diff --git a/chrome/test/webdriver/http_response.h b/chrome/test/webdriver/http_response.h new file mode 100644 index 0000000..9139d92 --- /dev/null +++ b/chrome/test/webdriver/http_response.h @@ -0,0 +1,80 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_TEST_WEBDRIVER_HTTP_RESPONSE_H_ +#define CHROME_TEST_WEBDRIVER_HTTP_RESPONSE_H_ +#pragma once + +#include <map> +#include <string> + +#include "base/basictypes.h" + +namespace webdriver { + +class HttpResponse { + public: + typedef std::map<std::string, std::string> HeaderMap; + + // The supported HTTP response codes. + static const int kOk; + static const int kNoContent; + static const int kSeeOther; + static const int kNotModified; + static const int kBadRequest; + static const int kNotFound; + static const int kMethodNotAllowed; + static const int kInternalServerError; + + HttpResponse(); + ~HttpResponse(); + + // Sets a header in this response. If a header with the same |name| already + // exists, the |value| will be appended to the existing values. Since header + // names are case insensitive, the header will be stored in lowercase format. + void AddHeader(const std::string& name, const std::string& value); + + // Retrieves the value of the specified header. If there is no such header, + // the output |value| will not be modified and false will be returned. + bool GetHeader(const std::string& name, std::string* value) const; + + // Removes the header with the given |name|. Returns whether there was a + // matching header to remove. + bool RemoveHeader(const std::string& name); + + // Removes all headers. + void ClearHeaders(); + + // Convenience function for setting the Content-Type header for this response. + void SetMimeType(const std::string& mime_type); + + // Sets the message body for this response; will also set the "Content-Length" + // message header. + void SetBody(const std::string& data); + void SetBody(const char* const data, size_t length); + + // Returns the status phrase recommended by RFC 2616 section 6.1.1 for this + // response's status code. If the status code is not recognized, the default + // "Unknown" status phrase will be used. + std::string GetReasonPhrase() const; + + int status() const; + void set_status(int status); + const HeaderMap* const headers() const; + const char* const data() const; + size_t length() const; + + private: + void UpdateHeader(const std::string& name, const std::string& new_value); + + int status_; + HeaderMap headers_; + std::string data_; + + DISALLOW_COPY_AND_ASSIGN(HttpResponse); +}; + +} // webdriver + +#endif // CHROME_TEST_WEBDRIVER_HTTP_RESPONSE_H_ diff --git a/chrome/test/webdriver/http_response_unittest.cc b/chrome/test/webdriver/http_response_unittest.cc new file mode 100644 index 0000000..71be83f --- /dev/null +++ b/chrome/test/webdriver/http_response_unittest.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2011 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 "chrome/test/webdriver/http_response.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace webdriver { + +namespace { + +void ExpectHeaderValue(const HttpResponse& response, const std::string& name, + const std::string& expected_value) { + std::string actual_value; + EXPECT_TRUE(response.GetHeader(name, &actual_value)); + EXPECT_EQ(expected_value, actual_value); +} + +} // namespace + +TEST(HttpResponseTest, AddingHeaders) { + HttpResponse response; + + response.AddHeader("FOO", "a"); + ExpectHeaderValue(response, "foo", "a"); + + // Headers should be case insensitive. + response.AddHeader("fOo", "b,c"); + response.AddHeader("FoO", "d"); + ExpectHeaderValue(response, "foo", "a,b,c,d"); +} + +TEST(HttpResponseTest, RemovingHeaders) { + HttpResponse response; + + ASSERT_FALSE(response.RemoveHeader("I-Am-Not-There")); + + ASSERT_FALSE(response.GetHeader("foo", NULL)); + response.AddHeader("foo", "bar"); + ASSERT_TRUE(response.GetHeader("foo", NULL)); + ASSERT_TRUE(response.RemoveHeader("foo")); + ASSERT_FALSE(response.GetHeader("foo", NULL)); +} + +TEST(HttpResponseTest, CanClearAllHeaders) { + HttpResponse response; + response.AddHeader("food", "cheese"); + response.AddHeader("color", "red"); + + ExpectHeaderValue(response, "food", "cheese"); + ExpectHeaderValue(response, "color", "red"); + + response.ClearHeaders(); + EXPECT_FALSE(response.GetHeader("food", NULL)); + EXPECT_FALSE(response.GetHeader("color", NULL)); +} + +TEST(HttpResponseTest, CanSetMimeType) { + HttpResponse response; + + response.SetMimeType("application/json"); + ExpectHeaderValue(response, "content-type", "application/json"); + + response.SetMimeType("text/html"); + ExpectHeaderValue(response, "content-type", "text/html"); +} + +TEST(HttpResponseTest, SetBody) { + HttpResponse response; + + std::string body("foo bar"); + response.SetBody(body); + ASSERT_EQ(body.length(), response.length()); + ASSERT_EQ(body, std::string(response.data(), response.length())); + + // Grow the response size. + body.append(" baz"); + response.SetBody(body); + ASSERT_EQ(body.length(), response.length()); + ASSERT_EQ(body, std::string(response.data(), response.length())); + + // Shrink the response size. + body = "small"; + response.SetBody(body); + ASSERT_EQ(body.length(), response.length()); + ASSERT_EQ(body, std::string(response.data(), response.length())); +} + +} // namespace webdriver |