/**
 *
 *  @file test.cc
 *  @author An Tao
 *
 *  Copyright 2018, An Tao.  All rights reserved.
 *  https://github.com/an-tao/drogon
 *  Use of this source code is governed by a MIT license
 *  that can be found in the License file.
 *
 *  Drogon
 *
 */

// Make a http client to test the example server app;

#define DROGON_TEST_MAIN
#include <drogon/HttpAppFramework.h>
#include <drogon/drogon.h>
#include <drogon/drogon_test.h>
#include <trantor/net/EventLoopThread.h>
#include <trantor/net/TcpClient.h>

#include <algorithm>
#include <atomic>
#include <mutex>

#ifndef _WIN32
#include <unistd.h>
#endif

#include <sys/stat.h>

#define JPG_LEN 44618UL
size_t indexLen;
size_t indexImplicitLen;

using namespace drogon;

Cookie sessionID;

void doTest(const HttpClientPtr &client, std::shared_ptr<test::Case> TEST_CTX) {
	/// Get cookie
	if (!sessionID) {
		auto req = HttpRequest::newHttpRequest();
		req->setMethod(drogon::Get);
		req->setPath("/");
		std::promise<int> waitCookie;
		auto f = waitCookie.get_future();
		client->sendRequest(req,
				[client, &waitCookie, TEST_CTX](
						ReqResult result, const HttpResponsePtr &resp) {
					REQUIRE(result == ReqResult::Ok);

					auto &id = resp->getCookie("JSESSIONID");
					REQUIRE(id);

					sessionID = id;
					client->addCookie(id);
					waitCookie.set_value(1);
				});
		f.get();
	} else
		client->addCookie(sessionID);

	/// Test session
	auto req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/slow");
	client->sendRequest(
			req,
			[req, client, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);

				auto req1 = HttpRequest::newHttpRequest();
				req1->setMethod(drogon::Get);
				req1->setPath("/slow");
				client->sendRequest(
						req1,
						[req1, TEST_CTX](ReqResult result,
								const HttpResponsePtr &resp1) {
							REQUIRE(result == ReqResult::Ok);

							auto &json = resp1->jsonObject();
							REQUIRE(json != nullptr);
							REQUIRE(json->get("message", std::string("")).asString() ==
									"Access interval should be "
									"at least 10 "
									"seconds");
						});
			});
	/// Test gzip
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->addHeader("accept-encoding", "gzip");
	req->setPath("/api/v1/apitest/get/111");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody().length() == 4994UL);
			});
/// Test brotli
#ifdef USE_BROTLI
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->addHeader("accept-encoding", "br");
	req->setPath("/api/v1/apitest/get/111");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody().length() == 4994UL);
			});
