修复: 升级框架并完善报告导出
- 升级 Drogon 和 Trantor,修复畸形请求导致的连接计数泄漏\n- 增加第三方框架版本校验与自动重建\n- 完善完整报告导出和接口文档
This commit is contained in:
@@ -43,6 +43,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND BUILD_SHARED_LIBS)
|
||||
else()
|
||||
set(UNITTEST_SOURCES ${UNITTEST_SOURCES} ../src/HttpFileImpl.cc
|
||||
unittests/HttpFileTest.cc
|
||||
unittests/HttpMethodTest.cc
|
||||
unittests/WebsocketResponseTest.cc)
|
||||
endif()
|
||||
|
||||
|
||||
+38
-14
@@ -5,6 +5,7 @@
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
@@ -100,23 +101,46 @@ DROGON_TEST(RequestStreamTest)
|
||||
|
||||
LOG_INFO << "Test request stream";
|
||||
|
||||
std::string filePath = "./中文.txt";
|
||||
std::ifstream file(filePath);
|
||||
std::stringstream content;
|
||||
REQUIRE(file.is_open());
|
||||
content << file.rdbuf();
|
||||
const auto uniqueSuffix = std::to_string(
|
||||
std::chrono::steady_clock::now().time_since_epoch().count());
|
||||
auto tempDir = std::make_shared<std::filesystem::path>(
|
||||
std::filesystem::temp_directory_path() /
|
||||
("request_stream_upload_test_" + uniqueSuffix));
|
||||
std::filesystem::create_directories(*tempDir);
|
||||
auto tempPath = std::make_shared<std::filesystem::path>(
|
||||
*tempDir / std::filesystem::path(u8"中文.txt"));
|
||||
tempPath->make_preferred();
|
||||
{
|
||||
std::ofstream out(*tempPath, std::ios::binary | std::ios::trunc);
|
||||
REQUIRE(out.is_open());
|
||||
out << "request-stream-upload-content\nline2\n";
|
||||
}
|
||||
|
||||
req = HttpRequest::newFileUploadRequest({UploadFile{filePath}});
|
||||
std::ifstream in(*tempPath, std::ios::binary);
|
||||
REQUIRE(in.is_open());
|
||||
std::stringstream ss;
|
||||
ss << in.rdbuf();
|
||||
const auto uploadContent = std::make_shared<std::string>(ss.str());
|
||||
|
||||
const auto uploadPathUtf8 = std::make_shared<std::string>([&tempPath]() {
|
||||
auto u8Path = tempPath->u8string();
|
||||
return std::string(reinterpret_cast<const char *>(u8Path.data()),
|
||||
u8Path.size());
|
||||
}());
|
||||
req = HttpRequest::newFileUploadRequest({UploadFile{*uploadPathUtf8}});
|
||||
req->setPath("/stream_upload_echo");
|
||||
req->setMethod(Post);
|
||||
client->sendRequest(req,
|
||||
[TEST_CTX,
|
||||
content = content.str()](ReqResult r,
|
||||
const HttpResponsePtr &resp) {
|
||||
CHECK(r == ReqResult::Ok);
|
||||
CHECK(resp->statusCode() == k200OK);
|
||||
CHECK(resp->body() == content);
|
||||
});
|
||||
client->sendRequest(
|
||||
req,
|
||||
[TEST_CTX, tempPath, tempDir, uploadPathUtf8, content = uploadContent](
|
||||
ReqResult r, const HttpResponsePtr &resp) {
|
||||
CHECK(r == ReqResult::Ok);
|
||||
CHECK(resp->statusCode() == k200OK);
|
||||
CHECK(resp->body() == *content);
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(*tempPath, ec);
|
||||
std::filesystem::remove(*tempDir, ec);
|
||||
});
|
||||
|
||||
checkStreamRequest(TEST_CTX,
|
||||
client->getLoop(),
|
||||
|
||||
@@ -728,6 +728,23 @@ void doTest(const HttpClientPtr &client, std::shared_ptr<test::Case> TEST_CTX)
|
||||
CHECK((*json)["P2"] == "test");
|
||||
});
|
||||
|
||||
// Test file upload from memory
|
||||
auto hello = std::make_shared<std::string>("hello world!");
|
||||
UploadFile memfile(hello->data(),
|
||||
hello->length(),
|
||||
"hello_world.txt",
|
||||
"hellofile",
|
||||
ContentType::CT_TEXT_PLAIN);
|
||||
req = HttpRequest::newFileUploadRequest({memfile});
|
||||
req->setPath("/api/attachment/uploadMemory");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX, hello](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
REQUIRE(resp->contentType() == CT_TEXT_PLAIN);
|
||||
CHECK(resp->getBody() == *hello);
|
||||
});
|
||||
|
||||
// Test newFileResponse
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setPath("/RangeTestController/");
|
||||
|
||||
@@ -103,6 +103,32 @@ void Attachment::uploadImage(
|
||||
callback(resp);
|
||||
}
|
||||
|
||||
void Attachment::uploadMemory(
|
||||
const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
MultiPartParser fileUpload;
|
||||
|
||||
if (fileUpload.parse(req) == 0 && fileUpload.getFiles().size() == 1)
|
||||
{
|
||||
auto &file = fileUpload.getFiles()[0];
|
||||
if (file.getItemName() == "hellofile")
|
||||
{
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->setStatusCode(HttpStatusCode::k200OK);
|
||||
resp->setContentTypeCode(ContentType::CT_TEXT_PLAIN);
|
||||
std::string hello = std::string(file.fileData(), file.fileLength());
|
||||
resp->setBody(std::move(hello));
|
||||
callback(resp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG_DEBUG << "upload text from memory error!";
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->setStatusCode(HttpStatusCode::k400BadRequest);
|
||||
callback(resp);
|
||||
}
|
||||
|
||||
void Attachment::download(
|
||||
const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
|
||||
@@ -12,6 +12,7 @@ class Attachment : public drogon::HttpController<Attachment>
|
||||
METHOD_ADD(Attachment::get, "", Get); // Path is '/api/attachment'
|
||||
METHOD_ADD(Attachment::upload, "/upload", Post);
|
||||
METHOD_ADD(Attachment::uploadImage, "/uploadImage", Post);
|
||||
METHOD_ADD(Attachment::uploadMemory, "/uploadMemory", Post);
|
||||
METHOD_ADD(Attachment::download, "/download", Get);
|
||||
METHOD_LIST_END
|
||||
// your declaration of processing function maybe like this:
|
||||
@@ -21,6 +22,8 @@ class Attachment : public drogon::HttpController<Attachment>
|
||||
std::function<void(const HttpResponsePtr &)> &&callback);
|
||||
void uploadImage(const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback);
|
||||
void uploadMemory(const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback);
|
||||
void download(const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback);
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ DROGON_TEST(Base64)
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
|
||||
CHECK(decoded == in);
|
||||
CHECK(drogon::utils::isBase64(encoded));
|
||||
|
||||
SUBSECTION(InvalidChars)
|
||||
{
|
||||
@@ -31,6 +32,7 @@ DROGON_TEST(Base64)
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw");
|
||||
CHECK(decoded == in);
|
||||
CHECK(drogon::utils::isBase64(encoded));
|
||||
}
|
||||
|
||||
SUBSECTION(LongString)
|
||||
@@ -46,6 +48,9 @@ DROGON_TEST(Base64)
|
||||
auto encoded = drogon::utils::base64Encode(in);
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(decoded == in);
|
||||
CHECK(out == encoded);
|
||||
CHECK(drogon::utils::isBase64(out));
|
||||
CHECK(drogon::utils::isBase64(encoded));
|
||||
}
|
||||
|
||||
SUBSECTION(URLSafe)
|
||||
@@ -55,6 +60,7 @@ DROGON_TEST(Base64)
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
|
||||
CHECK(decoded == in);
|
||||
CHECK(drogon::utils::isBase64(encoded));
|
||||
}
|
||||
|
||||
SUBSECTION(UnpaddedURLSafe)
|
||||
@@ -64,6 +70,7 @@ DROGON_TEST(Base64)
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw");
|
||||
CHECK(decoded == in);
|
||||
CHECK(drogon::utils::isBase64(encoded));
|
||||
}
|
||||
|
||||
SUBSECTION(LongURLSafe)
|
||||
@@ -77,5 +84,24 @@ DROGON_TEST(Base64)
|
||||
auto encoded = drogon::utils::base64Encode(in, true);
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(decoded == in);
|
||||
CHECK(drogon::utils::isBase64(encoded));
|
||||
}
|
||||
|
||||
SUBSECTION(emptyString)
|
||||
{
|
||||
auto encoded = "";
|
||||
CHECK(!drogon::utils::isBase64(encoded));
|
||||
}
|
||||
|
||||
SUBSECTION(size1Padding)
|
||||
{
|
||||
auto encoded = "ZHJvZ29uIGZyYW1ld29=";
|
||||
CHECK(drogon::utils::isBase64(encoded));
|
||||
}
|
||||
|
||||
SUBSECTION(size1PaddingNotModulo4)
|
||||
{
|
||||
auto encoded = "ZHJvZ29uIGZyYW1ld29ya=";
|
||||
CHECK(!drogon::utils::isBase64(encoded));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,3 +66,195 @@ DROGON_TEST(ResquestSetCustomContentTypeString)
|
||||
req->setContentTypeString("thisdoesnotexist/unknown");
|
||||
CHECK(req->getContentType() == CT_CUSTOM);
|
||||
}
|
||||
|
||||
DROGON_TEST(HttpOptionsHeadersResponse)
|
||||
{
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
auto resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(!resp);
|
||||
|
||||
req->setMethod(HttpMethod::Options);
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Vary") == "Origin");
|
||||
CHECK(resp->getHeader("Allow") == "OPTIONS");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Origin") == "");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Methods") == "");
|
||||
|
||||
req->attributes()->insert("drogon.corsMethods",
|
||||
std::string("GET, POST, OPTIONS"));
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Vary") == "Origin");
|
||||
CHECK(resp->getHeader("Allow") == "GET, POST, OPTIONS");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Origin") == "");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Methods") == "");
|
||||
|
||||
req->addHeader("Origin", "http://somepage");
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Vary") == "Origin");
|
||||
CHECK(resp->getHeader("Allow") == "GET, POST, OPTIONS");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Origin") == "");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Methods") == "");
|
||||
}
|
||||
|
||||
DROGON_TEST(HttpCorsHeadersResponse)
|
||||
{
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->addHeader("Origin", "");
|
||||
req->addHeader("Access-Control-Request-Method", "OPTIONS");
|
||||
auto resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(!resp);
|
||||
|
||||
// empty origin -> error
|
||||
req->setMethod(HttpMethod::Options);
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k400BadRequest);
|
||||
|
||||
// null origin -> check if allowed or not
|
||||
req->addHeader("Origin", "null");
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k403Forbidden);
|
||||
resp = HttpResponse::newOptionsResponse(req, {}, true);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Origin") == "null");
|
||||
|
||||
// normal origin but no requested method -> error
|
||||
req->addHeader("Origin", "http://somepage");
|
||||
req->addHeader("Access-Control-Request-Method", "");
|
||||
resp = HttpResponse::newOptionsResponse(req, {}, true);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k400BadRequest);
|
||||
|
||||
// valid CORS preflight request
|
||||
req->addHeader("Access-Control-Request-Method", "OPTIONS");
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Vary") == "Origin");
|
||||
CHECK(resp->getHeader("Allow") == "");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Origin") == "http://somepage");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Methods") == "OPTIONS");
|
||||
|
||||
// origin validator
|
||||
resp = HttpResponse::newOptionsResponse(req, [](std::string_view origin) {
|
||||
return origin == "http://somepage";
|
||||
});
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
resp = HttpResponse::newOptionsResponse(req, [](std::string_view origin) {
|
||||
return origin != "http://somepage";
|
||||
});
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k403Forbidden);
|
||||
|
||||
// unallowed method
|
||||
req->addHeader("Access-Control-Request-Method", "PUT");
|
||||
req->attributes()->insert("drogon.corsMethods",
|
||||
std::string("GET,POST,OPTIONS"));
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k405MethodNotAllowed);
|
||||
CHECK(resp->getHeader("Allow") == "GET,POST,OPTIONS");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Methods") ==
|
||||
"GET,POST,OPTIONS");
|
||||
|
||||
// allowed method
|
||||
req->addHeader("Access-Control-Request-Method", "GET");
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Allow") == "");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Origin") == "http://somepage");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Methods") ==
|
||||
"GET,POST,OPTIONS");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Credentials") == "");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Private-Network") == "");
|
||||
CHECK(resp->getHeader("Access-Control-Max-Age") == "");
|
||||
|
||||
// no restriction on requested headers
|
||||
req->addHeader("Access-Control-Request-Headers", "X-Foo, X-Bar");
|
||||
resp = HttpResponse::newOptionsResponse(req);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Headers") == "X-Bar,X-Foo");
|
||||
|
||||
// unallowed header
|
||||
resp = HttpResponse::newOptionsResponse(req, {"X-Foo"});
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k403Forbidden);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Headers") == "X-Foo");
|
||||
|
||||
// all requested headers allowed
|
||||
resp = HttpResponse::newOptionsResponse(req, {"X-Foo", "X-Bar"});
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Headers") == "X-Bar,X-Foo");
|
||||
|
||||
// allow credentials
|
||||
resp = HttpResponse::newOptionsResponse(req, nullptr, false, true);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Credentials") == "true");
|
||||
|
||||
// private network access
|
||||
req->addHeader("Access-Control-Request-Private-Network", "true");
|
||||
resp = HttpResponse::newOptionsResponse(req, nullptr, false, false, false);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Private-Network") == "");
|
||||
resp = HttpResponse::newOptionsResponse(req, nullptr, false, false, true);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Private-Network") == "true");
|
||||
|
||||
// CORS max age
|
||||
resp = HttpResponse::newOptionsResponse(
|
||||
req, nullptr, false, false, false, 600);
|
||||
CHECK(resp->getStatusCode() == HttpStatusCode::k204NoContent);
|
||||
CHECK(resp->getHeader("Access-Control-Max-Age") == "600");
|
||||
}
|
||||
|
||||
DROGON_TEST(AddHttpCorsHeaders)
|
||||
{
|
||||
using namespace std::literals;
|
||||
|
||||
// no Origin -> do nothing
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(Get);
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->addCorsHeaders(req, {"X-Foo"}, true);
|
||||
CHECK(resp->headers().empty());
|
||||
|
||||
// with Origin -> Allow-Origin + Vary (not overwritten) + Expose-Headers
|
||||
req->addHeader("Origin", "http://somepage");
|
||||
resp->addHeader("Vary", "X-SomeHeader");
|
||||
resp->addCorsHeaders(req, {"X-Foo"});
|
||||
CHECK(resp->getHeader("Vary") == "Origin,X-SomeHeader");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Origin") == "http://somepage");
|
||||
CHECK(resp->getHeader("Access-Control-Expose-Headers") == "X-Foo");
|
||||
|
||||
// add a new exposed header
|
||||
resp->addCorsHeaders(req, {"X-Bar"});
|
||||
CHECK(resp->getHeader("Access-Control-Expose-Headers") == "X-Bar,X-Foo");
|
||||
// no duplicate Origin in Vary
|
||||
CHECK(resp->getHeader("Vary") == "Origin,X-SomeHeader");
|
||||
|
||||
// check credentials (true/false/unchanged)
|
||||
resp->addCorsHeaders(req, {}, true);
|
||||
CHECK(resp->getHeader("Access-Control-Expose-Headers") == "X-Bar,X-Foo");
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Credentials") == "true");
|
||||
resp->addCorsHeaders(req, {}, false);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Credentials") == "");
|
||||
resp->addCorsHeaders(req, {}, true);
|
||||
resp->addCorsHeaders(req);
|
||||
CHECK(resp->getHeader("Access-Control-Allow-Credentials") == "true");
|
||||
}
|
||||
|
||||
DROGON_TEST(ClearHeaders)
|
||||
{
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
// set a custom path to ensure it is not cleared
|
||||
req->setPath("/api/test");
|
||||
req->addHeader("X-Test", "value");
|
||||
req->addHeader("Authorization", "Bearer token");
|
||||
|
||||
CHECK(req->headers().size() == 2);
|
||||
CHECK(req->getHeader("X-Test") == "value");
|
||||
CHECK(req->getHeader("Authorization") == "Bearer token");
|
||||
|
||||
req->clearHeaders();
|
||||
|
||||
CHECK(req->headers().empty());
|
||||
// verify path unchanged
|
||||
CHECK(req->path() == "/api/test");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <drogon/HttpTypes.h>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include "../../lib/src/HttpRequestImpl.h"
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
// Helper: parse a method string through HttpRequestImpl::setMethod
|
||||
static std::pair<bool, HttpMethod> parseMethod(const std::string &str)
|
||||
{
|
||||
HttpRequestImpl req(nullptr);
|
||||
bool ok = req.setMethod(str.data(), str.data() + str.size());
|
||||
return {ok, req.method()};
|
||||
}
|
||||
|
||||
DROGON_TEST(StandardHttpMethods)
|
||||
{
|
||||
auto [ok, m] = parseMethod("GET");
|
||||
CHECK(ok);
|
||||
CHECK(m == Get);
|
||||
|
||||
std::tie(ok, m) = parseMethod("POST");
|
||||
CHECK(ok);
|
||||
CHECK(m == Post);
|
||||
|
||||
std::tie(ok, m) = parseMethod("PUT");
|
||||
CHECK(ok);
|
||||
CHECK(m == Put);
|
||||
|
||||
std::tie(ok, m) = parseMethod("DELETE");
|
||||
CHECK(ok);
|
||||
CHECK(m == Delete);
|
||||
|
||||
std::tie(ok, m) = parseMethod("HEAD");
|
||||
CHECK(ok);
|
||||
CHECK(m == Head);
|
||||
|
||||
std::tie(ok, m) = parseMethod("OPTIONS");
|
||||
CHECK(ok);
|
||||
CHECK(m == Options);
|
||||
|
||||
std::tie(ok, m) = parseMethod("PATCH");
|
||||
CHECK(ok);
|
||||
CHECK(m == Patch);
|
||||
}
|
||||
|
||||
DROGON_TEST(WebDavMethods)
|
||||
{
|
||||
auto [ok, m] = parseMethod("PROPFIND");
|
||||
CHECK(ok);
|
||||
CHECK(m == Propfind);
|
||||
|
||||
std::tie(ok, m) = parseMethod("MKCOL");
|
||||
CHECK(ok);
|
||||
CHECK(m == Mkcol);
|
||||
|
||||
std::tie(ok, m) = parseMethod("COPY");
|
||||
CHECK(ok);
|
||||
CHECK(m == Copy);
|
||||
|
||||
std::tie(ok, m) = parseMethod("MOVE");
|
||||
CHECK(ok);
|
||||
CHECK(m == Move);
|
||||
}
|
||||
|
||||
DROGON_TEST(WebDavMethodStrings)
|
||||
{
|
||||
CHECK(to_string_view(Propfind) == "PROPFIND");
|
||||
CHECK(to_string_view(Mkcol) == "MKCOL");
|
||||
CHECK(to_string_view(Copy) == "COPY");
|
||||
CHECK(to_string_view(Move) == "MOVE");
|
||||
}
|
||||
|
||||
// Helper: serialize a request and return the first line (method + path)
|
||||
static std::string serializeMethod(HttpMethod method)
|
||||
{
|
||||
HttpRequestImpl req(nullptr);
|
||||
req.setMethod(method);
|
||||
req.setPath("/test");
|
||||
trantor::MsgBuffer buf;
|
||||
req.appendToBuffer(&buf);
|
||||
std::string result(buf.peek(), buf.readableBytes());
|
||||
// Return just up to the first space after the method
|
||||
auto pos = result.find(' ');
|
||||
return result.substr(0, pos);
|
||||
}
|
||||
|
||||
DROGON_TEST(MethodSerialization)
|
||||
{
|
||||
CHECK(serializeMethod(Get) == "GET");
|
||||
CHECK(serializeMethod(Post) == "POST");
|
||||
CHECK(serializeMethod(Put) == "PUT");
|
||||
CHECK(serializeMethod(Delete) == "DELETE");
|
||||
CHECK(serializeMethod(Head) == "HEAD");
|
||||
CHECK(serializeMethod(Options) == "OPTIONS");
|
||||
CHECK(serializeMethod(Patch) == "PATCH");
|
||||
CHECK(serializeMethod(Propfind) == "PROPFIND");
|
||||
CHECK(serializeMethod(Mkcol) == "MKCOL");
|
||||
CHECK(serializeMethod(Copy) == "COPY");
|
||||
CHECK(serializeMethod(Move) == "MOVE");
|
||||
}
|
||||
|
||||
DROGON_TEST(InvalidMethodsRejected)
|
||||
{
|
||||
auto [ok, m] = parseMethod("INVALID");
|
||||
CHECK(!ok);
|
||||
CHECK(m == Invalid);
|
||||
|
||||
std::tie(ok, m) = parseMethod("LOCK");
|
||||
CHECK(!ok);
|
||||
CHECK(m == Invalid);
|
||||
|
||||
std::tie(ok, m) = parseMethod("");
|
||||
CHECK(!ok);
|
||||
CHECK(m == Invalid);
|
||||
|
||||
std::tie(ok, m) = parseMethod("G");
|
||||
CHECK(!ok);
|
||||
CHECK(m == Invalid);
|
||||
}
|
||||
Reference in New Issue
Block a user