Implement MarkdownRouteHandler text HTML template text substitution
Close #11 - Add CMAke target that moves program data to the build folder Close #17 - Implement MarkdownRouteHandler text HTML template text substitution
This commit is contained in:
parent
3d0b08a612
commit
39eaee382c
@ -205,6 +205,7 @@ endif()
|
||||
|
||||
add_custom_target(copy_assets
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/data ${CMAKE_BINARY_DIR}/artifacts/share/data
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/data ${CMAKE_BINARY_DIR}/tests/data
|
||||
)
|
||||
|
||||
add_subdirectory(src)
|
||||
@ -218,6 +219,7 @@ if (ENABLE_TESTS)
|
||||
)
|
||||
add_dependencies(ctest
|
||||
mdml-tests
|
||||
copy_assets
|
||||
)
|
||||
|
||||
endif()
|
||||
|
@ -1,100 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Your Page Title</title>
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
||||
|
||||
<!-- My CSS --->
|
||||
<link rel="stylesheet" href="fonts/Berkeley-Mono-Font-face.css">
|
||||
<link rel="stylesheet" href="fonts/IBM-Plex-Sans.css">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<nav id="navbar"
|
||||
class="collapse navbar-collapse col-md-2 d-md-block bg-light sidebar">
|
||||
<!-- Left-column navbar -->
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link active" href="#home">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#about-me">About Me</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#projects">Projects</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#writing">Writing</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#site-cpp">This site runs C++</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- Page content with Lorem Ipsum text -->
|
||||
<main class="col-md-10 col-lg-10 px-4">
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Your Page Title</h1>
|
||||
<span class="navbar-light">
|
||||
<button id="menuButton" class="navbar-toggler" type="button"
|
||||
data-toggle="collapse" data-target="#navbar"
|
||||
aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Page body with Lorem Ipsum text -->
|
||||
<div>
|
||||
<h2>Page Content</h2>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Sed auctor non ante id facilisis. Suspendisse feugiat eros
|
||||
sit amet dui gravida, ac suscipit justo dignissim. Nullam
|
||||
hendrerit metus vitae felis cursus, a finibus neque
|
||||
consequat. Vestibulum id ullamcorper sapien. Phasellus vel
|
||||
bibendum metus. Donec eu enim erat.<br/>
|
||||
<span style="font-weight: bold;">0 1 2 3 4 5 6 7 8 9 10</span>
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS (including jQuery and Popper.js) -->
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.12.9/dist/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// JavaScript to handle the "Menu" button and show/hide the navbar
|
||||
const navbar = document.getElementById('navbar');
|
||||
const menuButton = document.getElementById('menuButton');
|
||||
|
||||
function onResize() {
|
||||
if (window.innerWidth < 768) {
|
||||
navbar.classList.remove('shown')
|
||||
menuButton.classList.remove('d-none');
|
||||
} else {
|
||||
navbar.classList.add('shown');
|
||||
menuButton.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
|
||||
function onMenuClick(){}
|
||||
|
||||
// Call the function when the page loads and on window resize
|
||||
window.addEventListener('resize', onResize);
|
||||
window.addEventListener('load', onResize);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!-- vim: set ts=2 sw=2 noet: -->
|
5
data/test.md
Normal file
5
data/test.md
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
## Subtitle
|
||||
|
||||
Hello world!
|
||||
|
10
data/test.thtml
Normal file
10
data/test.thtml
Normal file
@ -0,0 +1,10 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>%title%</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="title">%title%</div>
|
||||
<div class="content">%content%</div>
|
||||
</body>
|
||||
</html>
|
@ -23,6 +23,7 @@ class Application {
|
||||
Application(const Application&) = delete;
|
||||
Application& operator=(const Application&) = delete;
|
||||
|
||||
inline static Application& GetInstance() { return *instance_ptr; };
|
||||
virtual ~Application();
|
||||
|
||||
// #region Getters
|
||||
@ -42,6 +43,7 @@ class Application {
|
||||
|
||||
private:
|
||||
static count_t instance_count;
|
||||
static Application* instance_ptr;
|
||||
|
||||
protected:
|
||||
void parse_arguments(int argc, c::const_string argv[]);
|
||||
|
58
include/MarkdownRouteHandler.hpp
Normal file
58
include/MarkdownRouteHandler.hpp
Normal file
@ -0,0 +1,58 @@
|
||||
/* MarkdownRouteHandler.hpp
|
||||
* Copyright © 2023 Saul D. Beniquez
|
||||
* License: Mozilla Public License v. 2.0
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||
* v.2.0. If a copy of the MPL was not distributed with this file, You can
|
||||
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "IRouteHandler.hpp"
|
||||
#include "types.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace mdml {
|
||||
|
||||
namespace {
|
||||
namespace fs = std::filesystem;
|
||||
}
|
||||
|
||||
class MarkdownRouteHandler : public IRouteHandler {
|
||||
public:
|
||||
MarkdownRouteHandler();
|
||||
virtual ~MarkdownRouteHandler();
|
||||
|
||||
void LoadTemplate(const std::string& template_name);
|
||||
void LoadMarkdown(const std::string& markdown_page_name);
|
||||
|
||||
virtual Result<std::string> Process(
|
||||
const std::string& name, const std::string& request_uri
|
||||
);
|
||||
|
||||
std::reference_wrapper<std::ostream> OutputStream;
|
||||
|
||||
#ifdef TESTING
|
||||
inline std::string& GetHtmlData() { return html_data; }
|
||||
inline std::string& GetMarkdownData() { return markdown_data; }
|
||||
#endif
|
||||
protected:
|
||||
std::string render_document(
|
||||
const std::string& title, const std::string& request_uri
|
||||
);
|
||||
|
||||
static void load_document(
|
||||
const fs::path& document_path, std::string& out_document
|
||||
);
|
||||
|
||||
fs::path work_dir;
|
||||
std::string html_data;
|
||||
std::string markdown_data;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
// vim: set foldmethod=marker foldmarker=#region,#endregion textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen :
|
@ -24,7 +24,8 @@ namespace mdml {
|
||||
class exception : public std::exception {
|
||||
|
||||
public:
|
||||
exception(const char* error_message = default_error);
|
||||
exception(c::const_string _message = default_error);
|
||||
exception(const std::string& message);
|
||||
exception(const std::exception& inner);
|
||||
|
||||
exception& operator=(const exception&) = delete;
|
||||
|
@ -18,14 +18,21 @@
|
||||
using namespace mdml;
|
||||
|
||||
count_t Application::instance_count = 0;
|
||||
Application* Application::instance_ptr = nullptr;
|
||||
|
||||
Application::Application(int argc, c::const_string argv[], c::const_string env[])
|
||||
: arguments(), environment_variables()
|
||||
{
|
||||
if (Application::instance_count != 0) {
|
||||
auto fatal_exception =
|
||||
std::logic_error("Cannot instantiate more than one "
|
||||
"mdml::Application class at a time");
|
||||
std::stringstream buffer;
|
||||
buffer << this->instance_count;
|
||||
|
||||
auto fatal_exception = std::logic_error(
|
||||
"Cannot instantiate more than one "
|
||||
"mdml::Application class at a time.\n"
|
||||
" instance_count = " +
|
||||
buffer.str()
|
||||
);
|
||||
throw mdml::exception(fatal_exception);
|
||||
}
|
||||
|
||||
@ -42,11 +49,14 @@ Application::Application(int argc, c::const_string argv[], c::const_string env[]
|
||||
++(Application::instance_count);
|
||||
this->parse_arguments(argc, argv);
|
||||
this->create_env_dictionary(env);
|
||||
|
||||
Application::instance_ptr = this;
|
||||
}
|
||||
|
||||
Application::~Application()
|
||||
{
|
||||
(Application::instance_count)--;
|
||||
--(Application::instance_count);
|
||||
Application::instance_ptr = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -5,6 +5,7 @@ set(LIBMDML_SOURCES
|
||||
debuginfo.cpp
|
||||
Application.cpp
|
||||
CgiApplication.cpp
|
||||
MarkdownRouteHandler.cpp
|
||||
)
|
||||
|
||||
set(LIBMDML_LINK_LIBS ${STACKTRACE_DEP_LIBS})
|
||||
|
@ -43,10 +43,14 @@ CgiApplication::~CgiApplication()
|
||||
Result<std::string>
|
||||
CgiApplication::ProcessRequest()
|
||||
{
|
||||
auto& environment = Application::environment_variables;
|
||||
try {
|
||||
auto& environment = Application::environment_variables;
|
||||
|
||||
// Check if REQUEST_URI is in the environment map
|
||||
if (environment.find("REQUEST_URI") != environment.end()) {
|
||||
// Check if REQUEST_URI is in the environment map
|
||||
if (environment.find("REQUEST_URI") == environment.end()) {
|
||||
throw mdml::exception("No REQUEST_URI in Environment!");
|
||||
}
|
||||
/* Parse REQUEST_URI to get the route's basename */
|
||||
auto request_URI = environment["REQUEST_URI"];
|
||||
auto question_pos = request_URI.find('?');
|
||||
auto ampersand_pos = request_URI.find('&');
|
||||
@ -59,26 +63,29 @@ CgiApplication::ProcessRequest()
|
||||
}
|
||||
}
|
||||
if (question_pos != not_found) {
|
||||
page_name =
|
||||
request_URI.substr(question_pos, std::string::npos);
|
||||
page_name = request_URI.substr(0, question_pos);
|
||||
}
|
||||
if (routes.find(page_name) != routes.end()) {
|
||||
auto route_handler = routes[page_name];
|
||||
auto result =
|
||||
route_handler->Process(page_name, request_URI);
|
||||
|
||||
if (!result.IsError) {
|
||||
if (result.IsError) {
|
||||
auto except =
|
||||
mdml::exception(result.ErrorData.c_str());
|
||||
throw except;
|
||||
}
|
||||
|
||||
return result;
|
||||
} else {
|
||||
std::stringstream buffer;
|
||||
buffer << "Unknown route: " << request_URI << std::endl;
|
||||
throw mdml::exception(buffer.str().c_str());
|
||||
buffer << "Unknown route: " << page_name << std::endl;
|
||||
return { ERROR, buffer.str() };
|
||||
// throw mdml::exception(buffer.str().c_str());
|
||||
}
|
||||
} catch (const std::exception& except) {
|
||||
throw mdml::exception(except);
|
||||
}
|
||||
return { NO_ERROR, "Success" };
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
141
src/MarkdownRouteHandler.cpp
Normal file
141
src/MarkdownRouteHandler.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
/* MarkdownRouteHandler.cpp
|
||||
* Copyright © 2023 Saul D. Beniquez
|
||||
* License: Mozilla Public License v. 2.0
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||
* v.2.0. If a copy of the MPL was not distributed with this file, You can
|
||||
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "IRouteHandler.hpp"
|
||||
#include "MarkdownRouteHandler.hpp"
|
||||
#include "exception.hpp"
|
||||
#include "types.hpp"
|
||||
|
||||
#include "cmark.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace mdml;
|
||||
|
||||
#if defined(TESTING)
|
||||
constexpr const char* DEFAULT_WORKDIR = ".";
|
||||
#else
|
||||
constexpr const char* DEFAULT_WORKDIR = "/usr/local/www/templates";
|
||||
#endif
|
||||
|
||||
const auto NOT_FOUND = std::string::npos;
|
||||
|
||||
void
|
||||
string_replace(
|
||||
std::string& out_buffer, const std::string& pattern,
|
||||
const std::string& replacement
|
||||
)
|
||||
{
|
||||
for (auto pattern_pos = out_buffer.find(pattern);
|
||||
pattern_pos != NOT_FOUND;
|
||||
pattern_pos = out_buffer.find(pattern)) {
|
||||
if (pattern_pos != NOT_FOUND) {
|
||||
out_buffer.replace(
|
||||
pattern_pos, pattern.length(), replacement
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MarkdownRouteHandler::MarkdownRouteHandler()
|
||||
: IRouteHandler(), OutputStream(std::cout), work_dir(DEFAULT_WORKDIR)
|
||||
{
|
||||
work_dir = fs::absolute(work_dir);
|
||||
}
|
||||
|
||||
MarkdownRouteHandler::~MarkdownRouteHandler() {}
|
||||
|
||||
void
|
||||
MarkdownRouteHandler::LoadTemplate(const std::string& template_filename)
|
||||
{
|
||||
fs::path full_html_path = work_dir / template_filename;
|
||||
|
||||
this->load_document(full_html_path, this->html_data);
|
||||
}
|
||||
|
||||
void
|
||||
MarkdownRouteHandler::LoadMarkdown(const std::string& markdown_filename)
|
||||
{
|
||||
fs::path full_md_path = work_dir / markdown_filename;
|
||||
|
||||
this->load_document(full_md_path, this->markdown_data);
|
||||
}
|
||||
|
||||
Result<std::string>
|
||||
MarkdownRouteHandler::Process(
|
||||
const std::string& name, const std::string& request_uri
|
||||
)
|
||||
{
|
||||
auto document = render_document(name, request_uri);
|
||||
auto& out = OutputStream.get();
|
||||
|
||||
out << document << std::flush;
|
||||
|
||||
return { NO_ERROR, document };
|
||||
}
|
||||
|
||||
void
|
||||
MarkdownRouteHandler::load_document(
|
||||
const fs::path& document_path, std::string& out_document
|
||||
)
|
||||
{
|
||||
if (!fs::exists(document_path) || !fs::is_regular_file(document_path)) {
|
||||
auto error = "File not found: " + document_path.string();
|
||||
throw mdml::exception(error);
|
||||
}
|
||||
|
||||
try {
|
||||
std::ifstream file(document_path);
|
||||
if (file.is_open()) {
|
||||
std::string htmlContent(
|
||||
(std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>()
|
||||
);
|
||||
out_document = htmlContent;
|
||||
} else {
|
||||
auto error_message =
|
||||
(std::stringstream()
|
||||
<< "Could not open file '"
|
||||
<< document_path.filename() << "', at path "
|
||||
<< document_path.parent_path() << ". ")
|
||||
.str();
|
||||
throw mdml::exception(error_message);
|
||||
}
|
||||
} catch (const mdml::exception& e) {
|
||||
throw e;
|
||||
} catch (const std::exception& e) {
|
||||
throw mdml::exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
MarkdownRouteHandler::render_document(
|
||||
const std::string& title, const std::string& request_uri
|
||||
)
|
||||
{
|
||||
std::string result = this->html_data;
|
||||
std::string token = "%content%";
|
||||
|
||||
std::string generated_html = cmark_markdown_to_html(
|
||||
this->markdown_data.c_str(),
|
||||
this->markdown_data.length(),
|
||||
CMARK_OPT_DEFAULT
|
||||
);
|
||||
|
||||
string_replace(result, token, generated_html);
|
||||
string_replace(result, "%title%", title);
|
||||
|
||||
return result;
|
||||
}
|
||||
// clang-format off
|
||||
// vim: set foldmethod=marker foldmarker=#region,#endregion textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen :
|
@ -26,7 +26,17 @@ constexpr unsigned DEFAULT_STACKFRAMES_TO_STRIP = 2;
|
||||
|
||||
// Helper classes and functions. #region
|
||||
|
||||
exception::exception(const char* error_message)
|
||||
exception::exception(c::const_string error_message)
|
||||
: std::exception()
|
||||
, error_message(error_message)
|
||||
, what_message()
|
||||
, stack_trace(mdml::generate_stacktrace(DEFAULT_STACKFRAMES_TO_STRIP))
|
||||
, inner_exception_ptr()
|
||||
{
|
||||
build_what_message();
|
||||
}
|
||||
|
||||
exception::exception(const std::string& error_message)
|
||||
: std::exception()
|
||||
, error_message(error_message)
|
||||
, what_message()
|
||||
|
@ -12,6 +12,9 @@
|
||||
|
||||
#include "Application.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
struct simulated_launch {
|
||||
static const char* argv[];
|
||||
static const char* env[];
|
||||
@ -21,9 +24,12 @@ inline const char* simulated_launch::env[] = { "PATH=/usr/bin",
|
||||
"VAR2=TWO",
|
||||
nullptr };
|
||||
|
||||
template<typename T>
|
||||
using opt_reference = std::optional<std::reference_wrapper<T>>;
|
||||
|
||||
BEGIN_TEST_SUITE("Application-test")
|
||||
{
|
||||
TEST("Class construction")
|
||||
TEST("Application Class construction")
|
||||
{
|
||||
auto simple_construction = []() {
|
||||
auto test_object = mdml::Application(
|
||||
@ -49,11 +55,19 @@ BEGIN_TEST_SUITE("Application-test")
|
||||
double_construction(), mdml::exception
|
||||
);
|
||||
}
|
||||
SECTION("Double construction")
|
||||
SECTION("Get existing instance")
|
||||
{
|
||||
REQUIRE_THROWS_AS(
|
||||
double_construction(), mdml::exception
|
||||
);
|
||||
opt_reference<mdml::Application> app;
|
||||
try {
|
||||
auto test_object = mdml::Application(
|
||||
3,
|
||||
simulated_launch::argv,
|
||||
simulated_launch::env
|
||||
);
|
||||
app = test_object;
|
||||
} catch (const mdml::exception& e) {
|
||||
app = mdml::Application::GetInstance();
|
||||
}
|
||||
}
|
||||
auto incorrect_construction = []() {
|
||||
mdml::Application obj(0, nullptr, nullptr);
|
||||
@ -67,7 +81,7 @@ BEGIN_TEST_SUITE("Application-test")
|
||||
}
|
||||
}
|
||||
|
||||
TEST("Parameter capture")
|
||||
TEST("Application Parameter capture")
|
||||
{
|
||||
SECTION("Arguments are captured in vector")
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ set(MDML_TEST_SOURCE_LIST
|
||||
Result-test.cpp
|
||||
Application-test.cpp
|
||||
CgiApplication-test.cpp
|
||||
MarkdownRouteHandler-test.cpp
|
||||
)
|
||||
|
||||
add_executable(mdml-tests
|
||||
|
@ -7,11 +7,14 @@
|
||||
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "tests-common.hpp"
|
||||
|
||||
#include "CgiApplication.hpp"
|
||||
#include "IRouteHandler.hpp"
|
||||
#include "exception.hpp"
|
||||
|
||||
#include "tests-common.hpp"
|
||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||
|
||||
namespace {
|
||||
struct simulated_launch {
|
||||
static const char* argv[];
|
||||
static const char* env[];
|
||||
@ -20,21 +23,46 @@ inline const char* simulated_launch::argv[] = { "param1", "param2", "param3" };
|
||||
inline const char* simulated_launch::env[] = {
|
||||
"PATH=/usr/bin",
|
||||
"VAR2=TWO",
|
||||
"REQUEST_URI=markdown?msg=hello-world"
|
||||
"REQUEST_URI=markdown?msg=hello-world",
|
||||
nullptr
|
||||
};
|
||||
}
|
||||
|
||||
using namespace mdml;
|
||||
|
||||
TEST_CASE("CgiApplication Unit Tests")
|
||||
BEGIN_TEST_SUITE("CgiApplication")
|
||||
{
|
||||
CgiApplication cgiHandler(
|
||||
1, simulated_launch::argv, simulated_launch::env
|
||||
);
|
||||
using namespace mdml;
|
||||
struct TestFixture {
|
||||
const char* envp[4] = { "PATH=/usr/bin",
|
||||
"VAR2=TWO",
|
||||
"REQUEST_URI=markdown?msg=hello-world",
|
||||
nullptr };
|
||||
TestFixture() : cgi_app(1, simulated_launch::argv, envp) {}
|
||||
|
||||
SECTION("ProcessRequest with valid route")
|
||||
CgiApplication cgi_app;
|
||||
};
|
||||
|
||||
TEST("CgiApplication Constructor Test")
|
||||
{
|
||||
|
||||
// Define a test route handler for the "/markdown" route
|
||||
CgiApplication test(
|
||||
1, simulated_launch::argv, simulated_launch::env
|
||||
);
|
||||
};
|
||||
|
||||
TEST("CgiApplication::ProcessRequest no REQUEST_URI variable")
|
||||
{
|
||||
const char* no_request_env[] = { "PATH=/usr/bin",
|
||||
"VAR2=TWO",
|
||||
nullptr };
|
||||
CgiApplication test(1, simulated_launch::argv, no_request_env);
|
||||
REQUIRE_THROWS_AS(
|
||||
[&]() { test.ProcessRequest(); }(), mdml::exception
|
||||
);
|
||||
}
|
||||
|
||||
FIXTURE_TEST("CgiApplication ProcessRequest with valid route")
|
||||
{
|
||||
// Define a test oute handler for the "/markdown" route
|
||||
class ExampleRouteHandler : public IRouteHandler {
|
||||
public:
|
||||
virtual Result<std::string> Process(
|
||||
@ -42,34 +70,66 @@ TEST_CASE("CgiApplication Unit Tests")
|
||||
const std::string& request_uri
|
||||
) override
|
||||
{
|
||||
// Implement test logic for the route handler
|
||||
return { mdml::NO_ERROR, "Success" };
|
||||
// Implement test logic for the route
|
||||
// handler
|
||||
return { mdml::NO_ERROR, "Processed" };
|
||||
}
|
||||
virtual ~ExampleRouteHandler() = default;
|
||||
};
|
||||
|
||||
// Add the test route handler to the Routes dictionary
|
||||
auto handler = CgiApplication::make_ptr<ExampleRouteHandler>();
|
||||
cgiHandler.Routes["/markdown"] = handler;
|
||||
cgi_app.Routes["markdown"] = handler;
|
||||
|
||||
auto result = cgiHandler.ProcessRequest();
|
||||
auto result = cgi_app.ProcessRequest();
|
||||
|
||||
// Add assertions to check the result
|
||||
CHECK(result.IsError == false);
|
||||
CHECK(result.ErrorData == "Success");
|
||||
CHECK(result.ErrorData == "Processed");
|
||||
REQUIRE(result.IsError == false);
|
||||
}
|
||||
|
||||
SECTION("ProcessRequest with unknown route")
|
||||
FIXTURE_TEST("CgiApplication ProcessRequest with unknown route")
|
||||
{
|
||||
// Remove the "/markdown" route from the Routes dictionary
|
||||
cgiHandler.Routes.erase("/markdown");
|
||||
|
||||
cgiHandler.ProcessRequest();
|
||||
// Remove the "markdown" route from the Routes
|
||||
// dictionary
|
||||
for (auto& [key, value] : cgi_app.Routes) {
|
||||
if (value.get() != nullptr) {
|
||||
value.reset();
|
||||
}
|
||||
cgi_app.Routes.erase(key);
|
||||
}
|
||||
|
||||
// Add assertions to check the result for the unknown route
|
||||
/*REQUIRE( Add assertions for the test case &/);*/
|
||||
auto result = cgi_app.ProcessRequest();
|
||||
|
||||
// Add assertions to check the result
|
||||
CHECK(result.ErrorData != "Success");
|
||||
REQUIRE(result.IsError == true);
|
||||
}
|
||||
|
||||
FIXTURE_TEST("CgiApplication ProcessRequest Route throws exception")
|
||||
{
|
||||
// Define a test oute handler for the "/markdown" route
|
||||
class BadRouteHandler : public IRouteHandler {
|
||||
public:
|
||||
virtual Result<std::string> Process(
|
||||
const std::string& name,
|
||||
const std::string& request_uri
|
||||
) override
|
||||
{
|
||||
throw std::runtime_error("ERROR");
|
||||
return { mdml::ERROR,
|
||||
"This line never executes" };
|
||||
}
|
||||
virtual ~BadRouteHandler() = default;
|
||||
};
|
||||
auto handler = CgiApplication::make_ptr<BadRouteHandler>();
|
||||
cgi_app.Routes["markdown"] = handler;
|
||||
|
||||
REQUIRE_THROWS_AS(
|
||||
[&]() { cgi_app.ProcessRequest(); }(), mdml::exception
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
// vim: set foldmethod=marker foldmarker=#region,#endregion textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen :
|
||||
|
108
tests/MarkdownRouteHandler-test.cpp
Normal file
108
tests/MarkdownRouteHandler-test.cpp
Normal file
@ -0,0 +1,108 @@
|
||||
/* CgiApplication-test.cpp
|
||||
* Copyright © 2023 Saul D. Beniquez
|
||||
* License: Mozilla Public License v. 2.0
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||
* v.2.0. If a copy of the MPL was not distributed with this file, You can
|
||||
* obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "tests-common.hpp"
|
||||
|
||||
#include "IRouteHandler.hpp"
|
||||
#include "MarkdownRouteHandler.hpp"
|
||||
|
||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace {
|
||||
struct simulated_launch {
|
||||
static const char* argv[];
|
||||
static const char* env[];
|
||||
};
|
||||
inline const char* simulated_launch::argv[] = { "param1", "param2", "param3" };
|
||||
inline const char* simulated_launch::env[] = {
|
||||
"PATH=/usr/bin",
|
||||
"VAR2=TWO",
|
||||
"REQUEST_URI=markdown?src=index.md"
|
||||
};
|
||||
}
|
||||
|
||||
using namespace mdml;
|
||||
|
||||
BEGIN_TEST_SUITE("MarkdownRouteHandler Unit Tests")
|
||||
{
|
||||
|
||||
TEST("MarkdownRouteHandler Construction and allocation")
|
||||
{
|
||||
|
||||
REQUIRE_NOTHROW([]() { MarkdownRouteHandler test; }());
|
||||
}
|
||||
|
||||
struct TestFixture {
|
||||
MarkdownRouteHandler test_obj;
|
||||
};
|
||||
|
||||
FIXTURE_TEST("MarkdownRouteHandler LoadHtml")
|
||||
{
|
||||
MarkdownRouteHandler test_obj;
|
||||
|
||||
SECTION("File exists")
|
||||
{
|
||||
REQUIRE_NOTHROW([&]() {
|
||||
test_obj.LoadTemplate("data/test.thtml");
|
||||
}());
|
||||
|
||||
REQUIRE(false == test_obj.GetHtmlData().empty());
|
||||
}
|
||||
SECTION("File does not exists")
|
||||
{
|
||||
REQUIRE_THROWS([&]() {
|
||||
test_obj.LoadTemplate("not-found.txt");
|
||||
}());
|
||||
}
|
||||
}
|
||||
FIXTURE_TEST("MarkdownRouteHandler LoadMarkdown")
|
||||
{
|
||||
MarkdownRouteHandler test_obj;
|
||||
|
||||
SECTION("File exists")
|
||||
{
|
||||
REQUIRE_NOTHROW([&]() {
|
||||
test_obj.LoadMarkdown("data/test.md");
|
||||
}());
|
||||
|
||||
REQUIRE(false == test_obj.GetMarkdownData().empty());
|
||||
}
|
||||
SECTION("File does not exists")
|
||||
{
|
||||
REQUIRE_THROWS([&]() {
|
||||
test_obj.LoadMarkdown("not-found.txt");
|
||||
}());
|
||||
}
|
||||
}
|
||||
FIXTURE_TEST("MarkdownRouteHandler Process")
|
||||
{
|
||||
std::stringstream buffer;
|
||||
test_obj.OutputStream = buffer;
|
||||
|
||||
REQUIRE_NOTHROW([&]() {
|
||||
test_obj.LoadTemplate("data/test.thtml");
|
||||
test_obj.LoadMarkdown("data/test.md");
|
||||
|
||||
test_obj.Process("test", "test?param=1");
|
||||
}());
|
||||
auto resulting_document = buffer.str();
|
||||
REQUIRE_THAT(
|
||||
resulting_document,
|
||||
!Catch::Matchers::ContainsSubstring("%content%")
|
||||
);
|
||||
REQUIRE_THAT(
|
||||
resulting_document,
|
||||
!Catch::Matchers::ContainsSubstring("%title%")
|
||||
);
|
||||
}
|
||||
}
|
||||
// clang-format off
|
||||
// vim: set foldmethod=marker foldmarker=#region,#endregion textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen :
|
@ -23,7 +23,7 @@ namespace Match = Catch::Matchers;
|
||||
using Catch::CaseSensitive;
|
||||
|
||||
void
|
||||
throw_local_variable()
|
||||
throw_local_exception()
|
||||
{
|
||||
auto local = std::logic_error("This is a stack variable");
|
||||
|
||||
@ -48,10 +48,15 @@ BEGIN_TEST_SUITE("mdml::exception")
|
||||
{
|
||||
mdml::exception obj;
|
||||
}
|
||||
SECTION("2. With string parameter")
|
||||
SECTION("2a. With cstring parameter")
|
||||
{
|
||||
mdml::exception obj("Sample Error");
|
||||
}
|
||||
SECTION("2b. With std::string parameter")
|
||||
{
|
||||
mdml::exception obj(std::string("Sample Error"));
|
||||
}
|
||||
|
||||
SECTION("3. With STL exception")
|
||||
{
|
||||
mdml::exception obj(std::runtime_error("Sample Error"));
|
||||
@ -59,7 +64,7 @@ BEGIN_TEST_SUITE("mdml::exception")
|
||||
SECTION("4. With destroyed stack")
|
||||
{
|
||||
auto nested_function_call = []() {
|
||||
throw_local_variable();
|
||||
throw_local_exception();
|
||||
};
|
||||
try {
|
||||
nested_function_call();
|
||||
|
@ -24,9 +24,9 @@
|
||||
#define TEST(testname) TEST_CASE(testname, TEST_SUITE_NAME)
|
||||
|
||||
#define TEST_WITH_FIXTURE(FixtureName, testname) \
|
||||
TEST_CASE_METHOD(FixtureName, #testname, TEST_SUITE_NAME)
|
||||
TEST_CASE_METHOD(FixtureName, testname, TEST_SUITE_NAME)
|
||||
|
||||
#define FIXTURE_TEST(testname) TEST_WITH_FIXTURE(TestFixture, #testname)
|
||||
#define FIXTURE_TEST(testname) TEST_WITH_FIXTURE(TestFixture, testname)
|
||||
|
||||
#ifdef VIM_COMPLETION
|
||||
#define UNIT_TEST 1
|
||||
|
Loading…
Reference in New Issue
Block a user