复现已有算法

This commit is contained in:
cloud
2026-07-14 15:43:18 +08:00
parent abebd2a683
commit 50b8111fd9
860 changed files with 182250 additions and 18 deletions
@@ -0,0 +1,81 @@
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
#include <string>
DROGON_TEST(Base64)
{
std::string in{"drogon framework"};
auto encoded = drogon::utils::base64Encode(in);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
CHECK(decoded == in);
SUBSECTION(InvalidChars)
{
auto decoded =
drogon::utils::base64Decode("ZHJvZ2*9uIGZy**YW1ld2***9yaw*=*=");
CHECK(decoded == in);
}
SUBSECTION(InvalidCharsNoPadding)
{
auto decoded =
drogon::utils::base64Decode("ZHJvZ2*9uIGZy**YW1ld2***9yaw**");
CHECK(decoded == in);
}
SUBSECTION(Unpadded)
{
std::string in{"drogon framework"};
auto encoded = drogon::utils::base64EncodeUnpadded(in);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw");
CHECK(decoded == in);
}
SUBSECTION(LongString)
{
std::string in;
in.reserve(100000);
for (int i = 0; i < 100000; ++i)
{
in.append(1, char(i));
}
auto out = drogon::utils::base64Encode(in);
auto out2 = drogon::utils::base64Decode(out);
auto encoded = drogon::utils::base64Encode(in);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(decoded == in);
}
SUBSECTION(URLSafe)
{
std::string in{"drogon framework"};
auto encoded = drogon::utils::base64Encode(in, true);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
CHECK(decoded == in);
}
SUBSECTION(UnpaddedURLSafe)
{
std::string in{"drogon framework"};
auto encoded = drogon::utils::base64EncodeUnpadded(in, true);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw");
CHECK(decoded == in);
}
SUBSECTION(LongURLSafe)
{
std::string in;
in.reserve(100000);
for (int i = 0; i < 100000; ++i)
{
in.append(1, char(i));
}
auto encoded = drogon::utils::base64Encode(in, true);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(decoded == in);
}
}
@@ -0,0 +1,30 @@
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
#include <string>
#include <iostream>
using namespace drogon::utils;
DROGON_TEST(BrotliTest)
{
SUBSECTION(shortText)
{
std::string source{"123中文顶替要枯械"};
auto compressed = brotliCompress(source.data(), source.length());
auto decompressed =
brotliDecompress(compressed.data(), compressed.length());
CHECK(source == decompressed);
}
SUBSECTION(longText)
{
std::string source;
for (size_t i = 0; i < 100000; i++)
{
source.append(std::to_string(i));
}
auto compressed = brotliCompress(source.data(), source.length());
auto decompressed =
brotliDecompress(compressed.data(), compressed.length());
CHECK(source == decompressed);
}
}
@@ -0,0 +1,39 @@
#include <drogon/drogon_test.h>
#include <drogon/CacheMap.h>
#include <drogon/HttpAppFramework.h>
#include <trantor/net/EventLoopThread.h>
#include <chrono>
using namespace drogon;
using namespace std::chrono_literals;
DROGON_TEST(CacheMapTest)
{
trantor::EventLoopThread loopThread;
loopThread.run();
drogon::CacheMap<std::string, std::string> cache(loopThread.getLoop(),
0.1f,
4,
30);
for (size_t i = 1; i < 40; i++)
cache.insert(std::to_string(i), "a", i);
cache.insert("bla", "");
cache.insert("zzz", "-");
std::this_thread::sleep_for(3s);
CHECK(cache.find("0") == false); // doesn't exist
CHECK(cache.find("1") == false); // timeout
CHECK(cache.find("15") == true);
CHECK(cache.find("bla") == true);
cache.erase("30");
CHECK(cache.find("30") == false);
cache.modify("bla", [](std::string &s) { s = "asd"; });
CHECK(cache["bla"] == "asd");
std::string content;
cache.findAndFetch("zzz", content);
CHECK(content == "-");
}
@@ -0,0 +1,29 @@
#include <drogon/drogon_test.h>
namespace api
{
namespace v1
{
template <typename T>
class handler : public drogon::DrObject<T>
{
public:
static std::string name()
{
return handler<T>::classTypeName();
}
};
class hh : public handler<hh>
{
};
} // namespace v1
} // namespace api
DROGON_TEST(ClassName)
{
api::v1::hh h;
CHECK(h.className() == "api::v1::hh");
CHECK(api::v1::hh::classTypeName() == "api::v1::hh");
CHECK(h.name() == "api::v1::hh");
}
@@ -0,0 +1,48 @@
#include <drogon/HttpController.h>
#include <drogon/HttpSimpleController.h>
#include <drogon/WebSocketController.h>
#include <drogon/drogon_test.h>
class Ctrl : public drogon::HttpController<Ctrl, false>
{
public:
static void initPathRouting()
{
created = true;
};
static bool created;
};
class SimpleCtrl : public drogon::HttpController<Ctrl, false>
{
public:
static void initPathRouting()
{
created = true;
};
static bool created;
};
class WsCtrl : public drogon::WebSocketController<WsCtrl, false>
{
public:
static void initPathRouting()
{
created = true;
};
static bool created;
};
bool Ctrl::created = false;
bool SimpleCtrl::created = false;
bool WsCtrl::created = false;
DROGON_TEST(ControllerCreation)
{
REQUIRE(Ctrl::created == false);
REQUIRE(SimpleCtrl::created == false);
REQUIRE(WsCtrl::created == false);
}
@@ -0,0 +1,63 @@
#include <drogon/Cookie.h>
#include <drogon/drogon_test.h>
DROGON_TEST(CookieTest)
{
drogon::Cookie cookie1("test", "1");
CHECK(cookie1.cookieString() == "Set-Cookie: test=1; HttpOnly\r\n");
drogon::Cookie cookie2("test", "2");
cookie2.setSecure(true);
CHECK(cookie2.cookieString() == "Set-Cookie: test=2; Secure; HttpOnly\r\n");
drogon::Cookie cookie3("test", "3");
cookie3.setDomain("drogon.org");
cookie3.setExpiresDate(trantor::Date(1621561557000000L));
CHECK(cookie3.cookieString() ==
"Set-Cookie: test=3; Expires=Fri, 21 May 2021 01:45:57 GMT; "
"Domain=drogon.org; HttpOnly\r\n");
drogon::Cookie cookie4("test", "4");
cookie4.setMaxAge(3600);
cookie4.setSameSite(drogon::Cookie::SameSite::kStrict);
CHECK(cookie4.cookieString() ==
"Set-Cookie: test=4; Max-Age=3600; "
"SameSite=Strict; HttpOnly\r\n");
drogon::Cookie cookie5("test", "5");
cookie5.setSameSite(drogon::Cookie::SameSite::kNone);
CHECK(cookie5.cookieString() ==
"Set-Cookie: test=5; "
"SameSite=None; Secure; HttpOnly\r\n");
CHECK(drogon::Cookie::SameSite::kLax ==
drogon::Cookie::convertString2SameSite("Lax"));
CHECK(drogon::Cookie::SameSite::kStrict ==
drogon::Cookie::convertString2SameSite("Strict"));
CHECK(drogon::Cookie::SameSite::kNone ==
drogon::Cookie::convertString2SameSite("None"));
// Test for Partitioned attribute
drogon::Cookie cookie6("test", "6");
cookie6.setPartitioned(true);
CHECK(cookie6.cookieString() ==
"Set-Cookie: test=6; Secure; HttpOnly; Partitioned\r\n");
// Test that partitioned attribute automatically sets secure
drogon::Cookie cookie7("test", "7");
cookie7.setPartitioned(true);
CHECK(cookie7.isSecure() == true);
// Test other attributes
drogon::Cookie cookie8("test", "8");
cookie8.setPartitioned(true);
cookie8.setDomain("drogon.org");
cookie8.setMaxAge(3600);
CHECK(cookie8.cookieString() ==
"Set-Cookie: test=8; Max-Age=3600; Domain=drogon.org; Secure; "
"HttpOnly; Partitioned\r\n");
// Teset Partitioned and SameSite can coexist
drogon::Cookie cookie9("test", "9");
cookie9.setPartitioned(true);
cookie9.setSameSite(drogon::Cookie::SameSite::kLax);
CHECK(
cookie9.cookieString() ==
"Set-Cookie: test=9; SameSite=Lax; Secure; HttpOnly; Partitioned\r\n");
}
@@ -0,0 +1,330 @@
#include <drogon/drogon_test.h>
#include <drogon/utils/coroutine.h>
#include <drogon/HttpAppFramework.h>
#include <trantor/net/EventLoopThread.h>
#include <trantor/net/EventLoopThreadPool.h>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <exception>
#include <future>
#include <memory>
#include <mutex>
#include <optional>
#include <type_traits>
using namespace drogon;
namespace drogon::internal
{
struct SomeStruct
{
~SomeStruct()
{
beenDestructed = true;
}
static bool beenDestructed;
};
bool SomeStruct::beenDestructed = false;
struct StructAwaiter : public CallbackAwaiter<std::shared_ptr<SomeStruct>>
{
void await_suspend(std::coroutine_handle<> handle)
{
setValue(std::make_shared<SomeStruct>());
handle.resume();
}
};
} // namespace drogon::internal
// Workaround limitation of macros
template <typename T>
using is_int = std::is_same<T, int>;
template <typename T>
using is_void = std::is_same<T, void>;
DROGON_TEST(CroutineBasics)
{
// Basic checks making sure coroutine works as expected
STATIC_REQUIRE(is_awaitable_v<Task<>>);
STATIC_REQUIRE(is_awaitable_v<Task<int>>);
STATIC_REQUIRE(is_awaitable_v<Task<>>);
STATIC_REQUIRE(is_awaitable_v<Task<int>>);
STATIC_REQUIRE(is_int<await_result_t<Task<int>>>::value);
STATIC_REQUIRE(is_void<await_result_t<Task<>>>::value);
// No, you cannot await AsyncTask. By design
STATIC_REQUIRE(is_awaitable_v<AsyncTask> == false);
// AsyncTask should execute eagerly
int m = 0;
[&m]() -> AsyncTask {
m = 1;
co_return;
}();
REQUIRE(m == 1);
// Make sure sync_wait works
CHECK(sync_wait([]() -> Task<int> { co_return 1; }()) == 1);
// make sure it does affect the outside world
int n = 0;
sync_wait([&]() -> Task<> {
n = 1;
co_return;
}());
CHECK(n == 1);
// Testing that exceptions can propagate through coroutines
auto throw_in_task = [TEST_CTX]() -> Task<> {
auto f = []() -> Task<> { throw std::runtime_error("test error"); };
CHECK_THROWS_AS(co_await f(), std::runtime_error);
};
sync_wait(throw_in_task());
// Test sync_wait propagates exception
auto throws = []() -> Task<> {
throw std::runtime_error("bla");
co_return;
};
CHECK_THROWS_AS(sync_wait(throws()), std::runtime_error);
// Test co_return non-copyable object works
auto return_unique_ptr = [TEST_CTX]() -> Task<std::unique_ptr<int>> {
co_return std::make_unique<int>(42);
};
CHECK(*sync_wait(return_unique_ptr()) == 42);
// Test co_awaiting non-copyable object works
auto await_non_copyable = [TEST_CTX]() -> Task<> {
auto return_unique_ptr = []() -> Task<std::unique_ptr<int>> {
co_return std::make_unique<int>(123);
};
auto ptr = co_await return_unique_ptr();
CHECK(*ptr == 123);
};
sync_wait(await_non_copyable());
// This only works because async_run tries to run the coroutine as soon as
// possible and the coroutine does not wait
int testVar = 0;
async_run([&testVar]() -> Task<void> {
testVar = 1;
co_return;
});
CHECK(testVar == 1);
async_run([TEST_CTX]() -> Task<void> {
auto val =
co_await queueInLoopCoro<int>(app().getLoop(), []() { return 42; });
CHECK(val == 42);
});
async_run([TEST_CTX]() -> Task<void> {
co_await queueInLoopCoro<void>(app().getLoop(), []() { LOG_DEBUG; });
});
}
DROGON_TEST(CompilcatedCoroutineLifetime)
{
auto coro = []() -> Task<Task<std::string>> {
auto coro2 = []() -> Task<std::string> {
auto coro3 = []() -> Task<std::string> {
co_return std::string("Hello, World!");
};
auto coro4 = [coro3 = std::move(coro3)]() -> Task<std::string> {
auto coro5 = []() -> Task<> { co_return; };
co_await coro5();
co_return co_await coro3();
};
co_return co_await coro4();
};
co_return coro2();
};
auto task1 = coro();
auto task2 = sync_wait(task1);
std::string str = sync_wait(task2);
CHECK(str == "Hello, World!");
}
DROGON_TEST(CoroutineDestruction)
{
// Test coroutine destruction
auto destruct = []() -> Task<> {
auto awaitStruct = []() -> Task<std::shared_ptr<internal::SomeStruct>> {
co_return co_await internal::StructAwaiter();
};
auto awaitNothing = [awaitStruct]() -> Task<> {
co_await awaitStruct();
};
co_await awaitNothing();
};
sync_wait(destruct());
CHECK(internal::SomeStruct::beenDestructed == true);
}
DROGON_TEST(AsyncWaitLifetime)
{
app().getLoop()->queueInLoop([TEST_CTX]() {
async_run([TEST_CTX]() -> Task<> {
auto ptr = std::make_shared<std::string>("test");
CHECK(ptr.use_count() == 1);
co_await sleepCoro(drogon::app().getLoop(), 0.01);
CHECK(ptr.use_count() == 1);
});
});
app().getLoop()->queueInLoop([TEST_CTX]() {
auto ptr = std::make_shared<std::string>("test");
async_run([ptr, TEST_CTX]() -> Task<> {
CHECK(ptr.use_count() == 2);
co_await sleepCoro(drogon::app().getLoop(), 0.01);
CHECK(ptr.use_count() == 1);
});
});
auto ptr = std::make_shared<std::string>("test");
app().getLoop()->queueInLoop([ptr, TEST_CTX]() {
async_run([ptr, TEST_CTX]() -> Task<> {
co_await sleepCoro(drogon::app().getLoop(), 0.01);
CHECK(ptr.use_count() == 1);
});
});
auto ptr2 = std::make_shared<std::string>("test");
app().getLoop()->queueInLoop(async_func([ptr2, TEST_CTX]() -> Task<> {
co_await sleepCoro(drogon::app().getLoop(), 0.01);
CHECK(ptr2.use_count() == 1);
}));
}
DROGON_TEST(SwitchThread)
{
trantor::EventLoopThread thread;
thread.getLoop()->setIndex(12345);
thread.run();
auto switch_thread = [TEST_CTX, &thread]() -> Task<> {
co_await switchThreadCoro(thread.getLoop());
auto currentLoop = trantor::EventLoop::getEventLoopOfCurrentThread();
MANDATE(currentLoop != nullptr);
CHECK(currentLoop->index() == 12345);
currentLoop->quit();
};
sync_wait(switch_thread());
thread.wait();
}
DROGON_TEST(Mutex)
{
trantor::EventLoopThreadPool pool{3};
pool.start();
Mutex mutex;
async_run([&]() -> Task<> {
co_await switchThreadCoro(pool.getLoop(0));
auto guard = co_await mutex.scoped_lock();
co_await sleepCoro(pool.getLoop(1), std::chrono::seconds(2));
co_return;
});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::promise<void> done;
async_run([&]() -> Task<> {
co_await switchThreadCoro(pool.getLoop(2));
auto id = std::this_thread::get_id();
co_await mutex.lock();
CHECK(id == std::this_thread::get_id());
mutex.unlock();
CHECK(id == std::this_thread::get_id());
done.set_value();
co_return;
});
done.get_future().wait();
for (int16_t i = 0; i < 3; i++)
pool.getLoop(i)->quit();
pool.wait();
}
DROGON_TEST(WhenAll)
{
using TestCtx = std::shared_ptr<drogon::test::Case>;
[](TestCtx TEST_CTX) -> AsyncTask {
size_t counter = 0;
auto t1 = [](TestCtx TEST_CTX, size_t *counter) -> Task<> {
co_await drogon::sleepCoro(app().getLoop(), 0.2);
(*counter)++;
}(TEST_CTX, &counter);
auto t2 = [](TestCtx TEST_CTX, size_t *counter) -> Task<> {
co_await drogon::sleepCoro(app().getLoop(), 0.1);
(*counter)++;
}(TEST_CTX, &counter);
std::vector<Task<void>> tasks;
tasks.emplace_back(std::move(t1));
tasks.emplace_back(std::move(t2));
co_await when_all(std::move(tasks));
CHECK(counter == 2);
}(TEST_CTX);
[](TestCtx TEST_CTX) -> AsyncTask {
std::vector<Task<void>> tasks;
co_await when_all(std::move(tasks));
SUCCESS();
}(TEST_CTX);
[](TestCtx TEST_CTX) -> AsyncTask {
auto t1 = [](TestCtx TEST_CTX) -> Task<int> { co_return 1; }(TEST_CTX);
auto t2 = [](TestCtx TEST_CTX) -> Task<int> { co_return 2; }(TEST_CTX);
std::vector<Task<int>> tasks;
tasks.emplace_back(std::move(t1));
tasks.emplace_back(std::move(t2));
auto res = co_await when_all(std::move(tasks));
CO_REQUIRE(res.size() == 2);
CHECK(res[0] == 1);
CHECK(res[1] == 2);
}(TEST_CTX);
[](TestCtx TEST_CTX) -> AsyncTask {
auto t1 = [](TestCtx TEST_CTX) -> Task<int> { co_return 1; }(TEST_CTX);
auto t2 = [](TestCtx TEST_CTX) -> Task<std::string> {
co_return "Hello";
}(TEST_CTX);
auto [num, str] = co_await when_all(std::move(t1), std::move(t2));
CHECK(num == 1);
CHECK(str == "Hello");
}(TEST_CTX);
[](TestCtx TEST_CTX) -> AsyncTask {
size_t counter = 0;
// Even on corutine throws, other coroutins run to completion
auto t1 = [](TestCtx TEST_CTX, size_t *counter) -> Task<int> {
co_await drogon::sleepCoro(app().getLoop(), 0.2);
(*counter)++;
co_return 1;
}(TEST_CTX, &counter);
auto t2 = [](TestCtx TEST_CTX) -> Task<std::string> {
co_await drogon::sleepCoro(app().getLoop(), 0.1);
throw std::runtime_error("Test exception");
}(TEST_CTX);
CO_REQUIRE_THROWS(co_await when_all(std::move(t1), std::move(t2)));
CHECK(counter == 1);
}(TEST_CTX);
[](TestCtx TEST_CTX) -> AsyncTask {
size_t counter = 0;
// void retuens gets mapped to std::false_type in the tuple API
auto t1 = [](TestCtx TEST_CTX, size_t *counter) -> Task<> {
(*counter)++;
co_return;
}(TEST_CTX, &counter);
auto [res] = co_await when_all(std::move(t1));
CHECK(counter == 1);
}(TEST_CTX);
}
@@ -0,0 +1,86 @@
#include <drogon/DrObject.h>
#include <drogon/drogon_test.h>
#include <drogon/HttpController.h>
using namespace drogon;
class TestA : public DrObject<TestA>
{
};
namespace test
{
class TestB : public DrObject<TestB>
{
};
} // namespace test
DROGON_TEST(DrObjectCreationTest)
{
using PtrType = std::shared_ptr<DrObjectBase>;
auto obj = PtrType(DrClassMap::newObject("TestA"));
CHECK(obj != nullptr);
auto objPtr = DrClassMap::getSingleInstance("TestA");
CHECK(objPtr.get() != nullptr);
auto objPtr2 = DrClassMap::getSingleInstance<TestA>();
CHECK(objPtr2.get() != nullptr);
CHECK(objPtr == objPtr2);
}
DROGON_TEST(DrObjectNamespaceTest)
{
using PtrType = std::shared_ptr<DrObjectBase>;
auto obj = PtrType(DrClassMap::newObject("test::TestB"));
CHECK(obj != nullptr);
auto objPtr = DrClassMap::getSingleInstance("test::TestB");
CHECK(objPtr.get() != nullptr);
auto objPtr2 = DrClassMap::getSingleInstance<::test::TestB>();
CHECK(objPtr2.get() != nullptr);
CHECK(objPtr == objPtr2);
}
class TestC : public DrObject<TestC>
{
public:
static constexpr bool isAutoCreation = true;
};
class TestD : public DrObject<TestD>
{
public:
static constexpr bool isAutoCreation = false;
};
class TestE : public DrObject<TestE>
{
public:
static constexpr double isAutoCreation = 3.0;
};
class CtrlA : public HttpController<CtrlA>
{
public:
METHOD_LIST_BEGIN
METHOD_LIST_END
};
class CtrlB : public HttpController<CtrlB, false>
{
public:
METHOD_LIST_BEGIN
METHOD_LIST_END
};
DROGON_TEST(IsAutoCreationClassTest)
{
STATIC_REQUIRE(isAutoCreationClass<TestA>::value == false);
STATIC_REQUIRE(isAutoCreationClass<TestC>::value == true);
STATIC_REQUIRE(isAutoCreationClass<TestD>::value == false);
STATIC_REQUIRE(isAutoCreationClass<TestE>::value == false);
STATIC_REQUIRE(isAutoCreationClass<CtrlA>::value == true);
STATIC_REQUIRE(isAutoCreationClass<CtrlB>::value == false);
}
@@ -0,0 +1,101 @@
#include "../lib/src/HttpUtils.h"
#include <drogon/drogon_test.h>
#include <string>
using namespace drogon;
DROGON_TEST(ExtensionTest)
{
SUBSECTION(normal)
{
std::string str{"drogon.jpg"};
CHECK(getFileExtension(str) == "jpg");
}
SUBSECTION(negative)
{
std::string str{"drogon."};
CHECK(getFileExtension(str) == "");
str = "drogon";
CHECK(getFileExtension(str) == "");
str = "";
CHECK(getFileExtension(str) == "");
str = "....";
CHECK(getFileExtension(str) == "");
}
}
DROGON_TEST(ContentTypeTest)
{
SUBSECTION(normal)
{
for (int i = CT_NONE + 1; i < CT_CUSTOM; i++)
{
auto contentType = ContentType(i);
const auto mimeType = contentTypeToMime(contentType);
CHECK(mimeType.empty() == false);
CHECK(parseContentType(mimeType) == contentType);
auto exts = getFileExtensions(contentType);
bool shouldBeEmpty = (contentType == CT_APPLICATION_X_FORM) ||
(contentType == CT_APPLICATION_OCTET_STREAM) ||
(contentType == CT_MULTIPART_FORM_DATA);
CHECK(exts.empty() == shouldBeEmpty);
for (const auto &ext : exts)
{
auto dummyFile{std::string("dummy.").append(ext)};
// Handle multiple mime types by setting the default ContentType
if (contentType == CT_APPLICATION_X_JAVASCRIPT)
contentType = CT_TEXT_JAVASCRIPT;
if (contentType == CT_TEXT_XML)
contentType = CT_APPLICATION_XML;
CHECK(getContentType(dummyFile) == contentType);
CHECK(parseFileType(dummyFile) != FT_UNKNOWN);
}
if (!shouldBeEmpty)
{
CHECK(getFileType(contentType) != FT_UNKNOWN);
CHECK(getFileType(contentType) != FT_CUSTOM);
}
}
}
SUBSECTION(negative)
{
CHECK(getFileType(CT_NONE) == FT_UNKNOWN);
CHECK(getFileType(CT_CUSTOM) == FT_CUSTOM);
CHECK(getFileType(CT_APPLICATION_X_FORM) == FT_UNKNOWN);
CHECK(getFileType(CT_APPLICATION_OCTET_STREAM) == FT_UNKNOWN);
CHECK(getFileType(CT_MULTIPART_FORM_DATA) == FT_UNKNOWN);
CHECK(contentTypeToMime(CT_NONE).empty());
CHECK(contentTypeToMime(CT_CUSTOM) ==
contentTypeToMime(CT_APPLICATION_OCTET_STREAM));
CHECK(parseContentType("") == CT_NONE);
CHECK(parseContentType("application/x-www-form-urlencoded") ==
CT_APPLICATION_X_FORM);
CHECK(parseContentType("multipart/form-data") ==
CT_MULTIPART_FORM_DATA);
CHECK(parseFileType("any.thing") == FT_CUSTOM);
CHECK(getContentType("any.thing") == CT_APPLICATION_OCTET_STREAM);
CHECK(getFileExtensions(CT_NONE).empty());
CHECK(getFileExtensions(CT_APPLICATION_X_FORM).empty());
CHECK(getFileExtensions(CT_APPLICATION_OCTET_STREAM).empty());
CHECK(getFileExtensions(CT_MULTIPART_FORM_DATA).empty());
CHECK(getFileExtensions(CT_CUSTOM).empty());
}
}
DROGON_TEST(FileTypeTest)
{
SUBSECTION(normal)
{
CHECK(parseFileType("jpg") == FT_IMAGE);
CHECK(parseFileType("mp4") == FT_MEDIA);
CHECK(parseFileType("csp") == FT_CUSTOM);
CHECK(parseFileType("html") == FT_DOCUMENT);
}
SUBSECTION(negative)
{
CHECK(parseFileType("") == FT_UNKNOWN);
CHECK(parseFileType("don'tknow") == FT_CUSTOM);
}
}
+319
View File
@@ -0,0 +1,319 @@
#include <drogon/drogon_test.h>
#include <drogon/utils/Utilities.h>
using namespace drogon;
DROGON_TEST(Gzip)
{
const std::string inStr =
"Applications\n"
"Developer\n"
"Library\n"
"Network\n"
"System\n"
"Users\n"
"Volumes\n"
"bin\n"
"cores\n"
"dev\n"
"etc\n"
"home\n"
"installer.failurerequests\n"
"net\n"
"opt\n"
"private\n"
"sbin\n"
"tmp\n"
"usb\n"
"usr\n"
"var\n"
"vm\n"
"\n"
"/Applications:\n"
"Adobe\n"
"Adobe Creative Cloud\n"
"Adobe Photoshop CC\n"
"AirPlayer Pro.app\n"
"Android Studio.app\n"
"App Store.app\n"
"Autodesk\n"
"Automator.app\n"
"Axure RP Pro 7.0.app\n"
"BaiduNetdisk_mac.app\n"
"CLion.app\n"
"Calculator.app\n"
"Calendar.app\n"
"Chess.app\n"
"CleanApp.app\n"
"Contacts.app\n"
"DVD Player.app\n"
"Dashboard.app\n"
"Dictionary.app\n"
"Docs for Xcode.app\n"
"FaceTime.app\n"
"FinalShell\n"
"Firefox.app\n"
"Folx.app\n"
"Font Book.app\n"
"GitHub.app\n"
"Google Chrome.app\n"
"Grammarly.app\n"
"Image Capture.app\n"
"Lantern.app\n"
"Launchpad.app\n"
"License.rtf\n"
"MacPorts\n"
"Mail.app\n"
"Maps.app\n"
"Messages.app\n"
"Microsoft Excel.app\n"
"Microsoft Office 2011\n"
"Microsoft OneNote.app\n"
"Microsoft Outlook.app\n"
"Microsoft PowerPoint.app\n"
"Microsoft Word.app\n"
"Mindjet MindManager.app\n"
"Mission Control.app\n"
"Mockplus.app\n"
"MyEclipse 2015\n"
"Notes.app\n"
"OmniGraffle.app\n"
"Pages.app\n"
"Photo Booth.app\n"
"Photos.app\n"
"Preview.app\n"
"QJVPN.app\n"
"QQ.app\n"
"QuickTime Player.app\n"
"RAR Extractor Lite.app\n"
"Reminders.app\n"
"Remote Desktop Connection.app\n"
"Renee Undeleter.app\n"
"Sabaki.app\n"
"Safari.app\n"
"ShadowsocksX.app\n"
"Siri.app\n"
"SogouInputPad.app\n"
"Stickies.app\n"
"System Preferences.app\n"
"TeX\n"
"Telegram.app\n"
"Termius.app\n"
"Tesumego - How to Make a Professional Go Player.app\n"
"TextEdit.app\n"
"Thunder.app\n"
"Time Machine.app\n"
"Tunnelblick.app\n"
"Utilities\n"
"VPN Shield.appdownload\n"
"VirtualBox.app\n"
"WeChat.app\n"
"WinOnX2.app\n"
"Wireshark.app\n"
"Xcode.app\n"
"Yose.app\n"
"YoudaoNote.localized\n"
"finalshelldata\n"
"iBooks.app\n"
"iPhoto.app\n"
"iTools.app\n"
"iTunes.app\n"
"pgAdmin 4.app\n"
"wechatwebdevtools.app\n"
"\n"
"/Applications/Adobe:\n"
"Flash Player\n"
"\n"
"/Applications/Adobe/Flash Player:\n"
"AddIns\n"
"\n"
"/Applications/Adobe/Flash Player/AddIns:\n"
"airappinstaller\n"
"\n"
"/Applications/Adobe/Flash Player/AddIns/airappinstaller:\n"
"airappinstaller\n"
"digest.s\n"
"\n"
"/Applications/Adobe Creative Cloud:\n"
"Adobe Creative Cloud\n"
"Icon\n"
"Uninstall Adobe Creative Cloud\n"
"\n"
"/Applications/Adobe Photoshop CC:\n"
"Adobe Photoshop CC.app\n"
"Configuration\n"
"Icon\n"
"Legal\n"
"LegalNotices.pdf\n"
"Locales\n"
"Plug-ins\n"
"Presets\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop CC.app:\n"
"Contents\n"
"Linguistics\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop CC.app/Contents:\n"
"Application Data\n"
"Frameworks\n"
"Info.plist\n"
"MacOS\n"
"PkgInfo\n"
"Required\n"
"Resources\n"
"_CodeSignature\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data:\n"
"Custom File Info Panels\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info Panels:\n"
"4.0\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info Panels/4.0:\n"
"bin\n"
"custom\n"
"panels\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info Panels/4.0/bin:\n"
"FileInfoFoundation.swf\n"
"FileInfoUI.swf\n"
"framework.swf\n"
"loc\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info "
"Panels/4.0/bin/loc:\n"
"FileInfo_ar_AE.dat\n"
"FileInfo_bg_BG.dat\n"
"FileInfo_cs_CZ.dat\n"
"FileInfo_da_DK.dat\n"
"FileInfo_de_DE.dat\n"
"FileInfo_el_GR.dat\n"
"FileInfo_en_US.dat\n"
"FileInfo_es_ES.dat\n"
"FileInfo_et_EE.dat\n"
"FileInfo_fi_FI.dat\n"
"FileInfo_fr_FR.dat\n"
"FileInfo_he_IL.dat\n"
"FileInfo_hr_HR.dat\n"
"FileInfo_hu_HU.dat\n"
"FileInfo_it_IT.dat\n"
"FileInfo_ja_JP.dat\n"
"FileInfo_ko_KR.dat\n"
"FileInfo_lt_LT.dat\n"
"FileInfo_lv_LV.dat\n"
"FileInfo_nb_NO.dat\n"
"FileInfo_nl_NL.dat\n"
"FileInfo_pl_PL.dat\n"
"FileInfo_pt_BR.dat\n"
"FileInfo_ro_RO.dat\n"
"FileInfo_ru_RU.dat\n"
"FileInfo_sk_SK.dat\n"
"FileInfo_sl_SI.dat\n"
"FileInfo_sv_SE.dat\n"
"FileInfo_tr_TR.dat\n"
"FileInfo_uk_UA.dat\n"
"FileInfo_zh_CN.dat\n"
"FileInfo_zh_TW.dat\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info "
"Panels/4.0/custom:\n"
"DICOM.xml\n"
"Mobile.xml\n"
"loc\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info "
"Panels/4.0/custom/loc:\n"
"DICOM_ar_AE.dat\n"
"DICOM_bg_BG.dat\n"
"DICOM_cs_CZ.dat\n"
"DICOM_da_DK.dat\n"
"DICOM_de_DE.dat\n"
"DICOM_el_GR.dat\n"
"DICOM_en_US.dat\n"
"DICOM_es_ES.dat\n"
"DICOM_et_EE.dat\n"
"DICOM_fi_FI.dat\n"
"DICOM_fr_FR.dat\n"
"DICOM_he_IL.dat\n"
"DICOM_hr_HR.dat\n"
"DICOM_hu_HU.dat\n"
"DICOM_it_IT.dat\n"
"DICOM_ja_JP.dat\n"
"DICOM_ko_KR.dat\n"
"DICOM_lt_LT.dat\n"
"DICOM_lv_LV.dat\n"
"DICOM_nb_NO.dat\n"
"DICOM_nl_NL.dat\n"
"DICOM_pl_PL.dat\n"
"DICOM_pt_BR.dat\n"
"DICOM_ro_RO.dat\n"
"DICOM_ru_RU.dat\n"
"DICOM_sk_SK.dat\n"
"DICOM_sl_SI.dat\n"
"DICOM_sv_SE.dat\n"
"DICOM_tr_TR.dat\n"
"DICOM_uk_UA.dat\n"
"DICOM_zh_CN.dat\n"
"DICOM_zh_TW.dat\n"
"Mobile_ar_AE.dat\n"
"Mobile_bg_BG.dat\n"
"Mobile_cs_CZ.dat\n"
"Mobile_da_DK.dat\n"
"Mobile_de_DE.dat\n"
"Mobile_el_GR.dat\n"
"Mobile_en_US.dat\n"
"Mobile_es_ES.dat\n"
"Mobile_et_EE.dat\n"
"Mobile_fi_FI.dat\n"
"Mobile_fr_FR.dat\n"
"Mobile_he_IL.dat\n"
"Mobile_hr_HR.dat\n"
"Mobile_hu_HU.dat\n"
"Mobile_it_IT.dat\n"
"Mobile_ja_JP.dat\n"
"Mobile_ko_KR.dat\n"
"Mobile_lt_LT.dat\n"
"Mobile_lv_LV.dat\n"
"Mobile_nb_NO.dat\n"
"Mobile_nl_NL.dat\n"
"Mobile_pl_PL.dat\n"
"Mobile_pt_BR.dat\n"
"Mobile_ro_RO.dat\n"
"Mobile_ru_RU.dat\n"
"Mobile_sk_SK.dat\n"
"Mobile_sl_SI.dat\n"
"Mobile_sv_SE.dat\n"
"Mobile_tr_TR.dat\n"
"Mobile_uk_UA.dat\n"
"Mobile_zh_CN.dat\n"
"Mobile_zh_TW.dat\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info "
"Panels/4.0/panels:\n"
"IPTC\n"
"IPTCExt\n"
"advanced\n"
"audioData\n"
"camera\n"
"categories\n"
"description\n"
"dicom\n"
"gpsData\n"
"history\n"
"mobile\n"
"origin\n"
"rawpacket";
auto ret = utils::gzipCompress(inStr.c_str(), inStr.length());
REQUIRE(ret.empty() == false);
auto decompressStr = utils::gzipDecompress(ret.data(), ret.length());
CHECK(inStr == decompressStr);
}
@@ -0,0 +1,30 @@
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
using namespace drogon;
DROGON_TEST(HttpDate)
{
// RFC 850
auto date = utils::getHttpDate("Fri, 05-Jun-20 09:19:38 GMT");
CHECK(date.microSecondsSinceEpoch() /
trantor::Date::MICRO_SECONDS_PER_SEC ==
1591348778);
// Reddit format
date = utils::getHttpDate("Fri, 05-Jun-2020 09:19:38 GMT");
CHECK(date.microSecondsSinceEpoch() /
trantor::Date::MICRO_SECONDS_PER_SEC ==
1591348778);
// Invalid
date = utils::getHttpDate("Fri, this format is invalid");
CHECK(date.microSecondsSinceEpoch() == std::numeric_limits<int64_t>::max());
// ASC Time
auto epoch = time(nullptr);
auto str = asctime(gmtime(&epoch));
date = utils::getHttpDate(str);
CHECK(date.microSecondsSinceEpoch() /
trantor::Date::MICRO_SECONDS_PER_SEC ==
epoch);
}
@@ -0,0 +1,82 @@
#include "../../lib/src/HttpFileImpl.h"
#include <drogon/drogon_test.h>
#include <filesystem>
using namespace drogon;
using namespace std;
DROGON_TEST(HttpFile)
{
SUBSECTION(SavePathUsingDefaultConfigPath)
{
HttpFileImpl file;
file.setFileName("test_file_name");
file.setFile("test", 4);
auto out = file.save();
CHECK(out == 0);
CHECK(filesystem::exists("./uploads/test_file_name"));
filesystem::remove_all("./uploads/test_file_name");
}
SUBSECTION(SavePathWithSpecificRelativePath)
{
HttpFileImpl file;
file.setFileName("test_file_name");
file.setFile("test", 4);
auto out = file.save("./test_uploads_dir");
CHECK(out == 0);
CHECK(filesystem::exists("./test_uploads_dir/test_file_name"));
filesystem::remove_all("./test_uploads_dir");
}
SUBSECTION(SavePathWithSpecificAbsolutePath)
{
auto uploadPath = filesystem::current_path() / "test_uploads_dir";
HttpFileImpl file;
file.setFileName("test_file_name");
file.setFile("test", 4);
auto out = file.save(uploadPath.string());
CHECK(out == 0);
CHECK(filesystem::exists(uploadPath / "test_file_name"));
filesystem::remove_all(uploadPath.string());
}
SUBSECTION(FileNameWithRelativePath)
{
auto uploadPath = filesystem::current_path() / "test_uploads_dir";
HttpFileImpl file;
file.setFileName("../test_malicious_file_name");
file.setFile("test", 4);
auto out = file.save(uploadPath.string());
CHECK(out == -1);
CHECK(!filesystem::exists(uploadPath / "../test_malicious_file_name"));
filesystem::remove_all(uploadPath);
filesystem::remove(uploadPath / "../test_malicious_file_name");
}
SUBSECTION(FileNameWithAbsolutePath)
{
auto fileName = filesystem::current_path() / "test_malicious_file_name";
HttpFileImpl file;
file.setFileName(fileName.string());
file.setFile("test", 4);
auto out = file.save("./test_uploads_dir");
CHECK(out == -1);
CHECK(!filesystem::exists(fileName.string()));
filesystem::remove_all("test_uploads_dir");
filesystem::remove(fileName.string());
}
}
@@ -0,0 +1,13 @@
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
#include <string>
#include <iostream>
using namespace drogon;
DROGON_TEST(HttpFullDateTest)
{
auto str = utils::getHttpFullDateStr();
auto date = utils::getHttpDate(str);
CHECK(utils::getHttpFullDate(date) == str);
}
@@ -0,0 +1,68 @@
#include <drogon/drogon_test.h>
#include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include "../../lib/src/HttpResponseImpl.h"
using namespace drogon;
DROGON_TEST(HttpHeaderRequest)
{
auto req = HttpRequest::newHttpRequest();
req->addHeader("Abc", "abc");
CHECK(req->getHeader("Abc") == "abc");
CHECK(req->getHeader("abc") == "abc");
req->removeHeader("Abc");
CHECK(req->getHeader("abc") == "");
}
DROGON_TEST(HttpHeaderResponse)
{
auto resp = std::dynamic_pointer_cast<HttpResponseImpl>(
HttpResponse::newHttpResponse());
REQUIRE(resp != nullptr);
resp->addHeader("Abc", "abc");
CHECK(resp->getHeader("Abc") == "abc");
CHECK(resp->getHeader("abc") == "abc");
resp->makeHeaderString();
auto buffer = resp->renderToBuffer();
auto str = std::string{buffer->peek(), buffer->readableBytes()};
CHECK(str.find("abc") != std::string::npos);
resp->removeHeader("Abc");
buffer = resp->renderToBuffer();
str = std::string{buffer->peek(), buffer->readableBytes()};
CHECK(str.find("abc") == std::string::npos);
CHECK(resp->getHeader("abc") == "");
}
DROGON_TEST(ResponseSetCustomContentTypeString)
{
auto resp = HttpResponse::newHttpResponse();
resp->setContentTypeString("text/html");
CHECK(resp->getContentType() == CT_TEXT_HTML);
resp = HttpResponse::newHttpResponse();
resp->setContentTypeString("image/bmp");
CHECK(resp->getContentType() == CT_IMAGE_BMP);
resp = HttpResponse::newHttpResponse();
resp->setContentTypeString("thisdoesnotexist/unknown");
CHECK(resp->getContentType() == CT_CUSTOM);
}
DROGON_TEST(ResquestSetCustomContentTypeString)
{
auto req = HttpRequest::newHttpRequest();
req->setContentTypeString("text/html");
CHECK(req->getContentType() == CT_TEXT_HTML);
req = HttpRequest::newHttpRequest();
req->setContentTypeString("image/bmp");
CHECK(req->getContentType() == CT_IMAGE_BMP);
req = HttpRequest::newHttpRequest();
req->setContentTypeString("thisdoesnotexist/unknown");
CHECK(req->getContentType() == CT_CUSTOM);
}
@@ -0,0 +1,41 @@
#include <drogon/HttpViewData.h>
#include <drogon/drogon_test.h>
#include <iostream>
using namespace drogon;
DROGON_TEST(HttpViewData)
{
HttpViewData data;
data.insert("1", 1);
data.insertAsString("2", 2.0);
data.insertFormattedString("3", "third value is %d", 3);
data.insertAsString("4", "4");
data.insert("5", 5);
data.insert("5", std::string("5!!!!!!!")); // Overrides the old value
char six = 6;
data.insert("6", six);
CHECK(data.get<int>("1") == 1);
CHECK(data.get<std::string>("2") == "2");
CHECK(data.get<std::string>("3") == "third value is 3");
CHECK(data.get<std::string>("4") == "4");
CHECK(data.get<std::string>("5") == "5!!!!!!!");
CHECK(data.get<char>("6") == 6);
CHECK(data.get<int>("1") == 1); // get a second time
// Bad key returns a default constructed value
CHECK_NOTHROW(data.get<int>("this_does_not_exist"));
SUBSECTION(Translate)
{
CHECK(HttpViewData::needTranslation("") == false);
CHECK(HttpViewData::needTranslation("!)(*#") == false);
CHECK(HttpViewData::needTranslation("#include <iostream>") == true);
CHECK(HttpViewData::needTranslation("<body></body>") == true);
CHECK(HttpViewData::htmlTranslate("#include <iostream>") ==
"#include &lt;iostream&gt;");
CHECK(HttpViewData::htmlTranslate("&gt;") == "&amp;gt;");
}
}
+16
View File
@@ -0,0 +1,16 @@
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
#include <string>
DROGON_TEST(Md5Test)
{
CHECK(drogon::utils::getMd5("123456789012345678901234567890123456789012345"
"678901234567890123456789012345678901234567890"
"1234567890") ==
"49CB3608E2B33FAD6B65DF8CB8F49668");
CHECK(drogon::utils::getMd5("1") == "C4CA4238A0B923820DCC509A6F75849B");
CHECK(drogon::utils::getMd5("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") ==
"59F761506DFA597B0FAF1968F7CCA867");
}
@@ -0,0 +1,42 @@
#include <drogon/HttpAppFramework.h>
#include <drogon/drogon_test.h>
#include <string>
#include <iostream>
using namespace drogon;
struct TestCookie
{
TestCookie(std::shared_ptr<test::CaseBase> ctx) : TEST_CTX(ctx)
{
}
~TestCookie()
{
if (!taken)
FAIL("Test cookie not taken");
else
SUCCESS();
}
void take()
{
taken = true;
}
protected:
bool taken = false;
std::shared_ptr<test::CaseBase> TEST_CTX;
};
DROGON_TEST(MainLoopTest)
{
auto cookie = std::make_shared<TestCookie>(TEST_CTX);
drogon::app().getLoop()->queueInLoop([cookie]() { cookie->take(); });
std::thread t([TEST_CTX]() {
auto cookie2 = std::make_shared<TestCookie>(TEST_CTX);
drogon::app().getLoop()->queueInLoop([cookie2]() { cookie2->take(); });
});
t.join();
}
@@ -0,0 +1,66 @@
#include <trantor/utils/MsgBuffer.h>
#include <drogon/drogon_test.h>
#include <string>
#include <iostream>
using namespace trantor;
DROGON_TEST(MsgBufferTest)
{
SUBSECTION(readableTest)
{
MsgBuffer buffer;
CHECK(buffer.readableBytes() == 0UL);
buffer.append(std::string(128, 'a'));
CHECK(buffer.readableBytes() == 128UL);
buffer.retrieve(100);
CHECK(buffer.readableBytes() == 28UL);
CHECK(buffer.peekInt8() == 'a');
buffer.retrieveAll();
CHECK(buffer.readableBytes() == 0UL);
}
SUBSECTION(writableTest)
{
MsgBuffer buffer(100);
CHECK(buffer.writableBytes() == 100UL);
buffer.append("abcde");
CHECK(buffer.writableBytes() == 95UL);
buffer.append(std::string(100, 'x'));
CHECK(buffer.writableBytes() == 111UL);
buffer.retrieve(100);
CHECK(buffer.writableBytes() == 111UL);
buffer.append(std::string(112, 'c'));
CHECK(buffer.writableBytes() == 99UL);
buffer.retrieveAll();
CHECK(buffer.writableBytes() == 216UL);
}
SUBSECTION(addInFrontTest)
{
MsgBuffer buffer(100);
CHECK(buffer.writableBytes() == 100UL);
buffer.addInFrontInt8('a');
CHECK(buffer.writableBytes() == 100UL);
buffer.addInFrontInt64(123);
CHECK(buffer.writableBytes() == 92UL);
buffer.addInFrontInt64(100);
CHECK(buffer.writableBytes() == 84UL);
buffer.addInFrontInt8(1);
CHECK(buffer.writableBytes() == 84UL);
}
SUBSECTION(MoveAssignmentOperator)
{
MsgBuffer buf(100);
const char *bufptr = buf.peek();
size_t writable = buf.writableBytes();
MsgBuffer buffnew(1000);
buffnew = std::move(buf);
CHECK(bufptr == buffnew.peek());
CHECK(writable == buffnew.writableBytes());
}
}
@@ -0,0 +1,132 @@
#include <drogon/MultiPart.h>
#include <drogon/drogon_test.h>
#include <drogon/HttpRequest.h>
#include "../../lib/src/MultipartStreamParser.h"
DROGON_TEST(MultiPartParser)
{
drogon::MultiPartParser parser1;
auto req = drogon::HttpRequest::newHttpRequest();
req->setMethod(drogon::Post);
req->addHeader("content-type", "multipart/form-data; boundary=\"12345\"");
req->setBody(
"--12345\r\n"
"Content-Disposition: form-data; name=\"somekey\"\r\n"
"\r\n"
"Hello; World\r\n"
"--12345--");
CHECK(0 == parser1.parse(req));
CHECK(parser1.getParameters().size() == 1);
CHECK(parser1.getParameters().at("somekey") == "Hello; World");
req->setBody(
"--12345\r\n"
"Content-Disposition: form-data; name=\"somekey\"; "
"filename=\"test\"\r\n"
"\r\n"
"Hello; World\r\n"
"--12345--");
drogon::MultiPartParser parser2;
CHECK(0 == parser2.parse(req));
auto filesMap = parser2.getFilesMap();
CHECK(filesMap.size() == 1);
CHECK(filesMap.at("somekey").getFileName() == "test");
CHECK(filesMap.at("somekey").fileContent() == "Hello; World");
req->setBody(
"--12345\r\n"
"Content-Disposition: form-data; name=\"name of pdf\"; "
"filename=\"pdf-file.pdf\"\r\n"
"Content-Type: application/octet-stream\r\n"
"content-transfer-encoding: quoted-printable\r\n"
"\r\n"
"bytes of pdf file\r\n"
"--12345--");
drogon::MultiPartParser parser3;
CHECK(0 == parser3.parse(req));
filesMap = parser3.getFilesMap();
CHECK(filesMap.size() == 1);
CHECK(filesMap.at("name of pdf").getFileName() == "pdf-file.pdf");
CHECK(filesMap.at("name of pdf").fileContent() == "bytes of pdf file");
CHECK(filesMap.at("name of pdf").getContentType() ==
drogon::CT_APPLICATION_OCTET_STREAM);
CHECK(filesMap.at("name of pdf").getContentTransferEncoding() ==
"quoted-printable");
req->setBody(
"--12345\r\n"
"Content-Disposition: form-data; name=\"some;key\"\r\n"
"\r\n"
"Hello; World\r\n"
"--12345--");
drogon::MultiPartParser parser4;
CHECK(0 == parser4.parse(req));
CHECK(parser4.getParameters().size() == 1);
CHECK(parser4.getParameters().at("some;key") == "Hello; World");
}
DROGON_TEST(MultiPartStreamParser)
{
static const std::string ct = "multipart/form-data; boundary=\"12345\"";
static const std::string_view data =
"--12345\r\n"
"Content-Disposition: form-data; name=\"key1\"; filename=\"file1\"\r\n"
"\r\n"
"Hello; World\r\n"
"--12345\r\n"
"Content-Disposition: form-data; name=\"key2\"\r\n"
"\r\n"
"value2\r\n"
"--12345--";
struct Entry
{
drogon::MultipartHeader header;
std::string value;
std::string fileContent;
};
auto check = [TEST_CTX](size_t step) {
drogon::MultipartStreamParser parser(ct);
auto entries = std::make_shared<std::vector<Entry>>();
auto headerCb = [TEST_CTX, entries](drogon::MultipartHeader hdr) {
entries->emplace_back(Entry{std::move(hdr)});
};
auto dataCb = [TEST_CTX, entries](const char *data, size_t length) {
MANDATE(!entries->empty());
if (length == 0)
{
// Field finished
return;
}
if (entries->back().header.filename.empty())
{
entries->back().value.append(data, length);
}
else
{
entries->back().fileContent.append(data, length);
}
};
size_t i = 0;
while (i < data.length() && parser.isValid())
{
size_t end = i + step < data.length() ? i + step : data.length();
parser.parse(data.data() + i, end - i, headerCb, dataCb);
CHECK(parser.isValid());
i = end;
}
MANDATE(i == data.length());
MANDATE(parser.isFinished());
MANDATE(entries->size() == 2);
CHECK(entries->at(0).header.name == "key1");
CHECK(entries->at(0).fileContent == "Hello; World");
CHECK(entries->at(1).header.name == "key2");
CHECK(entries->at(1).value == "value2");
};
check(1);
check(3);
check(7);
check(20);
}
@@ -0,0 +1,77 @@
#include <drogon/utils/OStringStream.h>
#include <drogon/drogon_test.h>
#include <string>
#include <string_view>
#include <iostream>
DROGON_TEST(OStringStreamTest)
{
SUBSECTION(CanConvertToString)
{
using drogon::internal::CanConvertToString;
static_assert(CanConvertToString<int>::value);
static_assert(CanConvertToString<long>::value);
static_assert(CanConvertToString<long long>::value);
static_assert(CanConvertToString<unsigned>::value);
static_assert(CanConvertToString<unsigned long>::value);
static_assert(CanConvertToString<unsigned long long>::value);
static_assert(CanConvertToString<float>::value);
static_assert(CanConvertToString<double>::value);
static_assert(CanConvertToString<long double>::value);
static_assert(!CanConvertToString<std::string>::value);
static_assert(!CanConvertToString<std::string_view>::value);
}
SUBSECTION(integer)
{
drogon::OStringStream ss;
ss << 12;
ss << 345L;
CHECK(ss.str() == "12345");
}
SUBSECTION(float_number)
{
drogon::OStringStream ss;
ss << 3.14f;
ss << 3.1416;
CHECK(ss.str() == "3.143.1416");
}
SUBSECTION(literal_string)
{
drogon::OStringStream ss;
ss << "hello";
ss << " world";
CHECK(ss.str() == "hello world");
}
SUBSECTION(std::string_view)
{
drogon::OStringStream ss;
ss << std::string_view("hello");
ss << std::string_view(" world");
CHECK(ss.str() == "hello world");
}
SUBSECTION(std_string)
{
drogon::OStringStream ss;
ss << std::string("hello");
ss << std::string(" world");
CHECK(ss.str() == "hello world");
}
SUBSECTION(mix)
{
drogon::OStringStream ss;
ss << std::string("hello");
ss << std::string_view(" world");
ss << "!";
ss << 123;
ss << 3.14f;
CHECK(ss.str() == "hello world!1233.14");
}
}
@@ -0,0 +1,18 @@
#include <drogon/PubSubService.h>
#include <drogon/drogon_test.h>
DROGON_TEST(PubSubServiceTest)
{
drogon::PubSubService<std::string> service;
auto id = service.subscribe("topic1",
[TEST_CTX](const std::string &topic,
const std::string &message) {
CHECK(topic == "topic1");
CHECK(message == "hello world");
});
service.publish("topic1", "hello world");
service.publish("topic2", "hello world");
CHECK(service.size() == 1UL);
service.unsubscribe("topic1", id);
CHECK(service.size() == 0UL);
}
+12
View File
@@ -0,0 +1,12 @@
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
#include <string>
DROGON_TEST(SHA1Test)
{
char in[] =
"1234567890123456789012345678901234567890123456789012345"
"678901234567890123456789012345678901234567890";
auto str = drogon::utils::getSha1(in, strlen((const char *)in));
CHECK(str == "FECFD28BBC9345891A66D7C1B8FF46E60192D284");
}
@@ -0,0 +1,179 @@
#include <drogon/drogon_test.h>
#include <../../lib/src/SlashRemover.cc>
#include <string>
using std::string;
DROGON_TEST(SlashRemoverTest)
{
string cleanUrl;
{ // Regular URL
const string urlNoTrail = "///home//page", urlNoDup = "/home/page/",
urlNoExcess = "/home/page";
SUBSECTION(Full)
{
const string url = "///home//page//";
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
CHECK(cleanUrl == urlNoTrail);
cleanUrl = url;
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
CHECK(cleanUrl == urlNoDup);
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
}
SUBSECTION(Partial)
{
removeExcessiveSlashes(cleanUrl,
findExcessiveSlashes(urlNoTrail),
urlNoTrail);
CHECK(cleanUrl == urlNoExcess);
removeExcessiveSlashes(cleanUrl,
findExcessiveSlashes(urlNoDup),
urlNoDup);
CHECK(cleanUrl == urlNoExcess);
}
}
SUBSECTION(Root)
{
const string urlNoExcess = "/";
{ // Overlapping indices
const string url = "//";
cleanUrl = url;
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
cleanUrl = url;
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
CHECK(cleanUrl == urlNoExcess);
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
}
{ // Intersecting indices
const string url = "///";
cleanUrl = url;
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
cleanUrl = url;
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
CHECK(cleanUrl == urlNoExcess);
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
}
}
SUBSECTION(Overlap)
{
const string urlNoDup = "/a/", urlNoExcess = "/a";
{ // Overlapping indices
const string url = "/a//";
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
cleanUrl = url;
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
CHECK(cleanUrl == urlNoDup);
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
}
{ // Intersecting indices
const string url = "/a///";
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
cleanUrl = url;
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
CHECK(cleanUrl == urlNoDup);
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
}
}
SUBSECTION(NoTrail)
{
const string url = "//a", urlNoExcess = "/a";
cleanUrl.clear();
{
auto find = findTrailingSlashes(url);
if (find != string::npos)
removeTrailingSlashes(cleanUrl, find, url);
}
CHECK(cleanUrl.empty());
cleanUrl = url;
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
CHECK(cleanUrl == urlNoExcess);
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
}
SUBSECTION(NoDuplicate)
{
const string url = "/a/", urlNoExcess = "/a";
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
cleanUrl = url;
{
auto find = findDuplicateSlashes(cleanUrl);
if (find != string::npos)
removeDuplicateSlashes(cleanUrl, find);
}
CHECK(cleanUrl == url);
cleanUrl = url;
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
}
SUBSECTION(None)
{
const string url = "/a";
cleanUrl.clear();
{
auto find = findTrailingSlashes(url);
if (find != string::npos)
removeTrailingSlashes(cleanUrl, find, url);
}
CHECK(cleanUrl.empty());
cleanUrl = url;
{
auto find = findDuplicateSlashes(cleanUrl);
if (find != string::npos)
removeDuplicateSlashes(cleanUrl, find);
}
CHECK(cleanUrl == url);
cleanUrl.clear();
{
auto find = findExcessiveSlashes(url);
if (find.first != string::npos || find.second != string::npos)
removeExcessiveSlashes(cleanUrl, find, url);
}
CHECK(cleanUrl.empty());
}
}
@@ -0,0 +1,92 @@
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
#include <string>
struct SameContent
{
SameContent(const std::vector<std::string> &container)
: container_(container.begin(), container.end())
{
}
std::vector<std::string> container_;
};
template <typename Container1>
inline bool operator==(const Container1 &a, const SameContent &wrapper)
{
const auto &b = wrapper.container_;
if (a.size() != b.size())
return false;
auto ait = a.begin();
auto bit = b.begin();
while (ait != a.end() && bit != b.end())
{
if (*ait != *bit)
break;
ait++;
bit++;
}
return ait == a.end() && bit == b.end();
}
using namespace drogon;
DROGON_TEST(StringOpsTest)
{
SUBSECTION(SplitString)
{
std::string str = "1,2,3,3,,4";
CHECK(utils::splitString(str, ",") ==
SameContent({"1", "2", "3", "3", "4"}));
CHECK(utils::splitString(str, ",", true) ==
SameContent({"1", "2", "3", "3", "", "4"}));
CHECK(utils::splitString(str, "|", true) ==
SameContent({"1,2,3,3,,4"}));
str = "a||b||c||||";
CHECK(utils::splitString(str, "||") == SameContent({"a", "b", "c"}));
CHECK(utils::splitString(str, "||", true) ==
SameContent({"a", "b", "c", "", ""}));
str = "aabbbaabbbb";
CHECK(utils::splitString(str, "bb") == SameContent({"aa", "baa"}));
CHECK(utils::splitString(str, "bb", true) ==
SameContent({"aa", "baa", "", ""}));
str = "";
CHECK(utils::splitString(str, ",") == SameContent({}));
CHECK(utils::splitString(str, ",", true) == SameContent({""}));
}
SUBSECTION(SplitStringToSet)
{
// splitStringToSet ignores empty strings
std::string str = "1,2,3,3,,4";
auto s = utils::splitStringToSet(str, ",");
CHECK(s.size() == 4UL);
CHECK(s.count("1") == 1UL);
CHECK(s.count("2") == 1UL);
CHECK(s.count("3") == 1UL);
CHECK(s.count("4") == 1UL);
str = "a|||a";
s = utils::splitStringToSet(str, "||");
CHECK(s.size() == 2UL);
CHECK(s.count("a") == 1UL);
CHECK(s.count("|a") == 1UL);
}
SUBSECTION(ReplaceAll)
{
std::string str = "3.14159";
utils::replaceAll(str, "1", "a");
CHECK(str == "3.a4a59");
str = "aaxxxaaxxxxaaxxxxx";
utils::replaceAll(str, "xx", "oo");
CHECK(str == "aaooxaaooooaaoooox");
}
}
@@ -0,0 +1,14 @@
#include <string_view>
#include <drogon/utils/Utilities.h>
#include <iostream>
#include <drogon/drogon_test.h>
DROGON_TEST(URLCodec)
{
std::string input = "k1=1&k2=安";
auto encoded = drogon::utils::urlEncode(input);
auto decoded = drogon::utils::urlDecode(encoded);
CHECK(encoded == "k1=1&k2=%E5%AE%89");
CHECK(input == decoded);
}
@@ -0,0 +1,101 @@
#include <string>
#include <string_view>
#include <istream>
#include <array>
#include <vector>
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
struct ConvertibleFromStringStream
{
friend std::istream &operator>>(std::istream &os,
ConvertibleFromStringStream &)
{
return os;
}
};
struct NotConvertibleFromStringStream
{
};
DROGON_TEST(CanConvertFromStringStream)
{
using drogon::internal::CanConvertFromStringStream;
static_assert(CanConvertFromStringStream<unsigned short>::value);
static_assert(CanConvertFromStringStream<unsigned int>::value);
static_assert(CanConvertFromStringStream<long>::value);
static_assert(CanConvertFromStringStream<unsigned long>::value);
static_assert(CanConvertFromStringStream<long long>::value);
static_assert(CanConvertFromStringStream<unsigned long long>::value);
static_assert(CanConvertFromStringStream<float>::value);
static_assert(CanConvertFromStringStream<double>::value);
static_assert(CanConvertFromStringStream<long double>::value);
static_assert(CanConvertFromStringStream<bool>::value);
static_assert(CanConvertFromStringStream<void *>::value);
static_assert(CanConvertFromStringStream<short>::value);
static_assert(CanConvertFromStringStream<int>::value);
static_assert(
CanConvertFromStringStream<ConvertibleFromStringStream>::value);
static_assert(
!CanConvertFromStringStream<NotConvertibleFromStringStream>::value);
}
struct ConstructibleFromString
{
ConstructibleFromString(const std::string &)
{
}
};
struct NotConstructibleFromString
{
};
DROGON_TEST(CanConstructFromString)
{
using drogon::internal::CanConstructFromString;
static_assert(CanConstructFromString<ConstructibleFromString>::value);
static_assert(!CanConstructFromString<NotConstructibleFromString>::value);
static_assert(!CanConstructFromString<int>::value);
static_assert(!CanConstructFromString<double>::value);
static_assert(CanConstructFromString<std::string>::value);
static_assert(CanConstructFromString<std::string_view>::value);
static_assert(CanConstructFromString<const std::string>::value);
static_assert(!CanConstructFromString<std::string &>::value);
static_assert(CanConstructFromString<const std::string &>::value);
static_assert(!CanConstructFromString<std::string *>::value);
}
struct ConvertibleFromString
{
ConvertibleFromString &operator=(const std::string &)
{
return *this;
}
};
struct NotConvertibleFromString
{
};
DROGON_TEST(CanConvertFromString)
{
using drogon::internal::CanConvertFromString;
static_assert(CanConvertFromString<ConvertibleFromString>::value);
static_assert(!CanConvertFromString<NotConvertibleFromString>::value);
static_assert(CanConvertFromString<std::string>::value);
static_assert(!CanConvertFromString<const std::string>::value);
static_assert(!CanConvertFromString<const std::string &>::value);
static_assert(!CanConvertFromString<std::string *>::value);
static_assert(CanConvertFromString<std::string_view>::value);
static_assert(!CanConvertFromString<const char *>::value);
static_assert(!CanConvertFromString<std::vector<char>>::value);
static_assert(!CanConvertFromString<std::array<char, 5>>::value);
static_assert(!CanConvertFromString<int>::value);
static_assert(!CanConvertFromString<double>::value);
}
@@ -0,0 +1,17 @@
#include <string_view>
#include <drogon/utils/Utilities.h>
#include <iostream>
#include <drogon/drogon_test.h>
DROGON_TEST(UuidTest)
{
auto uuid = drogon::utils::getUuid();
std::cout << "uuid: " << uuid << std::endl;
CHECK(uuid[8] == '-');
CHECK(uuid[13] == '-');
CHECK(uuid[18] == '-');
CHECK(uuid[23] == '-');
CHECK(uuid.size() == 36);
}
@@ -0,0 +1,40 @@
#include <drogon/drogon_test.h>
#include "../../lib/src/HttpRequestImpl.h"
#include "../../lib/src/HttpResponseImpl.h"
#include "../../lib/src/HttpControllerBinder.h"
using namespace drogon;
DROGON_TEST(WebsocketReponseTest)
{
WebsocketControllerBinder binder;
auto reqPtr = std::make_shared<HttpRequestImpl>(nullptr);
// Value from rfc6455-1.3
reqPtr->addHeader("sec-websocket-key", "dGhlIHNhbXBsZSBub25jZQ==");
binder.handleRequest(reqPtr, [&](const HttpResponsePtr &resp) {
CHECK(resp->statusCode() == k101SwitchingProtocols);
CHECK(resp->headers().size() == 3);
CHECK(resp->getHeader("Upgrade") == "websocket");
CHECK(resp->getHeader("Connection") == "Upgrade");
// Value from rfc6455-1.3
CHECK(resp->getHeader("Sec-WebSocket-Accept") ==
"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
auto implPtr = std::dynamic_pointer_cast<HttpResponseImpl>(resp);
implPtr->makeHeaderString();
auto buffer = implPtr->renderToBuffer();
auto str = std::string{buffer->peek(), buffer->readableBytes()};
CHECK(str.find("upgrade: websocket") != std::string::npos);
CHECK(str.find("connection: Upgrade") != std::string::npos);
CHECK(str.find("sec-websocket-accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") !=
std::string::npos);
CHECK(str.find("content-length:") == std::string::npos);
});
}
+52
View File
@@ -0,0 +1,52 @@
#define DROGON_TEST_MAIN
#include <drogon/drogon_test.h>
#include <drogon/HttpAppFramework.h>
using namespace drogon;
using namespace trantor;
DROGON_TEST(TestFrameworkSelfTest)
{
CHECK(TEST_CTX->name() == "TestFrameworkSelfTest");
CHECK(true);
CHECK(false != true);
CHECK(1 * 2 == 1 + 1);
CHECK(42 < 100);
CHECK(0xff <= 255);
CHECK('a' >= 'a');
CHECK(3.14159 > 2.71828);
CHECK(nullptr == nullptr);
CHECK_THROWS(throw std::runtime_error("test exception"));
CHECK_THROWS_AS(throw std::domain_error("test exception"),
std::domain_error);
CHECK_NOTHROW([] { return 0; }());
STATIC_REQUIRE(std::is_standard_layout<int>::value);
STATIC_REQUIRE(std::is_default_constructible<test::Case>::value == false);
auto child_test = std::make_shared<test::Case>(TEST_CTX, "ChildTest");
CHECK(child_test->fullname() == "TestFrameworkSelfTest.ChildTest");
// Unlike Catch2, a subsection in drogon test does not provide a fixture
// It's only a way to signify testing different for things
SUBSECTION(Subsection)
{
CHECK(TEST_CTX->fullname() == "TestFrameworkSelfTest.Subsection");
}
}
int main(int argc, char **argv)
{
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;
}