Skip to content

Commit

Permalink
fix: pair request, added unit tests using curl
Browse files Browse the repository at this point in the history
  • Loading branch information
ABeltramo committed Sep 25, 2024
1 parent 2755bbe commit 0919a3b
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 3 deletions.
3 changes: 1 addition & 2 deletions src/moonlight-server/api/endpoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@ void UnixSocketServer::endpoint_Events(const HTTPRequest &req, std::shared_ptr<U
}

void UnixSocketServer::endpoint_PendingPairRequest(const HTTPRequest &req, std::shared_ptr<UnixSocket> socket) {
auto res = PendingPairRequestsResponse{.success = true};
auto requests = std::vector<PairRequest>();
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<UnixSocket> socket) {
Expand Down
1 change: 1 addition & 0 deletions src/moonlight-server/state/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 5 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
129 changes: 129 additions & 0 deletions tests/testWolfAPI.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#include <api/api.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include <curl/curl.h>
#include <state/config.hpp>

using Catch::Matchers::Equals;

using namespace wolf::api;
using curl_ptr = std::unique_ptr<CURL, decltype(&curl_easy_cleanup)>;

/**
* Perform a HTTP request using curl
*/
std::optional<std::pair<long /* response_code */, std::string /* raw message */>>
req(CURL *handle,
HTTPMethod method,
std::string_view target,
std::string_view post_body = {},
const std::vector<std::string> &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<size_t (*)(char *, size_t, size_t, void *)>([](char *ptr, size_t size, size_t nmemb, void *read_buf) {
*(static_cast<std::string *>(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<events::EventBusType>();
auto config = immer::box<state::Config>(state::load_or_default("config.test.toml", event_bus));
auto app_state = immer::box<state::AppState>(state::AppState{
.config = config,
.pairing_cache = std::make_shared<immer::atom<immer::map<std::string, state::PairCache>>>(),
.pairing_atom = std::make_shared<immer::atom<immer::map<std::string, immer::box<events::PairSignal>>>>(),
.event_bus = event_bus,
.running_sessions = std::make_shared<immer::atom<immer::vector<events::StreamSession>>>()});

// 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<boost::promise<std::string>>();

// Simulate a Moonlight pairing request
app_state->pairing_atom->update([pair_promise](auto pairing_map) {
return pairing_map.set("secret",
immer::box<events::PairSignal>{
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");
}

0 comments on commit 0919a3b

Please sign in to comment.