diff --git a/contract-tests/server-contract-tests/src/contract_test_hook.cpp b/contract-tests/server-contract-tests/src/contract_test_hook.cpp index 6d2f93f33..c143bb9f1 100644 --- a/contract-tests/server-contract-tests/src/contract_test_hook.cpp +++ b/contract-tests/server-contract-tests/src/contract_test_hook.cpp @@ -1,12 +1,12 @@ #include "contract_test_hook.hpp" +#include +#include #include #include -#include -#include -#include #include +#include #include #include #include @@ -55,10 +55,8 @@ std::optional ContractTestHook::GetErrorForStage( void ContractTestHook::PostCallback(std::string const& stage, nlohmann::json const& payload) { - // Parse the callback URI auto uri_result = boost::urls::parse_uri(config_.callbackUri); if (!uri_result) { - // Invalid URI, skip callback return; } @@ -70,57 +68,44 @@ void ContractTestHook::PostCallback(std::string const& stage, target = "/"; } - std::string body = payload.dump(); - - // Create a shared connection and post asynchronously without blocking - // This uses fire-and-forget semantics - we don't wait for response - auto conn = std::make_shared(executor_); - auto resolver = std::make_shared(executor_); - - // Capture everything needed by value in shared_ptrs - auto req = std::make_shared>(); - req->method(http::verb::post); - req->target(target); - req->version(11); - req->set(http::field::host, host); - req->set(http::field::user_agent, "cpp-server-sdk-contract-tests"); - req->set(http::field::content_type, "application/json"); - req->body() = body; - req->prepare_payload(); - - // Start async resolution - resolver->async_resolve(host, port, - [conn, resolver, req](beast::error_code ec, tcp::resolver::results_type results) { - if (ec) return; // Silently ignore resolution errors - - // Connect asynchronously - conn->async_connect(results, - [conn, resolver, req](beast::error_code ec, tcp::resolver::results_type::endpoint_type) { - if (ec) return; // Silently ignore connection errors - - // Write request asynchronously - http::async_write(*conn, *req, - [conn, resolver, req](beast::error_code ec, std::size_t) { - if (ec) return; // Silently ignore write errors - - // Read response asynchronously (but don't wait for result) - auto res = std::make_shared>(); - auto buffer = std::make_shared(); - http::async_read(*conn, *buffer, *res, - [conn, resolver, req, res, buffer](beast::error_code ec, std::size_t) { - // Close connection gracefully - conn->socket().shutdown(tcp::socket::shutdown_both, ec); - // Cleanup happens automatically via shared_ptr - }); - }); - }); - }); + // Send synchronously so callbacks from sequential hook invocations arrive + // at the harness in the order the SDK invoked them. + beast::error_code ec; + tcp::resolver resolver(executor_); + auto results = resolver.resolve(host, port, ec); + if (ec) { + return; + } + + beast::tcp_stream stream(executor_); + stream.connect(results, ec); + if (ec) { + return; + } + + http::request req{http::verb::post, target, 11}; + req.set(http::field::host, host); + req.set(http::field::user_agent, "cpp-server-sdk-contract-tests"); + req.set(http::field::content_type, "application/json"); + req.body() = payload.dump(); + req.prepare_payload(); + + http::write(stream, req, ec); + if (ec) { + return; + } + + beast::flat_buffer buffer; + http::response res; + http::read(stream, buffer, res, ec); + + beast::error_code ignore_ec; + stream.socket().shutdown(tcp::socket::shutdown_both, ignore_ec); } EvaluationSeriesData ContractTestHook::BeforeEvaluation( EvaluationSeriesContext const& series_context, EvaluationSeriesData data) { - // Check if we should throw an error for this stage auto error = GetErrorForStage("beforeEvaluation"); if (error) { @@ -149,12 +134,10 @@ EvaluationSeriesData ContractTestHook::BeforeEvaluation( // EvaluationSeriesContext nlohmann::json ctx; ctx["flagKey"] = std::string(series_context.FlagKey()); - ctx["context"] = nlohmann::json::parse( - boost::json::serialize( - boost::json::value_from(series_context.EvaluationContext()))); - ctx["defaultValue"] = nlohmann::json::parse( - boost::json::serialize( - boost::json::value_from(series_context.DefaultValue()))); + ctx["context"] = nlohmann::json::parse(boost::json::serialize( + boost::json::value_from(series_context.EvaluationContext()))); + ctx["defaultValue"] = nlohmann::json::parse(boost::json::serialize( + boost::json::value_from(series_context.DefaultValue()))); ctx["method"] = std::string(series_context.Method()); if (series_context.EnvironmentId()) { ctx["environmentId"] = std::string(*series_context.EnvironmentId()); @@ -181,7 +164,6 @@ EvaluationSeriesData ContractTestHook::AfterEvaluation( EvaluationSeriesContext const& series_context, EvaluationSeriesData data, launchdarkly::EvaluationDetail const& detail) { - // Check if we should throw an error for this stage auto error = GetErrorForStage("afterEvaluation"); if (error) { @@ -195,12 +177,10 @@ EvaluationSeriesData ContractTestHook::AfterEvaluation( // EvaluationSeriesContext nlohmann::json ctx; ctx["flagKey"] = std::string(series_context.FlagKey()); - ctx["context"] = nlohmann::json::parse( - boost::json::serialize( - boost::json::value_from(series_context.EvaluationContext()))); - ctx["defaultValue"] = nlohmann::json::parse( - boost::json::serialize( - boost::json::value_from(series_context.DefaultValue()))); + ctx["context"] = nlohmann::json::parse(boost::json::serialize( + boost::json::value_from(series_context.EvaluationContext()))); + ctx["defaultValue"] = nlohmann::json::parse(boost::json::serialize( + boost::json::value_from(series_context.DefaultValue()))); ctx["method"] = std::string(series_context.Method()); if (series_context.EnvironmentId()) { ctx["environmentId"] = std::string(*series_context.EnvironmentId()); @@ -236,9 +216,7 @@ EvaluationSeriesData ContractTestHook::AfterEvaluation( return data; } -void ContractTestHook::AfterTrack( - TrackSeriesContext const& series_context) { - +void ContractTestHook::AfterTrack(TrackSeriesContext const& series_context) { // Check if we should throw an error for this stage auto error = GetErrorForStage("afterTrack"); if (error) { @@ -251,17 +229,15 @@ void ContractTestHook::AfterTrack( // TrackSeriesContext nlohmann::json ctx; - ctx["context"] = nlohmann::json::parse( - boost::json::serialize( - boost::json::value_from(series_context.TrackContext()))); + ctx["context"] = nlohmann::json::parse(boost::json::serialize( + boost::json::value_from(series_context.TrackContext()))); ctx["key"] = std::string(series_context.Key()); if (series_context.MetricValue()) { ctx["metricValue"] = *series_context.MetricValue(); } if (series_context.Data()) { - ctx["data"] = nlohmann::json::parse( - boost::json::serialize( - boost::json::value_from(series_context.Data()->get()))); + ctx["data"] = nlohmann::json::parse(boost::json::serialize( + boost::json::value_from(series_context.Data()->get()))); } if (series_context.EnvironmentId()) { ctx["environmentId"] = std::string(*series_context.EnvironmentId());