#endif
	/// Post json
	Json::Value json;
	json["request"] = "json";
	req = HttpRequest::newCustomHttpRequest(json);
	req->setMethod(drogon::Post);
	req->setPath("/api/v1/apitest/json");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);

				std::shared_ptr<Json::Value> ret = *resp;
				REQUIRE(ret != nullptr);
				CHECK((*ret)["result"].asString() == "ok");
			});
	// Post json again
	req = HttpRequest::newHttpJsonRequest(json);
	assert(req->jsonObject());
	req->setMethod(drogon::Post);
	req->setPath("/api/v1/apitest/json");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);

				std::shared_ptr<Json::Value> ret = *resp;
				REQUIRE(ret != nullptr);
				CHECK((*ret)["result"].asString() == "ok");
			});

	// Post json again
	req = HttpRequest::newHttpJsonRequest(json);
	req->setMethod(drogon::Post);
	req->setPath("/api/v1/apitest/json");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);

				std::shared_ptr<Json::Value> ret = *resp;
				REQUIRE(ret != nullptr);
				CHECK((*ret)["result"].asString() == "ok");
			});

	// Test 404
	req = HttpRequest::newHttpJsonRequest(json);
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/apitest/notFoundRouting");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getStatusCode() == k404NotFound);
			});
	/// 1 Get /
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody() == "<p>Hello, world!</p>");
				// LOG_DEBUG << resp->getBody();
			});
	/// 3. Post to /tpost to test Http Method constraint
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/tpost");
	client->sendRequest(
			req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getStatusCode() == k405MethodNotAllowed);
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Post);
	req->setPath("/tpost");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody() == "<p>Hello, world!</p>");
			});

	/// 4. Http OPTIONS Method
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Options);
	req->setPath("/tpost");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->statusCode() == k200OK);
				auto allow = resp->getHeader("allow");
				CHECK(allow.find("POST") != std::string::npos);
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Options);
	req->setPath("/api/v1/apitest");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->statusCode() == k200OK);
				auto allow = resp->getHeader("allow");
				CHECK(allow == "OPTIONS,GET,HEAD,POST");
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Options);
	req->setPath("/slow");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->statusCode() == k403Forbidden);
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Options);
	req->setPath("/*");
	client->sendRequest(
			req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->statusCode() == k200OK);
				auto allow = resp->getHeader("allow");
				CHECK(allow == "GET,HEAD,POST,PUT,DELETE,OPTIONS,PATCH");
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Options);
	req->setPath("/api/v1/apitest/static");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->statusCode() == k200OK);
				auto allow = resp->getHeader("allow");
				CHECK(allow == "OPTIONS,GET,HEAD");
			});

	/// 4. Test HttpController
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Post);
	req->setPath("/api/v1/apitest");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody() == "ROOT Post!!!");
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/apitest");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody() == "ROOT Get!!!");
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/apitest/get/abc/123");
	client->sendRequest(
			req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto body = resp->getBody();
				CHECK(body.find("<td>p1</td>\n        <td>123</td>") !=
						std::string::npos);
				CHECK(body.find("<td>p2</td>\n        <td>abc</td>") !=
						std::string::npos);
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/apitest/3.14/List");
	req->setParameter("P2", "1234");
	client->sendRequest(
			req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto body = resp->getBody();
				CHECK(body.find("<td>p1</td>\n        <td>3.140000</td>") !=
						std::string::npos);
				CHECK(body.find("<td>p2</td>\n        <td>1234</td>") !=
						std::string::npos);
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/reg/123/rest/of/the/path");
	client->sendRequest(
			req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				REQUIRE(resp->getJsonObject() != nullptr);

				auto &json = resp->getJsonObject();
				CHECK(json->isMember("p1"));
				CHECK(json->get("p1", 0).asInt() == 123);
				CHECK(json->isMember("p2"));
				CHECK(json->get("p2", "").asString() == "rest/of/the/path");
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/apitest/static");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody() == "staticApi,hello!!");
			});

	// auto loop = app().loop();

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Post);
	req->setPath("/api/v1/apitest/static");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody() == "staticApi,hello!!");
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/apitest/get/111");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody().length() == 4994UL);
			});

	/// Test method routing, see MethodTest.h
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Post);
	req->setPath("/api/method/regex/drogon/test?test=drogon");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto jsonPtr = resp->getJsonObject();
				CHECK(jsonPtr);
				CHECK(jsonPtr->asString() == "POST");
			});
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/method/regex/drogon/test");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto jsonPtr = resp->getJsonObject();
				CHECK(jsonPtr);
				CHECK(jsonPtr->asString() == "GET");
			});
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Post);
	req->setPath("/api/method/drogon/test?test=drogon");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto jsonPtr = resp->getJsonObject();
				CHECK(jsonPtr);
				CHECK(jsonPtr->asString() == "POST");
			});
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/method/drogon/test");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto jsonPtr = resp->getJsonObject();
				CHECK(jsonPtr);
				CHECK(jsonPtr->asString() == "GET");
			});
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Post);
	req->setPath("/api/method/test?test=drogon");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto jsonPtr = resp->getJsonObject();
				CHECK(jsonPtr);
				CHECK(jsonPtr->asString() == "POST");
			});
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/method/test");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto jsonPtr = resp->getJsonObject();
				CHECK(jsonPtr);
				CHECK(jsonPtr->asString() == "GET");
			});
	/// Test static function
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/handle11/11/2 2/?p3=3 x");
	req->setParameter("p4", "44");
	client->sendRequest(
			req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto body = resp->getBody();
				CHECK(body.find("<td>int p1</td>\n        <td>11</td>") !=
						std::string::npos);
				CHECK(body.find("<td>int p4</td>\n        <td>44</td>") !=
						std::string::npos);
				CHECK(body.find("<td>string p2</td>\n        <td>2 2</td>") !=
						std::string::npos);
				CHECK(body.find("<td>string p3</td>\n        <td>3 x</td>") !=
						std::string::npos);
			});
	/// Test Incomplete URL
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/handle11/11/2 2/");
	client->sendRequest(
			req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto body = resp->getBody();
				CHECK(body.find("<td>int p1</td>\n        <td>11</td>") !=
						std::string::npos);
				CHECK(body.find("<td>int p4</td>\n        <td>0</td>") !=
						std::string::npos);
				CHECK(body.find("<td>string p2</td>\n        <td>2 2</td>") !=
						std::string::npos);
				CHECK(body.find("<td>string p3</td>\n        <td></td>") !=
						std::string::npos);
			});
	/// Test lambda
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/handle2/111/222");
	client->sendRequest(
			req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto body = resp->getBody();
				CHECK(body.find("<td>a</td>\n        <td>111</td>") !=
						std::string::npos);
				CHECK(body.find("<td>b</td>\n        <td>222.000000</td>") !=
						std::string::npos);
			});

	/// Test std::bind and std::function
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/handle4/444/333/111");
	client->sendRequest(
			req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto body = resp->getBody();
				CHECK(body.find("<td>int p1</td>\n        <td>111</td>") !=
						std::string::npos);
				CHECK(body.find("<td>int p4</td>\n        <td>444</td>") !=
						std::string::npos);
				CHECK(body.find("<td>string p2</td>\n        <td></td>") !=
						std::string::npos);
				CHECK(body.find("<td>string p3</td>\n        <td>333</td>") !=
						std::string::npos);
			});
	/// Test gzip_static
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/index.html");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody().length() == indexLen);
			});
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/index.html");
	req->addHeader("accept-encoding", "gzip");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody().length() == indexLen);
			});
	// Test 405
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Post);
	req->setPath("/drogon.jpg");
	client->sendRequest(req,
			[req, client, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getStatusCode() ==
						k405MethodNotAllowed);
			});
	/// Test file download
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/drogon.jpg");
	client->sendRequest(
			req,
			[req, client, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				REQUIRE(resp->getBody().length() == JPG_LEN);
				auto &lastModified = resp->getHeader("last-modified");
				// LOG_DEBUG << lastModified;
				// Test 'Not Modified'
				auto req = HttpRequest::newHttpRequest();
				req->setMethod(drogon::Get);
				req->setPath("/drogon.jpg");
				req->addHeader("If-Modified-Since", lastModified);
				client->sendRequest(req,
						[req, TEST_CTX](ReqResult result,
								const HttpResponsePtr &resp) {
							REQUIRE(result == ReqResult::Ok);
							REQUIRE(resp->statusCode() ==
									k304NotModified);
							// pro.set_value(1);
						});
			});

	/// Test file download, It is forbidden to download files from the
	/// parent folder
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/../../drogon.jpg");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->statusCode() == k403Forbidden);
			});
	/// Test controllers created and initialized by users
	req = HttpRequest::newHttpRequest();
	req->setPath("/customctrl/antao");
	req->addHeader("custom_header", "yes");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody() == "<P>Hi, antao</P>");
			});
	/// Test controllers created and initialized by users
	req = HttpRequest::newHttpRequest();
	req->setPath("/absolute/123");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->statusCode() == k200OK);
			});
	/// Test synchronous advice
	req = HttpRequest::newHttpRequest();
	req->setPath("/plaintext");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody() == "Hello, World!");
			});
	/// Test form post
	req = HttpRequest::newHttpFormPostRequest();
	req->setPath("/api/v1/apitest/form");
	req->setParameter("k1", "1");
	req->setParameter("k2", "安");
	req->setParameter("k3", "test@example.com");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto ret = resp->getJsonObject();
				REQUIRE(ret != nullptr);
				CHECK((*ret)["result"].asString() == "ok");
			});

	/// Test attributes
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/apitest/attrs");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto ret = resp->getJsonObject();
				REQUIRE(ret != nullptr);
				CHECK((*ret)["result"].asString() == "ok");
			});

	/// Test attachment download
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/attachment/download");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody().length() == JPG_LEN);
			});
	// Test implicit pages
	auto body = std::make_shared<std::string>();
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/a-directory");
	client->sendRequest(req,
			[req, TEST_CTX, body](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody().length() == indexImplicitLen);
				*body = std::string(resp->getBody().data(),
						resp->getBody().length());
			});
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/a-directory/page.html");
	client->sendRequest(req,
			[req, TEST_CTX, body](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getBody().length() == indexImplicitLen);
				CHECK(std::equal(body->begin(),
						body->end(),
						resp->getBody().begin()));
			});
	// Test file upload
	UploadFile file1("./drogon.jpg");
	UploadFile file2("./drogon.jpg", "drogon1.jpg");
	UploadFile file3("./config.example.json", "config.json", "file3");
	req = HttpRequest::newFileUploadRequest({ file1, file2, file3 });
	req->setPath("/api/attachment/upload");
	req->setParameter("P1", "upload");
	req->setParameter("P2", "test");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto json = resp->getJsonObject();
				REQUIRE(json != nullptr);
				CHECK((*json)["result"].asString() == "ok");
				CHECK((*json)["P1"] == "upload");
				CHECK((*json)["P2"] == "test");
			});

	// Test file upload, file type and extension interface.
	UploadFile image("./drogon.jpg");
	req = HttpRequest::newFileUploadRequest({ image });
	req->setPath("/api/attachment/uploadImage");
	req->setParameter("P1", "upload");
	req->setParameter("P2", "test");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				auto json = resp->getJsonObject();
				REQUIRE(json != nullptr);
				CHECK((*json)["P1"] == "upload");
				CHECK((*json)["P2"] == "test");
			});

	// Test exception handling
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/this_will_fail");
	client->sendRequest(
			req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getStatusCode() == k500InternalServerError);
			});

	// The result of this API is cached for (almost) forever. And the endpoint
	// increments a internal counter on each invoke. This tests if the respond
	// is taken from the cache after the first invoke.
	// Try poking the cache test endpoint 3 times. They should all respond 0
	// since the first respond is cached by the server.
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/ApiTest/cacheTest");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getStatusCode() == k200OK);
				CHECK(resp->body() == "0");
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/ApiTest/cacheTest");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getStatusCode() == k200OK);
				CHECK(resp->body() == "0");
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/ApiTest/cacheTest");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getStatusCode() == k200OK);
				CHECK(resp->body() == "0");
			});

	// This API caches it's result on the third (counting from 1) calls. Thus
	// we expect to always see 2 upon the third call. And all previous calls
	// should be less than or equal to 2, as another test is also poking the API
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/ApiTest/cacheTest2");
	client->sendRequest(
			req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getStatusCode() == k200OK);
				int n = 100;
				CHECK_NOTHROW(n = std::stoi(std::string(resp->body())));
				CHECK(n <= 2);
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/ApiTest/cacheTest2");
	client->sendRequest(
			req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getStatusCode() == k200OK);
				int n = 100;
				CHECK_NOTHROW(n = std::stoi(std::string(resp->body())));
				CHECK(n <= 2);
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/api/v1/ApiTest/cacheTest2");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getStatusCode() == k200OK);
				CHECK(resp->body() == "2");
			});

	// Same as cacheTest2. But the server has to handle this API through regex.
	// it is intentionally made that the final part of the path can't conatin
	// a "z" character
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/cacheTestRegex/foobar");
	client->sendRequest(
			req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getStatusCode() == k200OK);
				int n = 100;
				CHECK_NOTHROW(n = std::stoi(std::string(resp->body())));
				CHECK(n <= 2);
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/cacheTestRegex/deadbeef");
	client->sendRequest(
			req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getStatusCode() == k200OK);
				int n = 100;
				CHECK_NOTHROW(n = std::stoi(std::string(resp->body())));
				CHECK(n <= 2);
			});

	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/cacheTestRegex/leet");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getStatusCode() == k200OK);
				CHECK(resp->body() == "2");
			});
	req = HttpRequest::newHttpRequest();
	req->setMethod(drogon::Get);
	req->setPath("/cacheTestRegex/zebra");
	client->sendRequest(req,
			[req, TEST_CTX](ReqResult result,
					const HttpResponsePtr &resp) {
				REQUIRE(result == ReqResult::Ok);
				CHECK(resp->getStatusCode() == k404NotFound);
			});

