// SPDX-License-Identifier: GPL-3.0-or-later #include #include #include #include #include #include #include #include #include using namespace yams::daemon; namespace { class StatusOnlyProcessor final : public RequestProcessor { public: boost::asio::awaitable process(const Request& request) override { if (!std::holds_alternative(request)) { co_return Response{std::in_place_type, yams::daemon::dispatch::makeErrorResponse( yams::ErrorCode::InvalidArgument, "unexpected request type")}; } StatusResponse response{}; response.running = true; response.ready = true; response.version = "test"; response.overallStatus = "ready"; co_return Response{std::in_place_type, std::move(response)}; } }; Message read_message(boost::asio::local::stream_protocol::socket& socket, MessageFramer& framer) { std::array header_buf{}; boost::asio::read(socket, boost::asio::buffer(header_buf)); auto header_result = framer.parse_header(std::span(header_buf.data(), header_buf.size())); REQUIRE(header_result); std::vector frame(header_buf.begin(), header_buf.end()); const auto payload_size = header_result.value().payload_size; if (payload_size > 0) { std::vector payload(payload_size); boost::asio::read(socket, boost::asio::buffer(payload)); frame.insert(frame.end(), payload.begin(), payload.end()); } auto message_result = framer.parse_frame(frame); REQUIRE(message_result); return std::move(message_result.value()); } } // namespace TEST_CASE("RequestHandler: admission rejection keeps persistent session reusable", "[daemon][ipc][admission][reuse]") { #ifdef _WIN32 SKIP("Unix domain socket tests skipped on Windows"); #endif boost::asio::io_context io; boost::asio::local::stream_protocol::socket client_sock(io); boost::asio::local::stream_protocol::socket server_sock(io); boost::asio::local::connect_pair(client_sock, server_sock); RequestHandler::Config cfg; cfg.enable_multiplexing = false; cfg.enable_streaming = false; cfg.admission_control = [](const Request& request) -> std::optional { if (std::holds_alternative(request)) { return RequestHandler::Config::AdmissionDecision{ .code = yams::ErrorCode::ResourceExhausted, .message = "busy", .retry = ErrorResponse::RetryInfo{125, "overload"}, }; } return std::nullopt; }; auto processor = std::make_shared(); auto handler = std::make_shared(processor, cfg); yams::compat::stop_source stop_source; auto server_sock_ptr = std::make_shared(std::move(server_sock)); boost::asio::co_spawn( io, [handler, server_sock_ptr, token = stop_source.get_token()]() -> boost::asio::awaitable { co_await handler->handle_connection(server_sock_ptr, token, 1); co_return; }, boost::asio::detached); auto work = boost::asio::make_work_guard(io); std::thread io_thread([&io]() { io.run(); }); MessageFramer framer(1024 * 1024); Message rejected_request; rejected_request.version = 1; rejected_request.requestId = 10; rejected_request.payload = Request{std::in_place_type, PingRequest{}}; std::vector rejected_frame; REQUIRE(framer.frame_message_into(rejected_request, rejected_frame)); boost::asio::write(client_sock, boost::asio::buffer(rejected_frame)); auto rejected_response = read_message(client_sock, framer); REQUIRE(rejected_response.requestId == 10); auto* rejected_payload = std::get_if(&rejected_response.payload); REQUIRE(rejected_payload != nullptr); auto* rejected_error = std::get_if(rejected_payload); REQUIRE(rejected_error != nullptr); REQUIRE(rejected_error->code == yams::ErrorCode::ResourceExhausted); REQUIRE(rejected_error->message == "busy"); REQUIRE(rejected_error->retry.has_value()); CHECK(rejected_error->retry->retryAfterMs == 125); CHECK(rejected_error->retry->reason == "overload"); Message accepted_request; accepted_request.version = 1; accepted_request.requestId = 11; accepted_request.payload = Request{std::in_place_type, StatusRequest{.detailed = false}}; std::vector accepted_frame; REQUIRE(framer.frame_message_into(accepted_request, accepted_frame)); boost::asio::write(client_sock, boost::asio::buffer(accepted_frame)); auto accepted_response = read_message(client_sock, framer); REQUIRE(accepted_response.requestId == 11); auto* accepted_payload = std::get_if(&accepted_response.payload); REQUIRE(accepted_payload != nullptr); auto* status = std::get_if(accepted_payload); REQUIRE(status != nullptr); REQUIRE(status->ready); REQUIRE(status->overallStatus == "ready"); stop_source.request_stop(); { boost::system::error_code ec; client_sock.close(ec); } work.reset(); io_thread.join(); }