Fix posting build action to start it later; add basic test for the route

This commit is contained in:
Martchus 2021-01-26 19:34:54 +01:00
parent 0c8f0b04c2
commit 3894d911d7
6 changed files with 129 additions and 32 deletions

View File

@ -1,5 +1,9 @@
#include "../buildactions/buildaction.h"
#include "../serversetup.h"
#include "../webapi/params.h"
#include "../webapi/routes.h"
#include "../webapi/server.h"
#include "../webapi/session.h"
#include "../webclient/session.h"
#include "../../libpkg/data/config.h"
@ -13,6 +17,8 @@
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <boost/asio/ip/tcp.hpp>
#include <iostream>
#include <random>
#include <string>
@ -21,6 +27,7 @@
using namespace std;
using namespace CPPUNIT_NS;
using namespace CppUtilities;
using namespace CppUtilities::Literals;
using namespace LibRepoMgr;
using namespace LibRepoMgr::WebAPI;
@ -28,6 +35,7 @@ using namespace LibRepoMgr::WebAPI;
class WebAPITests : public TestFixture {
CPPUNIT_TEST_SUITE(WebAPITests);
CPPUNIT_TEST(testBasicNetworking);
CPPUNIT_TEST(testPostingBuildAction);
CPPUNIT_TEST_SUITE_END();
public:
@ -37,6 +45,9 @@ public:
void testRoutes(const std::list<std::pair<string, WebClient::Session::Handler>> &routes);
void testBasicNetworking();
std::shared_ptr<WebAPI::Response> invokeRouteHandler(
void (*handler)(const Params &params, ResponseHandler &&handler), std::vector<std::pair<std::string_view, std::string_view>> &&queryParams);
void testPostingBuildAction();
private:
ServiceSetup m_setup;
@ -46,7 +57,7 @@ private:
CPPUNIT_TEST_SUITE_REGISTRATION(WebAPITests);
unsigned short randomPort()
static unsigned short randomPort()
{
random_device dev;
default_random_engine engine(dev());
@ -68,6 +79,9 @@ void WebAPITests::tearDown()
{
}
/*!
* \brief Runs the Boost.Asio/Beast server and client to simulte accessing the specified \a routes.
*/
void WebAPITests::testRoutes(const std::list<std::pair<std::string, WebClient::Session::Handler>> &routes)
{
// get first route
@ -83,9 +97,6 @@ void WebAPITests::testRoutes(const std::list<std::pair<std::string, WebClient::S
// define function to stop server
const auto stopServer = [&] {
if (server->m_socket.is_open()) {
server->m_socket.cancel();
}
if (server->m_acceptor.is_open()) {
server->m_acceptor.cancel();
}
@ -104,7 +115,7 @@ void WebAPITests::testRoutes(const std::list<std::pair<std::string, WebClient::S
handleResponse = [&](WebClient::Session &session, const WebClient::HttpClientError &error) {
currentRoute->second(session, error);
if (++currentRoute == routes.end()) {
boost::asio::post(server->m_socket.get_executor(), stopServer);
boost::asio::post(server->m_acceptor.get_executor(), stopServer);
return;
}
testNextRoute();
@ -115,6 +126,10 @@ void WebAPITests::testRoutes(const std::list<std::pair<std::string, WebClient::S
m_setup.webServer.ioContext.run();
}
/*!
* \brief Checks a few basic routes using the Boost.Beast-based HTTP server and client to test whether basic
* networking and HTTP processing works.
*/
void WebAPITests::testBasicNetworking()
{
testRoutes({
@ -123,7 +138,6 @@ void WebAPITests::testBasicNetworking()
const auto &response = get<Response>(session.response);
CPPUNIT_ASSERT(!error);
CPPUNIT_ASSERT(!response.body().empty());
cout << "index: " << response.body() << endl;
} },
{ "/foo",
[](const WebClient::Session &session, const WebClient::HttpClientError &error) {
@ -145,7 +159,67 @@ void WebAPITests::testBasicNetworking()
CPPUNIT_ASSERT(!error);
CPPUNIT_ASSERT(!response.body().empty());
CPPUNIT_ASSERT_EQUAL("application/json"s, response[boost::beast::http::field::content_type].to_string());
cout << "status: " << response.body() << endl;
} },
});
}
/*!
* \brief Invokes the specified route \a handler with the specified \a queryParams and returns the response.
*/
std::shared_ptr<Response> WebAPITests::invokeRouteHandler(
void (*handler)(const Params &, ResponseHandler &&), std::vector<std::pair<std::string_view, std::string_view>> &&queryParams)
{
auto &ioc = m_setup.webServer.ioContext;
auto session = std::make_shared<WebAPI::Session>(boost::asio::ip::tcp::socket(ioc), m_setup);
auto params = WebAPI::Params(m_setup, *session, WebAPI::Url(std::string_view(), std::string_view(), std::move(queryParams)));
auto response = std::shared_ptr<WebAPI::Response>();
session->assignEmptyRequest();
std::invoke(handler, params, [&response](std::shared_ptr<WebAPI::Response> &&r) { response = r; });
return response;
}
/*!
* \brief Parses the specified \a json as build action storing results in \a buildAction.
*/
static void parseBuildAction(BuildAction &buildAction, std::string_view json)
{
const auto doc = ReflectiveRapidJSON::JsonReflector::parseJsonDocFromString(json.data(), json.size());
if (!doc.IsObject()) {
CPPUNIT_FAIL("json document is no object");
}
auto errors = ReflectiveRapidJSON::JsonDeserializationErrors();
ReflectiveRapidJSON::JsonReflector::pull(buildAction, doc.GetObject(), &errors);
CPPUNIT_ASSERT_EQUAL_MESSAGE("response is valid json", 0_st, errors.size());
}
/*!
* \brief Tests the handler to post a build action.
* \remarks Only covers a very basic use so far.
*/
void WebAPITests::testPostingBuildAction()
{
{
const auto response = invokeRouteHandler(&WebAPI::Routes::postBuildAction, {});
CPPUNIT_ASSERT_MESSAGE("got response", response);
CPPUNIT_ASSERT_EQUAL_MESSAGE("response body", "need exactly either one type or one task parameter"s, response->body());
CPPUNIT_ASSERT_EQUAL_MESSAGE("response ok", boost::beast::http::status::bad_request, response->result());
}
{
const auto response = invokeRouteHandler(&WebAPI::Routes::postBuildAction,
{
{ "type"sv, "prepare-build"sv },
{ "start-condition"sv, "manually"sv },
});
CPPUNIT_ASSERT_MESSAGE("got response", response);
auto buildAction = BuildAction();
parseBuildAction(buildAction, response->body());
CPPUNIT_ASSERT_EQUAL_MESSAGE("expected build action type returned", BuildActionType::PrepareBuild, buildAction.type);
const auto createdBuildAction = m_setup.building.getBuildAction(buildAction.id);
CPPUNIT_ASSERT_MESSAGE("build action actually created", createdBuildAction);
CPPUNIT_ASSERT_EQUAL_MESSAGE("build action not started yet", BuildActionStatus::Created, createdBuildAction->status);
CPPUNIT_ASSERT_EQUAL_MESSAGE("build action has no result yet", BuildActionResult::None, createdBuildAction->result);
CPPUNIT_ASSERT_EQUAL_MESSAGE("response ok", boost::beast::http::status::ok, response->result());
}
}

View File

@ -9,6 +9,13 @@ using namespace CppUtilities;
namespace LibRepoMgr {
namespace WebAPI {
Url::Url(std::string_view path, std::string_view hash, std::vector<std::pair<std::string_view, std::string_view>> &&params)
: path(path)
, hash(hash)
, params(std::move(params))
{
}
Url::Url(const Request &request)
{
const auto target = request.target();

View File

@ -35,6 +35,7 @@ inline BadRequest::BadRequest(const char *message)
}
struct LIBREPOMGR_EXPORT Url {
Url(std::string_view path, std::string_view hash, std::vector<std::pair<std::string_view, std::string_view>> &&params);
Url(const Request &request);
std::string_view path;
std::string_view hash;
@ -55,6 +56,7 @@ inline bool Url::hasPrettyFlag() const
struct LIBREPOMGR_EXPORT Params {
Params(ServiceSetup &setup, Session &session);
Params(ServiceSetup &setup, Session &session, Url &&target);
ServiceSetup &setup;
Session &session;
const Url target;
@ -70,6 +72,13 @@ inline Params::Params(ServiceSetup &setup, Session &session)
{
}
inline Params::Params(ServiceSetup &setup, Session &session, Url &&target)
: setup(setup)
, session(session)
, target(std::move(target))
{
}
inline const Request &Params::request() const
{
return session.request();

View File

@ -620,7 +620,7 @@ void postBuildAction(const Params &params, ResponseHandler &&handler)
auto buildLock = params.setup.building.lockToWrite();
const auto id = params.setup.building.allocateBuildActionID();
auto startsAfterBuildActions = params.setup.building.getBuildActions(startAfterIds);
const auto startNow = startImmediately || BuildAction::haveSucceeded(startsAfterBuildActions);
const auto startNow = startImmediately || (!startsAfterBuildActions.empty() && BuildAction::haveSucceeded(startsAfterBuildActions));
buildLock.unlock();
auto buildAction = std::make_shared<BuildAction>(id);
if (!directories.empty()) {
@ -734,7 +734,7 @@ void postBuildActionsFromTask(const Params &params, ResponseHandler &&handler, c
auto &building = params.setup.building;
auto buildLock = building.lockToWrite();
auto startsAfterBuildActions = building.getBuildActions(startAfterIds);
const auto startNow = startImmediately || BuildAction::haveSucceeded(startsAfterBuildActions);
const auto startNow = startImmediately || (!startsAfterBuildActions.empty() && BuildAction::haveSucceeded(startsAfterBuildActions));
for (auto &newBuildAction : newBuildActions) {
newBuildAction->id = building.allocateBuildActionID();
if (lastBuildAction) {

View File

@ -1,6 +1,7 @@
#ifndef LIBREPOMGR_ROUTES_H
#define LIBREPOMGR_ROUTES_H
#include "../global.h"
#include "./typedefs.h"
#include "../buildactions/buildactionfwd.h"
@ -9,27 +10,27 @@ namespace LibRepoMgr {
namespace WebAPI {
namespace Routes {
void getRoot(const Params &params, ResponseHandler &&handler);
void getVersion(const Params &params, ResponseHandler &&handler);
void getStatus(const Params &params, ResponseHandler &&handler);
void getDatabases(const Params &params, ResponseHandler &&handler);
void getUnresolved(const Params &params, ResponseHandler &&handler);
void getPackages(const Params &params, ResponseHandler &&handler);
void getBuildActions(const Params &params, ResponseHandler &&handler);
void getBuildActionDetails(const Params &params, ResponseHandler &&handler);
void getBuildActionOutput(const Params &params, ResponseHandler &&handler);
void getBuildActionLogFile(const Params &params, ResponseHandler &&handler);
void getBuildActionArtefact(const Params &params, ResponseHandler &&handler);
void postLoadPackages(const Params &params, ResponseHandler &&handler);
void postDumpCacheFile(const Params &params, ResponseHandler &&handler);
void postBuildAction(const Params &params, ResponseHandler &&handler);
void postBuildActionsFromTask(const Params &params, ResponseHandler &&handler, const std::string &taskName, const std::string &directory,
const std::vector<BuildActionIdType> &startAfterIds, bool startImmediately);
void deleteBuildActions(const Params &params, ResponseHandler &&handler);
void postCloneBuildActions(const Params &params, ResponseHandler &&handler);
void postStartBuildActions(const Params &params, ResponseHandler &&handler);
void postStopBuildActions(const Params &params, ResponseHandler &&handler);
void postQuit(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void getRoot(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void getVersion(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void getStatus(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void getDatabases(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void getUnresolved(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void getPackages(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void getBuildActions(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void getBuildActionDetails(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void getBuildActionOutput(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void getBuildActionLogFile(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void getBuildActionArtefact(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void postLoadPackages(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void postDumpCacheFile(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void postBuildAction(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void postBuildActionsFromTask(const Params &params, ResponseHandler &&handler, const std::string &taskName,
const std::string &directory, const std::vector<BuildActionIdType> &startAfterIds, bool startImmediately);
LIBREPOMGR_EXPORT void deleteBuildActions(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void postCloneBuildActions(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void postStartBuildActions(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void postStopBuildActions(const Params &params, ResponseHandler &&handler);
LIBREPOMGR_EXPORT void postQuit(const Params &params, ResponseHandler &&handler);
} // namespace Routes
} // namespace WebAPI

View File

@ -18,13 +18,14 @@ namespace WebAPI {
class Session : public std::enable_shared_from_this<Session> {
public:
Session(boost::asio::ip::tcp::socket socket, ServiceSetup &config);
Session(boost::asio::ip::tcp::socket &&socket, ServiceSetup &config);
void receive();
void respond(std::shared_ptr<Response> &&response);
void respond(const char *localFilePath, const char *mimeType, std::string_view urlPath);
void close();
const Request &request() const;
void assignEmptyRequest();
boost::asio::ip::tcp::socket &socket();
void received(boost::system::error_code ec, std::size_t bytesTransferred);
void responded(boost::system::error_code ec, std::size_t bytesTransferred, bool shouldClose);
@ -38,7 +39,7 @@ private:
std::shared_ptr<void> m_res;
};
inline Session::Session(boost::asio::ip::tcp::socket socket, ServiceSetup &setup)
inline Session::Session(boost::asio::ip::tcp::socket &&socket, ServiceSetup &setup)
: m_socket(std::move(socket))
, m_strand(m_socket.get_executor())
, m_setup(setup)
@ -50,6 +51,11 @@ inline const Request &Session::request() const
return m_parser->get();
}
inline void Session::assignEmptyRequest()
{
m_parser = std::make_unique<RequestParser>();
}
inline boost::asio::ip::tcp::socket &Session::socket()
{
return m_socket;