#if defined(__cpp_impl_coroutine)
	sync_wait([client, TEST_CTX]() -> Task<> {
		// Test coroutine requests
		try {
			auto req = HttpRequest::newHttpRequest();
			req->setPath("/api/v1/corotest/get");
			auto resp = co_await client->sendRequestCoro(req);
			CHECK(resp->getBody() == "DEADBEEF");
		} catch (const std::exception &e) {
			FAIL("Unexpected exception, what()" + std::string(e.what()));
		}

		// Test coroutine request with co_return
		try {
			auto req = HttpRequest::newHttpRequest();
			req->setPath("/api/v1/corotest/get2");
			auto resp = co_await client->sendRequestCoro(req);
			CHECK(resp->getBody() == "BADDBEEF");
		} catch (const std::exception &e) {
			FAIL("Unexpected exception, what()" + std::string(e.what()));
		}

		// Test Coroutine exception
		try {
			auto req = HttpRequest::newHttpRequest();
			req->setPath("/api/v1/corotest/this_will_fail");
			auto resp = co_await client->sendRequestCoro(req);
			CHECK(resp->getStatusCode() != k200OK);
		} catch (const std::exception &e) {
			FAIL("Unexpected exception, what()" + std::string(e.what()));
		}

		// Test Coroutine exception with co_return
		try {
			auto req = HttpRequest::newHttpRequest();
			req->setPath("/api/v1/corotest/this_will_fail2");
			auto resp = co_await client->sendRequestCoro(req);
			CHECK(resp->getStatusCode() != k200OK);
		} catch (const std::exception &e) {
			FAIL("Unexpected exception, what()" + std::string(e.what()));
		}
	}());
