From 93dabf567ca97908dc2a9d285f6f27ee13ac5e17 Mon Sep 17 00:00:00 2001 From: "sewon.jeon" Date: Tue, 24 Feb 2026 21:44:24 +0900 Subject: [PATCH 1/5] test(gateway): add unit tests for HealthHandlers Adds 14 unit tests covering the three HealthHandlers methods: - handle_health: verifies status field, timestamp, valid JSON body - handle_version_info: verifies sovd_info array, version, base_uri, vendor_info - handle_root: verifies required fields, endpoints list, capabilities, auth-disabled behavior (no auth endpoints, capabilities.authentication=false), and auth-enabled behavior (auth endpoints appear in list) Uses a null GatewayNode and null AuthManager, which is safe because none of these handler methods call ctx_.node() or ctx_.auth_manager(). Closes #177 Co-Authored-By: Claude Sonnet 4.6 --- src/ros2_medkit_gateway/CMakeLists.txt | 5 + .../test/test_health_handlers.cpp | 224 ++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 src/ros2_medkit_gateway/test/test_health_handlers.cpp diff --git a/src/ros2_medkit_gateway/CMakeLists.txt b/src/ros2_medkit_gateway/CMakeLists.txt index 984c4424..a394b241 100644 --- a/src/ros2_medkit_gateway/CMakeLists.txt +++ b/src/ros2_medkit_gateway/CMakeLists.txt @@ -366,6 +366,10 @@ if(BUILD_TESTING) ament_add_gtest(test_update_manager test/test_update_manager.cpp) target_link_libraries(test_update_manager gateway_lib) + # Add health handler tests + ament_add_gtest(test_health_handlers test/test_health_handlers.cpp) + target_link_libraries(test_health_handlers gateway_lib) + # Demo update backend plugin (.so for integration tests) add_library(test_update_backend MODULE test/demo_nodes/test_update_backend.cpp @@ -408,6 +412,7 @@ if(BUILD_TESTING) test_subscription_manager test_cyclic_subscription_handlers test_update_manager + test_health_handlers ) foreach(_target ${_test_targets}) target_compile_options(${_target} PRIVATE --coverage -O0 -g) diff --git a/src/ros2_medkit_gateway/test/test_health_handlers.cpp b/src/ros2_medkit_gateway/test/test_health_handlers.cpp new file mode 100644 index 00000000..39087ada --- /dev/null +++ b/src/ros2_medkit_gateway/test/test_health_handlers.cpp @@ -0,0 +1,224 @@ +// Copyright 2026 bburda +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include + +#include "ros2_medkit_gateway/http/handlers/health_handlers.hpp" + +using json = nlohmann::json; +using ros2_medkit_gateway::AuthConfig; +using ros2_medkit_gateway::CorsConfig; +using ros2_medkit_gateway::TlsConfig; +using ros2_medkit_gateway::handlers::HandlerContext; +using ros2_medkit_gateway::handlers::HealthHandlers; + +// HealthHandlers has no dependency on GatewayNode or AuthManager: +// - handle_health only calls HandlerContext::send_json() (static) +// - handle_version_info only calls HandlerContext::send_json() (static) +// - handle_root reads ctx_.auth_config() and ctx_.tls_config() (both disabled by default) +// All tests use a null GatewayNode and null AuthManager which is safe for these handlers. + +class HealthHandlersTest : public ::testing::Test +{ +protected: + CorsConfig cors_config_{}; + AuthConfig auth_config_{}; // enabled = false by default + TlsConfig tls_config_{}; // enabled = false by default + HandlerContext ctx_{nullptr, cors_config_, auth_config_, tls_config_, nullptr}; + HealthHandlers handlers_{ctx_}; +}; + +// --- handle_health --- + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleHealthResponseContainsStatusHealthy) +{ + httplib::Request req; + httplib::Response res; + handlers_.handle_health(req, res); + auto body = json::parse(res.body); + EXPECT_EQ(body["status"], "healthy"); +} + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleHealthResponseContainsTimestamp) +{ + httplib::Request req; + httplib::Response res; + handlers_.handle_health(req, res); + auto body = json::parse(res.body); + EXPECT_TRUE(body.contains("timestamp")); + EXPECT_TRUE(body["timestamp"].is_number()); +} + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleHealthResponseIsValidJson) +{ + httplib::Request req; + httplib::Response res; + handlers_.handle_health(req, res); + EXPECT_NO_THROW(json::parse(res.body)); +} + +// --- handle_version_info --- + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleVersionInfoContainsSovdInfoArray) +{ + httplib::Request req; + httplib::Response res; + handlers_.handle_version_info(req, res); + auto body = json::parse(res.body); + ASSERT_TRUE(body.contains("sovd_info")); + ASSERT_TRUE(body["sovd_info"].is_array()); + EXPECT_FALSE(body["sovd_info"].empty()); +} + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasVersionField) +{ + httplib::Request req; + httplib::Response res; + handlers_.handle_version_info(req, res); + auto body = json::parse(res.body); + auto & entry = body["sovd_info"][0]; + EXPECT_TRUE(entry.contains("version")); + EXPECT_TRUE(entry["version"].is_string()); +} + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasBaseUri) +{ + httplib::Request req; + httplib::Response res; + handlers_.handle_version_info(req, res); + auto body = json::parse(res.body); + auto & entry = body["sovd_info"][0]; + EXPECT_TRUE(entry.contains("base_uri")); +} + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasVendorInfo) +{ + httplib::Request req; + httplib::Response res; + handlers_.handle_version_info(req, res); + auto body = json::parse(res.body); + auto & entry = body["sovd_info"][0]; + EXPECT_TRUE(entry.contains("vendor_info")); + EXPECT_TRUE(entry["vendor_info"].contains("name")); + EXPECT_EQ(entry["vendor_info"]["name"], "ros2_medkit"); +} + +// --- handle_root --- + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleRootResponseContainsRequiredTopLevelFields) +{ + httplib::Request req; + httplib::Response res; + handlers_.handle_root(req, res); + auto body = json::parse(res.body); + EXPECT_TRUE(body.contains("name")); + EXPECT_TRUE(body.contains("version")); + EXPECT_TRUE(body.contains("api_base")); + EXPECT_TRUE(body.contains("endpoints")); + EXPECT_TRUE(body.contains("capabilities")); +} + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleRootEndpointsIsNonEmptyArray) +{ + httplib::Request req; + httplib::Response res; + handlers_.handle_root(req, res); + auto body = json::parse(res.body); + ASSERT_TRUE(body["endpoints"].is_array()); + EXPECT_FALSE(body["endpoints"].empty()); +} + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleRootCapabilitiesContainsDiscovery) +{ + httplib::Request req; + httplib::Response res; + handlers_.handle_root(req, res); + auto body = json::parse(res.body); + auto & caps = body["capabilities"]; + EXPECT_TRUE(caps.contains("discovery")); + EXPECT_TRUE(caps["discovery"].get()); +} + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleRootAuthDisabledNoAuthEndpoints) +{ + // With auth disabled (default), auth endpoints must not appear in the list + httplib::Request req; + httplib::Response res; + handlers_.handle_root(req, res); + auto body = json::parse(res.body); + for (const auto & ep : body["endpoints"]) { + EXPECT_EQ(ep.get().find("/auth/"), std::string::npos) + << "Unexpected auth endpoint when auth is disabled: " << ep; + } +} + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleRootCapabilitiesAuthDisabled) +{ + httplib::Request req; + httplib::Response res; + handlers_.handle_root(req, res); + auto body = json::parse(res.body); + EXPECT_FALSE(body["capabilities"]["authentication"].get()); +} + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleRootCapabilitiesTlsDisabled) +{ + httplib::Request req; + httplib::Response res; + handlers_.handle_root(req, res); + auto body = json::parse(res.body); + EXPECT_FALSE(body["capabilities"]["tls"].get()); +} + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleRootAuthEnabledAddsAuthEndpoints) +{ + AuthConfig auth_enabled{}; + auth_enabled.enabled = true; + TlsConfig tls{}; + CorsConfig cors{}; + HandlerContext ctx_auth(nullptr, cors, auth_enabled, tls, nullptr); + HealthHandlers handlers_auth(ctx_auth); + + httplib::Request req; + httplib::Response res; + handlers_auth.handle_root(req, res); + auto body = json::parse(res.body); + + bool has_auth_endpoint = false; + for (const auto & ep : body["endpoints"]) { + if (ep.get().find("/auth/") != std::string::npos) { + has_auth_endpoint = true; + break; + } + } + EXPECT_TRUE(has_auth_endpoint); + EXPECT_TRUE(body["capabilities"]["authentication"].get()); +} From 60068d8fe8b2960df9eb15151a75d5137cea040b Mon Sep 17 00:00:00 2001 From: "sewon.jeon" Date: Tue, 24 Feb 2026 22:18:37 +0900 Subject: [PATCH 2/5] test(gateway): expand health handlers coverage --- .../test/test_health_handlers.cpp | 91 ++++++++++++------- 1 file changed, 60 insertions(+), 31 deletions(-) diff --git a/src/ros2_medkit_gateway/test/test_health_handlers.cpp b/src/ros2_medkit_gateway/test/test_health_handlers.cpp index 39087ada..bd12fbea 100644 --- a/src/ros2_medkit_gateway/test/test_health_handlers.cpp +++ b/src/ros2_medkit_gateway/test/test_health_handlers.cpp @@ -32,9 +32,8 @@ using ros2_medkit_gateway::handlers::HealthHandlers; // - handle_root reads ctx_.auth_config() and ctx_.tls_config() (both disabled by default) // All tests use a null GatewayNode and null AuthManager which is safe for these handlers. -class HealthHandlersTest : public ::testing::Test -{ -protected: +class HealthHandlersTest : public ::testing::Test { + protected: CorsConfig cors_config_{}; AuthConfig auth_config_{}; // enabled = false by default TlsConfig tls_config_{}; // enabled = false by default @@ -45,8 +44,7 @@ class HealthHandlersTest : public ::testing::Test // --- handle_health --- // @verifies REQ_INTEROP_001 -TEST_F(HealthHandlersTest, HandleHealthResponseContainsStatusHealthy) -{ +TEST_F(HealthHandlersTest, HandleHealthResponseContainsStatusHealthy) { httplib::Request req; httplib::Response res; handlers_.handle_health(req, res); @@ -55,8 +53,7 @@ TEST_F(HealthHandlersTest, HandleHealthResponseContainsStatusHealthy) } // @verifies REQ_INTEROP_001 -TEST_F(HealthHandlersTest, HandleHealthResponseContainsTimestamp) -{ +TEST_F(HealthHandlersTest, HandleHealthResponseContainsTimestamp) { httplib::Request req; httplib::Response res; handlers_.handle_health(req, res); @@ -66,8 +63,7 @@ TEST_F(HealthHandlersTest, HandleHealthResponseContainsTimestamp) } // @verifies REQ_INTEROP_001 -TEST_F(HealthHandlersTest, HandleHealthResponseIsValidJson) -{ +TEST_F(HealthHandlersTest, HandleHealthResponseIsValidJson) { httplib::Request req; httplib::Response res; handlers_.handle_health(req, res); @@ -77,8 +73,7 @@ TEST_F(HealthHandlersTest, HandleHealthResponseIsValidJson) // --- handle_version_info --- // @verifies REQ_INTEROP_001 -TEST_F(HealthHandlersTest, HandleVersionInfoContainsSovdInfoArray) -{ +TEST_F(HealthHandlersTest, HandleVersionInfoContainsSovdInfoArray) { httplib::Request req; httplib::Response res; handlers_.handle_version_info(req, res); @@ -89,8 +84,7 @@ TEST_F(HealthHandlersTest, HandleVersionInfoContainsSovdInfoArray) } // @verifies REQ_INTEROP_001 -TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasVersionField) -{ +TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasVersionField) { httplib::Request req; httplib::Response res; handlers_.handle_version_info(req, res); @@ -101,8 +95,7 @@ TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasVersionField) } // @verifies REQ_INTEROP_001 -TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasBaseUri) -{ +TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasBaseUri) { httplib::Request req; httplib::Response res; handlers_.handle_version_info(req, res); @@ -112,8 +105,7 @@ TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasBaseUri) } // @verifies REQ_INTEROP_001 -TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasVendorInfo) -{ +TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasVendorInfo) { httplib::Request req; httplib::Response res; handlers_.handle_version_info(req, res); @@ -127,8 +119,7 @@ TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasVendorInfo) // --- handle_root --- // @verifies REQ_INTEROP_001 -TEST_F(HealthHandlersTest, HandleRootResponseContainsRequiredTopLevelFields) -{ +TEST_F(HealthHandlersTest, HandleRootResponseContainsRequiredTopLevelFields) { httplib::Request req; httplib::Response res; handlers_.handle_root(req, res); @@ -141,8 +132,7 @@ TEST_F(HealthHandlersTest, HandleRootResponseContainsRequiredTopLevelFields) } // @verifies REQ_INTEROP_001 -TEST_F(HealthHandlersTest, HandleRootEndpointsIsNonEmptyArray) -{ +TEST_F(HealthHandlersTest, HandleRootEndpointsIsNonEmptyArray) { httplib::Request req; httplib::Response res; handlers_.handle_root(req, res); @@ -152,8 +142,7 @@ TEST_F(HealthHandlersTest, HandleRootEndpointsIsNonEmptyArray) } // @verifies REQ_INTEROP_001 -TEST_F(HealthHandlersTest, HandleRootCapabilitiesContainsDiscovery) -{ +TEST_F(HealthHandlersTest, HandleRootCapabilitiesContainsDiscovery) { httplib::Request req; httplib::Response res; handlers_.handle_root(req, res); @@ -164,8 +153,7 @@ TEST_F(HealthHandlersTest, HandleRootCapabilitiesContainsDiscovery) } // @verifies REQ_INTEROP_001 -TEST_F(HealthHandlersTest, HandleRootAuthDisabledNoAuthEndpoints) -{ +TEST_F(HealthHandlersTest, HandleRootAuthDisabledNoAuthEndpoints) { // With auth disabled (default), auth endpoints must not appear in the list httplib::Request req; httplib::Response res; @@ -178,8 +166,7 @@ TEST_F(HealthHandlersTest, HandleRootAuthDisabledNoAuthEndpoints) } // @verifies REQ_INTEROP_001 -TEST_F(HealthHandlersTest, HandleRootCapabilitiesAuthDisabled) -{ +TEST_F(HealthHandlersTest, HandleRootCapabilitiesAuthDisabled) { httplib::Request req; httplib::Response res; handlers_.handle_root(req, res); @@ -188,18 +175,17 @@ TEST_F(HealthHandlersTest, HandleRootCapabilitiesAuthDisabled) } // @verifies REQ_INTEROP_001 -TEST_F(HealthHandlersTest, HandleRootCapabilitiesTlsDisabled) -{ +TEST_F(HealthHandlersTest, HandleRootCapabilitiesTlsDisabled) { httplib::Request req; httplib::Response res; handlers_.handle_root(req, res); auto body = json::parse(res.body); EXPECT_FALSE(body["capabilities"]["tls"].get()); + EXPECT_FALSE(body.contains("tls")); } // @verifies REQ_INTEROP_001 -TEST_F(HealthHandlersTest, HandleRootAuthEnabledAddsAuthEndpoints) -{ +TEST_F(HealthHandlersTest, HandleRootAuthEnabledAddsAuthEndpoints) { AuthConfig auth_enabled{}; auth_enabled.enabled = true; TlsConfig tls{}; @@ -222,3 +208,46 @@ TEST_F(HealthHandlersTest, HandleRootAuthEnabledAddsAuthEndpoints) EXPECT_TRUE(has_auth_endpoint); EXPECT_TRUE(body["capabilities"]["authentication"].get()); } + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleRootAuthEnabledIncludesAuthMetadataBlock) { + AuthConfig auth_enabled{}; + auth_enabled.enabled = true; + auth_enabled.require_auth_for = ros2_medkit_gateway::AuthRequirement::ALL; + auth_enabled.jwt_algorithm = ros2_medkit_gateway::JwtAlgorithm::HS256; + TlsConfig tls{}; + CorsConfig cors{}; + HandlerContext ctx_auth(nullptr, cors, auth_enabled, tls, nullptr); + HealthHandlers handlers_auth(ctx_auth); + + httplib::Request req; + httplib::Response res; + handlers_auth.handle_root(req, res); + auto body = json::parse(res.body); + + ASSERT_TRUE(body.contains("auth")); + EXPECT_TRUE(body["auth"]["enabled"].get()); + EXPECT_EQ(body["auth"]["algorithm"], "HS256"); + EXPECT_EQ(body["auth"]["require_auth_for"], "all"); +} + +// @verifies REQ_INTEROP_001 +TEST_F(HealthHandlersTest, HandleRootTlsEnabledIncludesTlsMetadataBlock) { + AuthConfig auth{}; + TlsConfig tls_enabled{}; + tls_enabled.enabled = true; + tls_enabled.min_version = "1.3"; + CorsConfig cors{}; + HandlerContext ctx_tls(nullptr, cors, auth, tls_enabled, nullptr); + HealthHandlers handlers_tls(ctx_tls); + + httplib::Request req; + httplib::Response res; + handlers_tls.handle_root(req, res); + auto body = json::parse(res.body); + + ASSERT_TRUE(body.contains("tls")); + EXPECT_TRUE(body["tls"]["enabled"].get()); + EXPECT_EQ(body["tls"]["min_version"], "1.3"); + EXPECT_TRUE(body["capabilities"]["tls"].get()); +} From 0e1080853e5d9ce87dc53d50b9d369a20cec4b66 Mon Sep 17 00:00:00 2001 From: sewon jeon Date: Tue, 24 Feb 2026 23:10:13 +0900 Subject: [PATCH 3/5] Update src/ros2_medkit_gateway/test/test_health_handlers.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/ros2_medkit_gateway/test/test_health_handlers.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ros2_medkit_gateway/test/test_health_handlers.cpp b/src/ros2_medkit_gateway/test/test_health_handlers.cpp index bd12fbea..e1b5d063 100644 --- a/src/ros2_medkit_gateway/test/test_health_handlers.cpp +++ b/src/ros2_medkit_gateway/test/test_health_handlers.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "ros2_medkit_gateway/http/handlers/health_handlers.hpp" From 425c3291fcfabdbacbfd0d28d272b83dfd71daba Mon Sep 17 00:00:00 2001 From: "sewon.jeon" Date: Tue, 24 Feb 2026 23:42:09 +0900 Subject: [PATCH 4/5] test(gateway): fix clang-format include order in health handlers test --- src/ros2_medkit_gateway/test/test_health_handlers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ros2_medkit_gateway/test/test_health_handlers.cpp b/src/ros2_medkit_gateway/test/test_health_handlers.cpp index e1b5d063..e7aa1570 100644 --- a/src/ros2_medkit_gateway/test/test_health_handlers.cpp +++ b/src/ros2_medkit_gateway/test/test_health_handlers.cpp @@ -14,9 +14,9 @@ #include +#include #include #include -#include #include "ros2_medkit_gateway/http/handlers/health_handlers.hpp" From e717fb99aa97b30de33f4d09a67eb031e61e4feb Mon Sep 17 00:00:00 2001 From: sewon jeon Date: Wed, 25 Feb 2026 09:38:18 +0900 Subject: [PATCH 5/5] test(gateway): fix PR #232 code review comments - Fixed @verifies tags for root and health handlers - Check non-empty value of response fields - Move req/res to fixture members - Added make_context helper for test fixture --- .../test/test_health_handlers.cpp | 140 +++++++----------- 1 file changed, 55 insertions(+), 85 deletions(-) diff --git a/src/ros2_medkit_gateway/test/test_health_handlers.cpp b/src/ros2_medkit_gateway/test/test_health_handlers.cpp index e7aa1570..f92a8d68 100644 --- a/src/ros2_medkit_gateway/test/test_health_handlers.cpp +++ b/src/ros2_medkit_gateway/test/test_health_handlers.cpp @@ -40,45 +40,41 @@ class HealthHandlersTest : public ::testing::Test { TlsConfig tls_config_{}; // enabled = false by default HandlerContext ctx_{nullptr, cors_config_, auth_config_, tls_config_, nullptr}; HealthHandlers handlers_{ctx_}; + + httplib::Request req_; + httplib::Response res_; + + HandlerContext make_context(const AuthConfig & auth, const TlsConfig & tls) { + return HandlerContext(nullptr, cors_config_, auth, tls, nullptr); + } }; // --- handle_health --- -// @verifies REQ_INTEROP_001 TEST_F(HealthHandlersTest, HandleHealthResponseContainsStatusHealthy) { - httplib::Request req; - httplib::Response res; - handlers_.handle_health(req, res); - auto body = json::parse(res.body); + handlers_.handle_health(req_, res_); + auto body = json::parse(res_.body); EXPECT_EQ(body["status"], "healthy"); } -// @verifies REQ_INTEROP_001 TEST_F(HealthHandlersTest, HandleHealthResponseContainsTimestamp) { - httplib::Request req; - httplib::Response res; - handlers_.handle_health(req, res); - auto body = json::parse(res.body); + handlers_.handle_health(req_, res_); + auto body = json::parse(res_.body); EXPECT_TRUE(body.contains("timestamp")); EXPECT_TRUE(body["timestamp"].is_number()); } -// @verifies REQ_INTEROP_001 TEST_F(HealthHandlersTest, HandleHealthResponseIsValidJson) { - httplib::Request req; - httplib::Response res; - handlers_.handle_health(req, res); - EXPECT_NO_THROW(json::parse(res.body)); + handlers_.handle_health(req_, res_); + EXPECT_NO_THROW(json::parse(res_.body)); } // --- handle_version_info --- // @verifies REQ_INTEROP_001 TEST_F(HealthHandlersTest, HandleVersionInfoContainsSovdInfoArray) { - httplib::Request req; - httplib::Response res; - handlers_.handle_version_info(req, res); - auto body = json::parse(res.body); + handlers_.handle_version_info(req_, res_); + auto body = json::parse(res_.body); ASSERT_TRUE(body.contains("sovd_info")); ASSERT_TRUE(body["sovd_info"].is_array()); EXPECT_FALSE(body["sovd_info"].empty()); @@ -86,31 +82,26 @@ TEST_F(HealthHandlersTest, HandleVersionInfoContainsSovdInfoArray) { // @verifies REQ_INTEROP_001 TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasVersionField) { - httplib::Request req; - httplib::Response res; - handlers_.handle_version_info(req, res); - auto body = json::parse(res.body); + handlers_.handle_version_info(req_, res_); + auto body = json::parse(res_.body); auto & entry = body["sovd_info"][0]; EXPECT_TRUE(entry.contains("version")); EXPECT_TRUE(entry["version"].is_string()); + EXPECT_FALSE(entry["version"].get().empty()); } // @verifies REQ_INTEROP_001 TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasBaseUri) { - httplib::Request req; - httplib::Response res; - handlers_.handle_version_info(req, res); - auto body = json::parse(res.body); + handlers_.handle_version_info(req_, res_); + auto body = json::parse(res_.body); auto & entry = body["sovd_info"][0]; EXPECT_TRUE(entry.contains("base_uri")); } // @verifies REQ_INTEROP_001 TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasVendorInfo) { - httplib::Request req; - httplib::Response res; - handlers_.handle_version_info(req, res); - auto body = json::parse(res.body); + handlers_.handle_version_info(req_, res_); + auto body = json::parse(res_.body); auto & entry = body["sovd_info"][0]; EXPECT_TRUE(entry.contains("vendor_info")); EXPECT_TRUE(entry["vendor_info"].contains("name")); @@ -119,85 +110,72 @@ TEST_F(HealthHandlersTest, HandleVersionInfoSovdEntryHasVendorInfo) { // --- handle_root --- -// @verifies REQ_INTEROP_001 +// @verifies REQ_INTEROP_010 TEST_F(HealthHandlersTest, HandleRootResponseContainsRequiredTopLevelFields) { - httplib::Request req; - httplib::Response res; - handlers_.handle_root(req, res); - auto body = json::parse(res.body); + handlers_.handle_root(req_, res_); + auto body = json::parse(res_.body); EXPECT_TRUE(body.contains("name")); + EXPECT_FALSE(body["name"].get().empty()); EXPECT_TRUE(body.contains("version")); + EXPECT_FALSE(body["version"].get().empty()); EXPECT_TRUE(body.contains("api_base")); + EXPECT_FALSE(body["api_base"].get().empty()); EXPECT_TRUE(body.contains("endpoints")); EXPECT_TRUE(body.contains("capabilities")); } -// @verifies REQ_INTEROP_001 +// @verifies REQ_INTEROP_010 TEST_F(HealthHandlersTest, HandleRootEndpointsIsNonEmptyArray) { - httplib::Request req; - httplib::Response res; - handlers_.handle_root(req, res); - auto body = json::parse(res.body); + handlers_.handle_root(req_, res_); + auto body = json::parse(res_.body); ASSERT_TRUE(body["endpoints"].is_array()); EXPECT_FALSE(body["endpoints"].empty()); } -// @verifies REQ_INTEROP_001 +// @verifies REQ_INTEROP_010 TEST_F(HealthHandlersTest, HandleRootCapabilitiesContainsDiscovery) { - httplib::Request req; - httplib::Response res; - handlers_.handle_root(req, res); - auto body = json::parse(res.body); + handlers_.handle_root(req_, res_); + auto body = json::parse(res_.body); auto & caps = body["capabilities"]; EXPECT_TRUE(caps.contains("discovery")); EXPECT_TRUE(caps["discovery"].get()); } -// @verifies REQ_INTEROP_001 +// @verifies REQ_INTEROP_010 TEST_F(HealthHandlersTest, HandleRootAuthDisabledNoAuthEndpoints) { // With auth disabled (default), auth endpoints must not appear in the list - httplib::Request req; - httplib::Response res; - handlers_.handle_root(req, res); - auto body = json::parse(res.body); + handlers_.handle_root(req_, res_); + auto body = json::parse(res_.body); for (const auto & ep : body["endpoints"]) { EXPECT_EQ(ep.get().find("/auth/"), std::string::npos) << "Unexpected auth endpoint when auth is disabled: " << ep; } } -// @verifies REQ_INTEROP_001 +// @verifies REQ_INTEROP_010 TEST_F(HealthHandlersTest, HandleRootCapabilitiesAuthDisabled) { - httplib::Request req; - httplib::Response res; - handlers_.handle_root(req, res); - auto body = json::parse(res.body); + handlers_.handle_root(req_, res_); + auto body = json::parse(res_.body); EXPECT_FALSE(body["capabilities"]["authentication"].get()); } -// @verifies REQ_INTEROP_001 +// @verifies REQ_INTEROP_010 TEST_F(HealthHandlersTest, HandleRootCapabilitiesTlsDisabled) { - httplib::Request req; - httplib::Response res; - handlers_.handle_root(req, res); - auto body = json::parse(res.body); + handlers_.handle_root(req_, res_); + auto body = json::parse(res_.body); EXPECT_FALSE(body["capabilities"]["tls"].get()); EXPECT_FALSE(body.contains("tls")); } -// @verifies REQ_INTEROP_001 +// @verifies REQ_INTEROP_010 TEST_F(HealthHandlersTest, HandleRootAuthEnabledAddsAuthEndpoints) { AuthConfig auth_enabled{}; auth_enabled.enabled = true; - TlsConfig tls{}; - CorsConfig cors{}; - HandlerContext ctx_auth(nullptr, cors, auth_enabled, tls, nullptr); + auto ctx_auth = make_context(auth_enabled, tls_config_); HealthHandlers handlers_auth(ctx_auth); - httplib::Request req; - httplib::Response res; - handlers_auth.handle_root(req, res); - auto body = json::parse(res.body); + handlers_auth.handle_root(req_, res_); + auto body = json::parse(res_.body); bool has_auth_endpoint = false; for (const auto & ep : body["endpoints"]) { @@ -210,21 +188,17 @@ TEST_F(HealthHandlersTest, HandleRootAuthEnabledAddsAuthEndpoints) { EXPECT_TRUE(body["capabilities"]["authentication"].get()); } -// @verifies REQ_INTEROP_001 +// @verifies REQ_INTEROP_010 TEST_F(HealthHandlersTest, HandleRootAuthEnabledIncludesAuthMetadataBlock) { AuthConfig auth_enabled{}; auth_enabled.enabled = true; auth_enabled.require_auth_for = ros2_medkit_gateway::AuthRequirement::ALL; auth_enabled.jwt_algorithm = ros2_medkit_gateway::JwtAlgorithm::HS256; - TlsConfig tls{}; - CorsConfig cors{}; - HandlerContext ctx_auth(nullptr, cors, auth_enabled, tls, nullptr); + auto ctx_auth = make_context(auth_enabled, tls_config_); HealthHandlers handlers_auth(ctx_auth); - httplib::Request req; - httplib::Response res; - handlers_auth.handle_root(req, res); - auto body = json::parse(res.body); + handlers_auth.handle_root(req_, res_); + auto body = json::parse(res_.body); ASSERT_TRUE(body.contains("auth")); EXPECT_TRUE(body["auth"]["enabled"].get()); @@ -232,20 +206,16 @@ TEST_F(HealthHandlersTest, HandleRootAuthEnabledIncludesAuthMetadataBlock) { EXPECT_EQ(body["auth"]["require_auth_for"], "all"); } -// @verifies REQ_INTEROP_001 +// @verifies REQ_INTEROP_010 TEST_F(HealthHandlersTest, HandleRootTlsEnabledIncludesTlsMetadataBlock) { - AuthConfig auth{}; TlsConfig tls_enabled{}; tls_enabled.enabled = true; tls_enabled.min_version = "1.3"; - CorsConfig cors{}; - HandlerContext ctx_tls(nullptr, cors, auth, tls_enabled, nullptr); + auto ctx_tls = make_context(auth_config_, tls_enabled); HealthHandlers handlers_tls(ctx_tls); - httplib::Request req; - httplib::Response res; - handlers_tls.handle_root(req, res); - auto body = json::parse(res.body); + handlers_tls.handle_root(req_, res_); + auto body = json::parse(res_.body); ASSERT_TRUE(body.contains("tls")); EXPECT_TRUE(body["tls"]["enabled"].get());