#ifndef CEPIS_TESTS_TEST_SERVER_HPP #define CEPIS_TESTS_TEST_SERVER_HPP #include #include #include #include #include #include #include #include #include namespace cepis::testing { /// One canned HTTP response. struct Reply { int status = 200; std::string body; std::vector> headers; }; /// A throwaway HTTP/1.1 server on a random loopback port. /// /// The tests point `Config::base_url` at it, so the code path they exercise is /// the one that runs in production — request construction, header reading and /// error-body reading included. Nothing is mocked below the socket, and it /// doubles as the test that `base_url` works: without it none of this runs. class Server { public: Server(); ~Server(); Server(const Server&) = delete; Server& operator=(const Server&) = delete; Server(Server&&) = delete; Server& operator=(Server&&) = delete; /// `http://127.0.0.1:`, ready to hand to `Config::base_url`. [[nodiscard]] std::string url() const; /// Registers the response for a path. The last entry of a queue repeats once /// the earlier ones are consumed, which is how retrying is tested. void respond(std::string path, Reply reply); void respond(std::string path, std::vector replies); /// Installs a catch-all that sees every request. Takes precedence over the /// registered paths. void handle(std::function handler); /// How many requests so far began with `prefix`. [[nodiscard]] int count(std::string_view prefix) const; /// Every path requested so far, in order. [[nodiscard]] std::vector calls() const; private: void serve(); Reply next(const std::string& path); int listen_fd_ = -1; int port_ = 0; std::atomic stopping_{false}; std::thread thread_; mutable std::mutex mutex_; std::map> routes_; std::function handler_; std::vector calls_; }; } // namespace cepis::testing #endif // CEPIS_TESTS_TEST_SERVER_HPP