#endif
}

void loadFileLengths() {
	struct stat filestat;
	if (stat("index.html", &filestat) < 0) {
		LOG_SYSERR << "Unable to retrieve index.html file sizes";
		exit(1);
	}
	indexLen = filestat.st_size;
	if (stat("a-directory/page.html", &filestat) < 0) {
		LOG_SYSERR << "Unable to retrieve a-directory/page.html file sizes";
		exit(1);
	}
	indexImplicitLen = filestat.st_size;
}

DROGON_TEST(HttpTest) {
	auto client = HttpClient::newHttpClient("http://127.0.0.1:8848");
	client->setPipeliningDepth(10);
	doTest(client, TEST_CTX);
}

DROGON_TEST(HttpsTest) {
	if (!app().supportSSL())
		return;

	auto client = HttpClient::newHttpClient("https://127.0.0.1:8849",
			app().getLoop(),
			false,
			false);
	client->setPipeliningDepth(10);
	doTest(client, TEST_CTX);
}

int main(int argc, char **argv) {
	trantor::Logger::setLogLevel(trantor::Logger::LogLevel::kDebug);
	loadFileLengths();

	std::promise<void> p1;
	std::future<void> f1 = p1.get_future();

	std::thread thr([&]() {
		p1.set_value();
		app().run();
	});

	f1.get();
	int testStatus = test::run(argc, argv);
	app().getLoop()->queueInLoop([]() { app().quit(); });
	thr.join();
	return testStatus;
}
