diff --git a/src/moonlight-server/api/endpoints.cpp b/src/moonlight-server/api/endpoints.cpp index 7a8c20b8..a09d25d6 100644 --- a/src/moonlight-server/api/endpoints.cpp +++ b/src/moonlight-server/api/endpoints.cpp @@ -13,12 +13,11 @@ void UnixSocketServer::endpoint_Events(const HTTPRequest &req, std::shared_ptr socket) { - auto res = PendingPairRequestsResponse{.success = true}; auto requests = std::vector(); for (auto [secret, pair_request] : *(state_->app_state)->pairing_atom->load()) { requests.push_back({.pair_secret = secret, .pin = pair_request->client_ip}); } - send_http(socket, 200, rfl::json::write(res)); + send_http(socket, 200, rfl::json::write(PendingPairRequestsResponse{.requests = requests})); } void UnixSocketServer::endpoint_Pair(const HTTPRequest &req, std::shared_ptr socket) { diff --git a/src/moonlight-server/state/config.hpp b/src/moonlight-server/state/config.hpp index 97870d99..01715877 100644 --- a/src/moonlight-server/state/config.hpp +++ b/src/moonlight-server/state/config.hpp @@ -111,5 +111,6 @@ static moonlight::control::pkts::CONTROLLER_TYPE get_controller_type(const Contr case ControllerType::AUTO: return moonlight::control::pkts::CONTROLLER_TYPE::AUTO; } + return moonlight::control::pkts::CONTROLLER_TYPE::AUTO; } } // namespace state \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 77e7630c..a413f490 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -20,7 +20,8 @@ set(SRC_LIST testCrypto.cpp testGSTPlugin.cpp testMoonlight.cpp - testRTSP.cpp) + testRTSP.cpp + testWolfAPI.cpp) if (UNIX AND NOT APPLE) option(TEST_RUST_WAYLAND "Enable custom wayland test" ON) @@ -77,6 +78,9 @@ if (UNIX AND NOT APPLE) endif () endif () +find_package(CURL) +target_link_libraries(wolftests PUBLIC CURL::libcurl) + list(APPEND SRC_LIST "testSerialization.cpp") option(TEST_DOCKER "Enable docker tests" ON) diff --git a/tests/testWolfAPI.cpp b/tests/testWolfAPI.cpp new file mode 100644 index 00000000..9a79e336 --- /dev/null +++ b/tests/testWolfAPI.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include + +using Catch::Matchers::Equals; + +using namespace wolf::api; +using curl_ptr = std::unique_ptr; + +/** + * Perform a HTTP request using curl + */ +std::optional> +req(CURL *handle, + HTTPMethod method, + std::string_view target, + std::string_view post_body = {}, + const std::vector &header_params = {}) { + logs::log(logs::trace, "[CURL] Sending [{}] -> {}", (int)method, target); + curl_easy_setopt(handle, CURLOPT_URL, target.data()); + + /* Set method */ + switch (method) { + case HTTPMethod::GET: + curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "GET"); + break; + case HTTPMethod::POST: + curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "POST"); + break; + case HTTPMethod::PUT: + curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "PUT"); + break; + case HTTPMethod::DELETE: + curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "DELETE"); + break; + } + curl_easy_setopt(handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + + struct curl_slist *headers = nullptr; + for (const auto &header : header_params) { + headers = curl_slist_append(headers, header.c_str()); + } + + /* Pass POST params (if present) */ + if (method == HTTPMethod::POST && !post_body.empty()) { + logs::log(logs::trace, "[CURL] POST: {}", post_body); + + curl_easy_setopt(handle, CURLOPT_POST, 1L); + headers = curl_slist_append(headers, "Content-type: application/json"); + curl_easy_setopt(handle, CURLOPT_POSTFIELDS, post_body.data()); + curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE, post_body.size()); + } + curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); + + /* Set custom writer (in order to receive back the response) */ + curl_easy_setopt( + handle, + CURLOPT_WRITEFUNCTION, + static_cast([](char *ptr, size_t size, size_t nmemb, void *read_buf) { + *(static_cast(read_buf)) += std::string{ptr, size * nmemb}; + return size * nmemb; + })); + std::string read_buf; + curl_easy_setopt(handle, CURLOPT_WRITEDATA, &read_buf); + + /* Run! */ + auto res = curl_easy_perform(handle); + curl_slist_free_all(headers); + if (res != CURLE_OK) { + logs::log(logs::warning, "[CURL] Request failed with error: {}", curl_easy_strerror(res)); + return {}; + } else { + long response_code; + curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &response_code); + logs::log(logs::trace, "[CURL] Received {} - {}", response_code, read_buf); + return {{response_code, read_buf}}; + } +} + +TEST_CASE("Test pair APIs", "[API]") { + auto event_bus = std::make_shared(); + auto config = immer::box(state::load_or_default("config.test.toml", event_bus)); + auto app_state = immer::box(state::AppState{ + .config = config, + .pairing_cache = std::make_shared>>(), + .pairing_atom = std::make_shared>>>(), + .event_bus = event_bus, + .running_sessions = std::make_shared>>()}); + + // Start the server + std::thread server_thread([app_state]() { wolf::api::start_server(app_state); }); + server_thread.detach(); + std::this_thread::sleep_for(std::chrono::milliseconds(42)); // Wait for the server to start + + curl_global_init(CURL_GLOBAL_ALL); + auto curl = curl_ptr(curl_easy_init(), ::curl_easy_cleanup); + + curl_easy_setopt(curl.get(), CURLOPT_UNIX_SOCKET_PATH, "/tmp/wolf.sock"); + curl_easy_setopt(curl.get(), CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + + auto response = req(curl.get(), HTTPMethod::GET, "http://localhost/api/v1/pair/pending"); + REQUIRE(response); + REQUIRE_THAT(response->second, Equals("{\"success\":true,\"requests\":[]}")); + + auto pair_promise = std::make_shared>(); + + // Simulate a Moonlight pairing request + app_state->pairing_atom->update([pair_promise](auto pairing_map) { + return pairing_map.set("secret", + immer::box{ + events::PairSignal{.client_ip = "1234", .host_ip = "5678", .user_pin = pair_promise}}); + }); + + response = req(curl.get(), HTTPMethod::GET, "http://localhost/api/v1/pair/pending"); + REQUIRE(response); + REQUIRE_THAT(response->second, + Equals("{\"success\":true,\"requests\":[{\"pair_secret\":\"secret\",\"pin\":\"1234\"}]}")); + + // Let's complete the pairing process + response = req(curl.get(), + HTTPMethod::POST, + "http://localhost/api/v1/pair/client", + "{\"pair_secret\":\"secret\",\"pin\":\"1234\"}"); + REQUIRE(response); + REQUIRE_THAT(response->second, Equals("{\"success\":true}")); + REQUIRE(pair_promise->get_future().get() == "1234"); +} \ No newline at end of file