You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

474 lines
12 KiB
C++

#include <fstream>
#include <iostream>
#include "boost/algorithm/string.hpp"
#include "gtest/gtest.h"
#include "json/json.h"
#include "webcc/client_session.h"
#include "webcc/fs.h"
#include "webcc/string.h"
// -----------------------------------------------------------------------------
// JSON helper functions (based on jsoncpp).
// Parse a string to JSON object.
static Json::Value StringToJson(const std::string& str) {
Json::Value json;
Json::CharReaderBuilder builder;
std::istringstream stream{ str };
std::string errors;
if (!Json::parseFromStream(builder, stream, &json, &errors)) {
std::cerr << errors << std::endl;
}
return json;
}
// -----------------------------------------------------------------------------
TEST(ClientTest, Head) {
webcc::ClientSession session;
try {
auto r = session.Send(webcc::RequestBuilder{}.
Head("http://httpbin.org/get")
());
EXPECT_EQ(webcc::Status::kOK, r->status());
EXPECT_EQ("OK", r->reason());
EXPECT_TRUE(r->HasHeader(webcc::headers::kContentLength));
// The response of HTTP HEAD method has no body.
EXPECT_EQ("", r->data());
} catch (const webcc::Error& error) {
std::cerr << error << std::endl;
}
}
// -----------------------------------------------------------------------------
TEST(ClientTest, Get) {
webcc::ClientSession session;
try {
auto r = session.Send(webcc::RequestBuilder{}.
Get("http://httpbin.org/get").
Query("key1", "value1").Query("key2", "value2").
Header("Accept", "application/json")
());
EXPECT_EQ(webcc::Status::kOK, r->status());
EXPECT_EQ("OK", r->reason());
Json::Value json = StringToJson(r->data());
Json::Value args = json["args"];
EXPECT_EQ(2, args.size());
EXPECT_EQ("value1", args["key1"].asString());
EXPECT_EQ("value2", args["key2"].asString());
Json::Value headers = json["headers"];
EXPECT_EQ("application/json", headers["Accept"].asString());
EXPECT_EQ("identity", headers["Accept-Encoding"].asString());
EXPECT_EQ("httpbin.org", headers["Host"].asString());
} catch (const webcc::Error& error) {
std::cerr << error << std::endl;
}
}
// Test the space in the query string could be encoded.
TEST(ClientTest, Get_QueryEncode) {
webcc::ClientSession session;
try {
auto r = session.Send(webcc::RequestBuilder{}.Get("http://httpbin.org/get").
Query("name", "Chunting Gu", true)
());
EXPECT_EQ(webcc::Status::kOK, r->status());
EXPECT_EQ("OK", r->reason());
Json::Value json = StringToJson(r->data());
Json::Value args = json["args"];
EXPECT_EQ(1, args.size());
EXPECT_EQ("Chunting Gu", args["name"].asString());
} catch (const webcc::Error& error) {
std::cerr << error << std::endl;
}
}
#if WEBCC_ENABLE_SSL
TEST(ClientTest, Get_SSL) {
webcc::ClientSession session;
try {
// HTTPS is auto-detected from the URL scheme.
auto r = session.Send(webcc::RequestBuilder{}.
Get("https://httpbin.org/get").
Query("key1", "value1").Query("key2", "value2").
Accept("application/json")
());
EXPECT_EQ(webcc::Status::kOK, r->status());
EXPECT_EQ("OK", r->reason());
Json::Value json = StringToJson(r->data());
Json::Value args = json["args"];
EXPECT_EQ(2, args.size());
EXPECT_EQ("value1", args["key1"].asString());
EXPECT_EQ("value2", args["key2"].asString());
Json::Value headers = json["headers"];
EXPECT_EQ("application/json", headers["Accept"].asString());
EXPECT_EQ("identity", headers["Accept-Encoding"].asString());
EXPECT_EQ("httpbin.org", headers["Host"].asString());
} catch (const webcc::Error& error) {
std::cerr << error << std::endl;
}
}
#endif // WEBCC_ENABLE_SSL
// Get a JPEG image (without streaming).
TEST(ClientTest, Get_Jpeg_NoStream) {
webcc::ClientSession session;
try {
auto r = session.Send(webcc::RequestBuilder{}.
Get("http://httpbin.org/image/jpeg")
());
// TODO: Verify the response is a valid JPEG image.
//std::ofstream ofs(<path>, std::ios::binary);
//ofs << r->data();
} catch (const webcc::Error& error) {
std::cerr << error << std::endl;
}
}
TEST(ClientTest, Get_Jpeg_Stream) {
webcc::ClientSession session;
try {
auto r = session.Send(webcc::RequestBuilder{}.
Get("http://httpbin.org/image/jpeg")
(), true);
auto file_body = r->file_body();
EXPECT_TRUE(!!file_body);
EXPECT_TRUE(!file_body->path().empty());
// Backup the path of the temp file.
const webcc::fs::path ori_path = file_body->path();
const webcc::fs::path new_path("./wolf.jpeg");
bool moved = file_body->Move(new_path);
EXPECT_TRUE(moved);
EXPECT_TRUE(webcc::fs::exists(new_path));
// The file in the original path should not exist any more.
EXPECT_TRUE(!webcc::fs::exists(ori_path));
// After move, the original path should be reset.
EXPECT_TRUE(file_body->path().empty());
} catch (const webcc::Error& error) {
std::cerr << error << std::endl;
}
}
// Test whether the streamed file will be deleted or not at the end if it's
// not moved to another path by the user.
TEST(ClientTest, Get_Jpeg_Stream_NoMove) {
webcc::ClientSession session;
try {
webcc::fs::path ori_path;
{
auto r = session.Send(webcc::RequestBuilder{}.
Get("http://httpbin.org/image/jpeg")
(), true);
auto file_body = r->file_body();
EXPECT_TRUE(!!file_body);
EXPECT_TRUE(!file_body->path().empty());
// Backup the path of the temp file.
ori_path = file_body->path();
}
// The temp file should be deleted.
EXPECT_TRUE(!webcc::fs::exists(ori_path));
} catch (const webcc::Error& error) {
std::cerr << error << std::endl;
}
}
// -----------------------------------------------------------------------------
#if WEBCC_ENABLE_GZIP
// Test Gzip compressed response.
TEST(ClientTest, Get_Gzip) {
webcc::ClientSession session;
session.AcceptGzip();
try {
auto r = session.Send(webcc::RequestBuilder{}.
Get("http://httpbin.org/gzip")
());
Json::Value json = StringToJson(r->data());
EXPECT_EQ(true, json["gzipped"].asBool());
} catch (const webcc::Error& error) {
std::cerr << error << std::endl;
}
}
// Test Deflate compressed response.
TEST(ClientTest, Get_Deflate) {
webcc::ClientSession session;
session.AcceptGzip();
try {
auto r = session.Send(webcc::RequestBuilder{}.
Get("http://httpbin.org/deflate")
());
Json::Value json = StringToJson(r->data());
EXPECT_EQ(true, json["deflated"].asBool());
} catch (const webcc::Error& error) {
std::cerr << error << std::endl;
}
}
#endif // WEBCC_ENABLE_GZIP
// -----------------------------------------------------------------------------
TEST(ClientTest, Post) {
webcc::ClientSession session;
try {
const std::string data = "{'name'='Adam', 'age'=20}";
auto r = session.Send(webcc::RequestBuilder{}.
Post("http://httpbin.org/post").Body(data).Json()
());
EXPECT_EQ(webcc::Status::kOK, r->status());
EXPECT_EQ("OK", r->reason());
Json::Value json = StringToJson(r->data());
EXPECT_EQ(data, json["data"].asString());
} catch (const webcc::Error& error) {
std::cerr << error << std::endl;
}
}
static webcc::fs::path GenerateTempFile(const std::string& data) {
try {
webcc::fs::path path =
webcc::fs::temp_directory_path() / webcc::RandomString(10);
webcc::fs::ofstream ofs;
ofs.open(path, std::ios::binary);
if (ofs.fail()) {
return webcc::fs::path{};
}
ofs << data;
return path;
} catch (const webcc::fs::filesystem_error&) {
return webcc::fs::path{};
}
}
TEST(ClientTest, Post_FileBody) {
webcc::ClientSession session;
const std::string data = "{'name'='Adam', 'age'=20}";
auto path = GenerateTempFile(data);
if (path.empty()) {
return;
}
try {
auto r = session.Send(webcc::RequestBuilder{}.
Post("http://httpbin.org/post").File(path)
());
EXPECT_EQ(webcc::Status::kOK, r->status());
EXPECT_EQ("OK", r->reason());
Json::Value json = StringToJson(r->data());
EXPECT_EQ(data, json["data"].asString());
} catch (const webcc::Error& error) {
std::cerr << error << std::endl;
}
// Remove the temp file.
webcc::fs::error_code ec;
webcc::fs::remove(path, ec);
}
#if WEBCC_ENABLE_GZIP
TEST(ClientTest, Post_Gzip_SmallData) {
webcc::ClientSession session;
try {
// This data is too small to be compressed.
const std::string data = "{'name'='Adam', 'age'=20}";
// This doesn't really compress the body!
auto r = session.Send(webcc::RequestBuilder{}.
Post("http://httpbin.org/post").Body(data).Json().
Gzip()
());
//Json::Value json = StringToJson(r->data());
} catch (const webcc::Error& error) {
std::cerr << error << std::endl;
}
}
#endif // WEBCC_ENABLE_GZIP
#if (WEBCC_ENABLE_GZIP && WEBCC_ENABLE_SSL)
// NOTE: Most servers don't support compressed requests!
TEST(ClientTest, Post_Gzip) {
webcc::ClientSession session;
try {
// Use Boost.org home page as the POST data.
auto r1 = session.Send(webcc::RequestBuilder{}.
Get("https://www.boost.org/")
());
const std::string& data = r1->data();
auto r2 = session.Send(webcc::RequestBuilder{}.
Post("http://httpbin.org/post").Body(data).Gzip()
());
EXPECT_EQ(webcc::Status::kOK, r2->status());
EXPECT_EQ("OK", r2->reason());
} catch (const webcc::Error& error) {
std::cerr << error << std::endl;
}
}
#endif // (WEBCC_ENABLE_GZIP && WEBCC_ENABLE_SSL)
// -----------------------------------------------------------------------------
// Test persistent (keep-alive) connections.
//
// NOTE:
// Boost.org doesn't support persistent connection and always includes
// "Connection: Close" header in the response.
// Both Google and GitHub support persistent connection but they don't like
// to include "Connection: Keep-Alive" header in the responses.
// URLs:
// "http://httpbin.org/get";
// "https://www.boost.org/LICENSE_1_0.txt";
// "https://www.google.com";
// "https://api.github.com/events";
//
TEST(ClientTest, KeepAlive) {
webcc::ClientSession session;
const std::string url = "http://httpbin.org/get";
try {
// Keep-Alive by default.
auto r = session.Send(webcc::RequestBuilder{}.Get(url)());
EXPECT_TRUE(boost::iequals(r->GetHeader("Connection"), "Keep-Alive"));
// Close by setting Connection header directly.
r = session.Send(webcc::RequestBuilder{}.Get(url).
Header("Connection", "Close")
());
EXPECT_TRUE(boost::iequals(r->GetHeader("Connection"), "Close"));
// Close by using request builder.
r = session.Send(webcc::RequestBuilder{}.Get(url).KeepAlive(false)());
EXPECT_TRUE(boost::iequals(r->GetHeader("Connection"), "Close"));
// Keep-Alive explicitly by using request builder.
r = session.Send(webcc::RequestBuilder{}.Get(url).KeepAlive(true)());
EXPECT_TRUE(boost::iequals(r->GetHeader("Connection"), "Keep-Alive"));
} catch (const webcc::Error& error) {
std::cerr << error << std::endl;
}
}
// Test that URL scheme is NOT case sensitive.
TEST(ClientTest, UpperCaseUrlScheme) {
webcc::ClientSession session;
try {
auto r = session.Send(WEBCC_GET("HTTP://httpbin.org/get")());
EXPECT_EQ(webcc::Status::kOK, r->status());
} catch (const webcc::Error& error) {
std::cerr << error << std::endl;
}
}
TEST(ClientTest, InvalidUrlScheme) {
webcc::ClientSession session;
webcc::Error::Code error_code = webcc::Error::kUnknownError;
try {
// NOTE: "httb" is not a valid/supported scheme.
auto r = session.Send(WEBCC_GET("httb://httpbin.org/get")());
} catch (const webcc::Error& error) {
error_code = error.code();
}
EXPECT_EQ(webcc::Error::kSyntaxError, error_code);
}