#include "app.h"
#include "data_source_sqlite.h"
#include "embedded_assets.h"
#include <chrono>
#include <string>
#include <thread>
#include <utility>
#include <gtest/gtest.h>
namespace
{
class TestApp : public App
{
public:
TestApp(Configuration configuration, DataSourceInterface& data_source)
: App(mw::IPSocketInfo{"127.0.0.1", 0}, std::move(configuration),
data_source)
{}
int listen()
{
setup();
const int port = server.bind_to_any_port("127.0.0.1");
if(port >= 0)
{
worker = std::thread(&TestApp::serve, this);
server.wait_until_ready();
}
return port;
}
~TestApp()
{
server.stop();
if(worker.joinable())
{
worker.join();
}
}
private:
void serve()
{
server.listen_after_bind();
}
std::thread worker;
};
Configuration emptyConfiguration()
{
return {};
}
Configuration serviceConfiguration()
{
return Configuration{
.worker_count = 1,
.database_path = ":memory:",
.groups = {{
.name = "Websites",
.services = {{
.id = "blog",
.name = "Blog",
.description = "Personal website",
.endpoint = HttpEndpoint{"https://example.test"},
.timeout = std::chrono::seconds(5),
.interval = std::chrono::hours(1),
.url = "https://example.test"
}}
}}
};
}
std::unique_ptr<DataSourceSqlite> openTestSource()
{
auto source = DataSourceSqlite::open(":memory:");
EXPECT_TRUE(source);
return source ? std::move(*source) : nullptr;
}
TEST(App, ServesEmbeddedAssets)
{
auto source = openTestSource();
ASSERT_TRUE(source);
TestApp app(emptyConfiguration(), *source);
const int port = app.listen();
ASSERT_GT(port, 0);
httplib::Client client("127.0.0.1", port);
for(const auto& asset : embeddedAssets())
{
if(asset.path == "/")
{
continue;
}
auto response = client.Get(std::string(asset.path) + "?v=1");
ASSERT_TRUE(response);
EXPECT_EQ(response->status, 200);
EXPECT_EQ(response->body, asset.content);
EXPECT_EQ(response->get_header_value("Content-Type"),
asset.content_type);
auto head = client.Head(std::string(asset.path));
ASSERT_TRUE(head);
EXPECT_EQ(head->status, 200);
EXPECT_TRUE(head->body.empty());
EXPECT_EQ(head->get_header_value("Content-Length"),
std::to_string(asset.content.size()));
}
auto index = client.Get("/");
ASSERT_TRUE(index);
EXPECT_EQ(index->status, 200);
EXPECT_NE(index->body.find("No services are configured"),
std::string::npos);
}
TEST(App, RejectsUnknownPathsAndWrites)
{
auto source = openTestSource();
ASSERT_TRUE(source);
TestApp app(emptyConfiguration(), *source);
const int port = app.listen();
ASSERT_GT(port, 0);
httplib::Client client("127.0.0.1", port);
for(const auto* path : {"/missing", "/static/missing.css", "/prd.md"})
{
auto response = client.Get(path);
ASSERT_TRUE(response);
EXPECT_EQ(response->status, 404);
}
auto response = client.Post("/static/style.css", "replace", "text/plain");
ASSERT_TRUE(response);
EXPECT_NE(response->status, 200);
}
TEST(App, RendersConfiguredStatuses)
{
auto source = openTestSource();
ASSERT_TRUE(source);
const auto now = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
ASSERT_TRUE(source->save({"blog", now, 1200, ProbeStatus::GOOD}));
TestApp app(serviceConfiguration(), *source);
const int port = app.listen();
ASSERT_GT(port, 0);
httplib::Client client("127.0.0.1", port);
auto response = client.Get("/");
ASSERT_TRUE(response);
EXPECT_EQ(response->status, 200);
EXPECT_NE(response->body.find("Websites"), std::string::npos);
EXPECT_NE(response->body.find("Blog"), std::string::npos);
EXPECT_NE(response->body.find("Personal website"), std::string::npos);
EXPECT_NE(response->body.find("led-good"), std::string::npos);
EXPECT_NE(response->body.find("Good"), std::string::npos);
}
TEST(App, RendersMissingStatusAsNa)
{
auto source = openTestSource();
ASSERT_TRUE(source);
TestApp app(serviceConfiguration(), *source);
const int port = app.listen();
ASSERT_GT(port, 0);
httplib::Client client("127.0.0.1", port);
auto response = client.Get("/");
ASSERT_TRUE(response);
EXPECT_EQ(response->status, 200);
EXPECT_NE(response->body.find("led-na"), std::string::npos);
EXPECT_NE(response->body.find("N/A"), std::string::npos);
}
TEST(App, DerivesNaForStaleStatus)
{
auto source = openTestSource();
ASSERT_TRUE(source);
const auto now = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
ASSERT_TRUE(source->save({"blog", now - 7201, 1200, ProbeStatus::GOOD}));
TestApp app(serviceConfiguration(), *source);
const int port = app.listen();
ASSERT_GT(port, 0);
httplib::Client client("127.0.0.1", port);
auto response = client.Get("/");
ASSERT_TRUE(response);
EXPECT_EQ(response->status, 200);
EXPECT_NE(response->body.find("led-na"), std::string::npos);
}
}