Skip to content

Commit cf8e429

Browse files
authored
chore: Add support for server-side hooks tests. (#498)
<!-- CURSOR_SUMMARY --> > [!NOTE] > Adds a new OpenTelemetry integration for the server SDK (tracing hook) with example and tests, enables hooks in contract tests, and updates build/CI/release to support it. > > - **OpenTelemetry Integration (new package)**: > - Adds `libs/server-sdk-otel` providing `TracingHook` (`include/launchdarkly/server_side/integrations/otel/tracing_hook.hpp`, `src/tracing_hook.cpp`) with options builder and unit tests. > - CMake support with `LD_BUILD_OTEL_SUPPORT`, `LD_BUILD_OTEL_FETCH_DEPS`, `LD_OTEL_CPP_VERSION`; links against `opentelemetry-cpp::api`. > - New example `examples/hello-cpp-server-otel` demonstrating tracing hook and OTLP exporter. > - **Contract Tests (hooks)**: > - Extends data model with `hooks` configuration and related types. > - Implements `ContractTestHook` used by server test harness; registers via config and advertises `evaluation-hooks` and `track-hooks` capabilities. > - **Build/CI/Release**: > - Updates `scripts/build.sh` to build OTel targets and fetch deps when needed. > - Adds `install_curl` input to shared CI and uses it for OTel builds; sets `CMAKE_PREFIX_PATH` for CURL. > - New workflow `server-otel.yml` to build/test OTel on Linux/macOS/Windows. > - `release-please` config/manifests updated to include `libs/server-sdk-otel` (v0.1.0) and outputs; docs workflow supports `libs/server-sdk-otel`. > - **CMake/Infra**: > - Root `CMakeLists.txt` adds `LD_BUILD_OTEL_SUPPORT` option and `add_subdirectory(libs/server-sdk-otel)`; examples gated on it. > - `cmake/json.cmake` switches FetchContent name to `nlohmann_json` to avoid target duplication. > - README documents new OTel options. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e62e575. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent d12b7a0 commit cf8e429

File tree

6 files changed

+388
-1
lines changed

6 files changed

+388
-1
lines changed

contract-tests/data-model/include/data_model/data_model.hpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,36 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigTags,
113113
applicationId,
114114
applicationVersion);
115115

116+
enum class HookStage {
117+
BeforeEvaluation,
118+
AfterEvaluation,
119+
AfterTrack
120+
};
121+
122+
NLOHMANN_JSON_SERIALIZE_ENUM(HookStage,
123+
{{HookStage::BeforeEvaluation, "beforeEvaluation"},
124+
{HookStage::AfterEvaluation, "afterEvaluation"},
125+
{HookStage::AfterTrack, "afterTrack"}})
126+
127+
struct ConfigHookInstance {
128+
std::string name;
129+
std::string callbackUri;
130+
std::optional<std::unordered_map<std::string, std::unordered_map<std::string, nlohmann::json>>> data;
131+
std::optional<std::unordered_map<std::string, std::string>> errors;
132+
};
133+
134+
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigHookInstance,
135+
name,
136+
callbackUri,
137+
data,
138+
errors);
139+
140+
struct ConfigHooksParams {
141+
std::vector<ConfigHookInstance> hooks;
142+
};
143+
144+
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigHooksParams, hooks);
145+
116146
struct ConfigParams {
117147
std::string credential;
118148
std::optional<uint32_t> startWaitTimeMs;
@@ -125,6 +155,7 @@ struct ConfigParams {
125155
std::optional<ConfigTags> tags;
126156
std::optional<ConfigTLSParams> tls;
127157
std::optional<ConfigProxyParams> proxy;
158+
std::optional<ConfigHooksParams> hooks;
128159
};
129160

130161
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigParams,
@@ -138,7 +169,8 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ConfigParams,
138169
clientSide,
139170
tags,
140171
tls,
141-
proxy);
172+
proxy,
173+
hooks);
142174

143175
struct ContextSingleParams {
144176
std::optional<std::string> kind;

contract-tests/server-contract-tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ add_executable(server-tests
1616
src/session.cpp
1717
src/entity_manager.cpp
1818
src/client_entity.cpp
19+
src/contract_test_hook.cpp
1920
)
2021

2122
target_link_libraries(server-tests PRIVATE
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#pragma once
2+
3+
#include <data_model/data_model.hpp>
4+
#include <launchdarkly/server_side/hooks/hook.hpp>
5+
6+
#include <boost/asio/any_io_executor.hpp>
7+
#include <memory>
8+
#include <string>
9+
10+
/**
11+
* ContractTestHook implements the Hook interface for contract testing.
12+
*
13+
* It posts hook execution payloads to a callback URI specified in the test
14+
* configuration, allowing the test harness to verify hook behavior.
15+
*/
16+
class ContractTestHook : public launchdarkly::server_side::hooks::Hook {
17+
public:
18+
/**
19+
* Constructs a contract test hook.
20+
* @param executor IO executor for async HTTP operations.
21+
* @param config Hook configuration from the test harness.
22+
*/
23+
ContractTestHook(boost::asio::any_io_executor executor,
24+
ConfigHookInstance config);
25+
26+
~ContractTestHook() override = default;
27+
28+
[[nodiscard]] launchdarkly::server_side::hooks::HookMetadata const&
29+
Metadata() const override;
30+
31+
launchdarkly::server_side::hooks::EvaluationSeriesData BeforeEvaluation(
32+
launchdarkly::server_side::hooks::EvaluationSeriesContext const&
33+
series_context,
34+
launchdarkly::server_side::hooks::EvaluationSeriesData data) override;
35+
36+
launchdarkly::server_side::hooks::EvaluationSeriesData AfterEvaluation(
37+
launchdarkly::server_side::hooks::EvaluationSeriesContext const&
38+
series_context,
39+
launchdarkly::server_side::hooks::EvaluationSeriesData data,
40+
launchdarkly::EvaluationDetail<launchdarkly::Value> const& detail)
41+
override;
42+
43+
void AfterTrack(launchdarkly::server_side::hooks::TrackSeriesContext const&
44+
series_context) override;
45+
46+
private:
47+
/**
48+
* Posts a hook execution payload to the callback URI.
49+
* @param stage The stage being executed.
50+
* @param payload The JSON payload to send.
51+
*/
52+
void PostCallback(std::string const& stage, nlohmann::json const& payload);
53+
54+
/**
55+
* Gets configured data for a specific stage.
56+
* @param stage The stage name.
57+
* @return Optional map of key-value pairs for this stage.
58+
*/
59+
std::optional<std::unordered_map<std::string, nlohmann::json>> GetDataForStage(
60+
std::string const& stage) const;
61+
62+
/**
63+
* Gets configured error for a specific stage.
64+
* @param stage The stage name.
65+
* @return Optional error message for this stage.
66+
*/
67+
std::optional<std::string> GetErrorForStage(std::string const& stage) const;
68+
69+
boost::asio::any_io_executor executor_;
70+
ConfigHookInstance config_;
71+
launchdarkly::server_side::hooks::HookMetadata metadata_;
72+
};

0 commit comments

Comments
 (0)