Skip to content

Commit 3f57e81

Browse files
committed
Add exception safety tests for C API filter chain (#102)
Create comprehensive test suite to verify exception safety in filter chain C API functions. Tests ensure no exceptions cross FFI boundary and appropriate error codes are returned.
1 parent de9adfc commit 3f57e81

File tree

2 files changed

+240
-0
lines changed

2 files changed

+240
-0
lines changed

tests/CMakeLists.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ if(BUILD_C_API)
9696
add_executable(test_c_api_json c_api/test_c_api_json.cc)
9797
add_executable(test_mcp_c_filter_api c_api/test_mcp_c_filter_api.cc)
9898
add_executable(test_mcp_c_filter_chain c_api/test_mcp_c_filter_chain.cc)
99+
add_executable(test_mcp_c_filter_chain_exception_safety c_api/test_mcp_c_filter_chain_exception_safety.cc)
99100
add_executable(test_mcp_c_filter_buffer c_api/test_mcp_c_filter_buffer.cc)
100101
add_executable(test_mcp_c_logging_api c_api/test_mcp_c_logging_api.cc)
101102
add_executable(test_handle_manager c_api/test_handle_manager.cc)
@@ -675,6 +676,25 @@ if(BUILD_C_API)
675676
target_link_libraries(test_mcp_c_filter_chain llhttp)
676677
endif()
677678

679+
target_link_libraries(test_mcp_c_filter_chain_exception_safety
680+
gopher_mcp_c_static
681+
gopher-mcp-static
682+
gopher-mcp-event-static
683+
${LIBEVENT_LIBRARIES}
684+
${OPENSSL_LIBRARIES}
685+
gtest
686+
gmock
687+
gtest_main
688+
Threads::Threads
689+
)
690+
if(NGHTTP2_FOUND)
691+
target_link_directories(test_mcp_c_filter_chain_exception_safety PRIVATE ${NGHTTP2_LIBRARY_DIRS})
692+
target_link_libraries(test_mcp_c_filter_chain_exception_safety ${NGHTTP2_LIBRARIES})
693+
endif()
694+
if(LLHTTP_FOUND)
695+
target_link_libraries(test_mcp_c_filter_chain_exception_safety llhttp)
696+
endif()
697+
678698
target_link_libraries(test_mcp_c_filter_buffer
679699
gopher_mcp_c_static
680700
gopher-mcp-static
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/**
2+
* @file test_mcp_c_filter_chain_exception_safety.cc
3+
* @brief Tests for exception safety in MCP Filter Chain C API
4+
*
5+
* These tests verify that:
6+
* 1. No exceptions cross the C API boundary
7+
* 2. Functions return appropriate error codes on failure
8+
* 3. Resource cleanup happens correctly
9+
*/
10+
11+
#include <gtest/gtest.h>
12+
#include <gmock/gmock.h>
13+
#include <memory>
14+
#include <stdexcept>
15+
16+
#include "mcp/c_api/mcp_c_filter_chain.h"
17+
#include "mcp/c_api/mcp_c_types.h"
18+
#include "mcp/c_api/mcp_c_api.h"
19+
#include "mcp/c_api/mcp_c_api_json.h"
20+
21+
using namespace testing;
22+
23+
namespace {
24+
25+
// Test fixture for exception safety tests
26+
class FilterChainExceptionSafetyTest : public ::testing::Test {
27+
protected:
28+
void SetUp() override {
29+
// Initialize MCP API
30+
mcp_result_t result = mcp_initialize(nullptr);
31+
ASSERT_EQ(result, MCP_OK);
32+
33+
// Create a dispatcher for testing
34+
dispatcher_ = mcp_dispatcher_create();
35+
ASSERT_NE(dispatcher_, nullptr);
36+
}
37+
38+
void TearDown() override {
39+
if (dispatcher_) {
40+
mcp_dispatcher_destroy(dispatcher_);
41+
dispatcher_ = nullptr;
42+
}
43+
44+
mcp_cleanup();
45+
}
46+
47+
mcp_dispatcher_t dispatcher_ = nullptr;
48+
};
49+
50+
// Test that handle-returning functions return nullptr/0 on exception
51+
TEST_F(FilterChainExceptionSafetyTest, HandleReturningFunctionsReturnNullOnException) {
52+
// Test mcp_chain_builder_create_ex with null config
53+
mcp_filter_chain_builder_t builder = mcp_chain_builder_create_ex(dispatcher_, nullptr);
54+
EXPECT_EQ(builder, nullptr);
55+
56+
// Test mcp_chain_create_from_json with null json
57+
mcp_filter_chain_t chain = mcp_chain_create_from_json(dispatcher_, nullptr);
58+
EXPECT_EQ(chain, 0);
59+
60+
// Test mcp_chain_export_to_json with invalid chain
61+
mcp_json_value_t json = mcp_chain_export_to_json(0);
62+
EXPECT_NE(json, nullptr); // Returns null JSON, not nullptr
63+
mcp_json_free(json);
64+
65+
// Test mcp_chain_clone with invalid chain
66+
mcp_filter_chain_t cloned = mcp_chain_clone(0);
67+
EXPECT_EQ(cloned, 0);
68+
69+
// Test mcp_chain_merge with invalid chains
70+
mcp_filter_chain_t merged = mcp_chain_merge(0, 0, MCP_CHAIN_MODE_SEQUENTIAL);
71+
EXPECT_EQ(merged, 0);
72+
73+
// Test mcp_chain_dump with invalid chain
74+
char* dump = mcp_chain_dump(0, "text");
75+
EXPECT_EQ(dump, nullptr);
76+
}
77+
78+
// Test that status-returning functions return error codes on exception
79+
TEST_F(FilterChainExceptionSafetyTest, StatusReturningFunctionsReturnErrorOnException) {
80+
// Test functions with invalid parameters
81+
mcp_result_t result;
82+
83+
// Test mcp_chain_builder_add_node with null builder
84+
result = mcp_chain_builder_add_node(nullptr, nullptr);
85+
EXPECT_EQ(result, MCP_ERROR_INVALID_ARGUMENT);
86+
87+
// Test mcp_chain_pause with invalid chain
88+
result = mcp_chain_pause(0);
89+
EXPECT_EQ(result, MCP_ERROR_NOT_FOUND);
90+
91+
// Test mcp_chain_resume with invalid chain
92+
result = mcp_chain_resume(0);
93+
EXPECT_EQ(result, MCP_ERROR_NOT_FOUND);
94+
95+
// Test mcp_chain_reset with invalid chain
96+
result = mcp_chain_reset(0);
97+
EXPECT_EQ(result, MCP_ERROR_NOT_FOUND);
98+
99+
// Test mcp_chain_set_filter_enabled with null filter name
100+
result = mcp_chain_set_filter_enabled(0, nullptr, true);
101+
EXPECT_EQ(result, MCP_ERROR_INVALID_ARGUMENT);
102+
103+
// Test mcp_chain_get_stats with null stats
104+
result = mcp_chain_get_stats(0, nullptr);
105+
EXPECT_EQ(result, MCP_ERROR_INVALID_ARGUMENT);
106+
107+
// Test mcp_chain_set_event_callback with invalid chain
108+
result = mcp_chain_set_event_callback(0, nullptr, nullptr);
109+
EXPECT_EQ(result, MCP_ERROR_NOT_FOUND);
110+
}
111+
112+
// Test that state-returning functions return error state on exception
113+
TEST_F(FilterChainExceptionSafetyTest, StateReturningFunctionsReturnErrorStateOnException) {
114+
// Test mcp_chain_get_state with invalid chain
115+
mcp_chain_state_t state = mcp_chain_get_state(0);
116+
EXPECT_EQ(state, MCP_CHAIN_STATE_ERROR);
117+
}
118+
119+
// Test that void functions don't crash on exception
120+
TEST_F(FilterChainExceptionSafetyTest, VoidFunctionsNoCrashOnException) {
121+
// These should not crash even with invalid parameters
122+
123+
// Test mcp_chain_router_destroy with null
124+
mcp_chain_router_destroy(nullptr);
125+
126+
// Test mcp_chain_pool_return with null pool
127+
mcp_chain_pool_return(nullptr, 0);
128+
129+
// Test mcp_chain_pool_destroy with null
130+
mcp_chain_pool_destroy(nullptr);
131+
}
132+
133+
// Test router functions for exception safety
134+
TEST_F(FilterChainExceptionSafetyTest, RouterFunctionsExceptionSafety) {
135+
// Create router with null config
136+
mcp_chain_router_t router = mcp_chain_router_create(nullptr);
137+
EXPECT_EQ(router, nullptr);
138+
139+
// Test add_route with null router
140+
mcp_result_t result = mcp_chain_router_add_route(nullptr, nullptr, 0);
141+
EXPECT_EQ(result, MCP_ERROR_INVALID_ARGUMENT);
142+
143+
// Test route with null router
144+
mcp_filter_chain_t chain = mcp_chain_router_route(nullptr, nullptr, nullptr);
145+
EXPECT_EQ(chain, 0);
146+
}
147+
148+
// Test pool functions for exception safety
149+
TEST_F(FilterChainExceptionSafetyTest, PoolFunctionsExceptionSafety) {
150+
// Create pool with invalid base chain
151+
mcp_chain_pool_t pool = mcp_chain_pool_create(0, 10, MCP_ROUTING_ROUND_ROBIN);
152+
// This may or may not return nullptr depending on implementation
153+
154+
// Test get_next with null pool
155+
mcp_filter_chain_t chain = mcp_chain_pool_get_next(nullptr);
156+
EXPECT_EQ(chain, 0);
157+
158+
// Test get_stats with null pool
159+
size_t active, idle;
160+
uint64_t processed;
161+
mcp_result_t result = mcp_chain_pool_get_stats(nullptr, &active, &idle, &processed);
162+
EXPECT_EQ(result, MCP_ERROR_INVALID_ARGUMENT);
163+
}
164+
165+
// Test optimization functions for exception safety
166+
TEST_F(FilterChainExceptionSafetyTest, OptimizationFunctionsExceptionSafety) {
167+
mcp_result_t result;
168+
169+
// Test with invalid chain (should be safe)
170+
result = mcp_chain_optimize(0);
171+
EXPECT_EQ(result, MCP_OK); // TODO functions return OK
172+
173+
result = mcp_chain_reorder_filters(0);
174+
EXPECT_EQ(result, MCP_OK); // TODO functions return OK
175+
176+
result = mcp_chain_profile(0, nullptr, 100, nullptr);
177+
EXPECT_EQ(result, MCP_OK); // TODO functions return OK
178+
179+
result = mcp_chain_set_trace_level(0, 1);
180+
EXPECT_EQ(result, MCP_OK); // TODO functions return OK
181+
182+
result = mcp_chain_validate(0, nullptr);
183+
EXPECT_EQ(result, MCP_OK); // TODO functions return OK
184+
}
185+
186+
// Test that complex operations with valid inputs don't throw
187+
TEST_F(FilterChainExceptionSafetyTest, ValidOperationsNoThrow) {
188+
// Create a simple chain configuration
189+
mcp_json_value_t config = mcp_json_create_object();
190+
ASSERT_NE(config, nullptr);
191+
192+
mcp_json_value_t name = mcp_json_create_string("test_chain");
193+
mcp_json_object_set(config, "name", name);
194+
195+
mcp_json_value_t filters = mcp_json_create_array();
196+
mcp_json_object_set(config, "filters", filters);
197+
198+
// This should handle any internal exceptions gracefully
199+
mcp_filter_chain_t chain = mcp_chain_create_from_json(dispatcher_, config);
200+
// Chain creation might fail but shouldn't crash
201+
202+
if (chain != 0) {
203+
// Test various operations that should be exception-safe
204+
mcp_chain_state_t state = mcp_chain_get_state(chain);
205+
EXPECT_NE(state, MCP_CHAIN_STATE_ERROR);
206+
207+
mcp_result_t result = mcp_chain_pause(chain);
208+
EXPECT_TRUE(result == MCP_OK || result == MCP_ERROR_NOT_FOUND);
209+
210+
result = mcp_chain_resume(chain);
211+
EXPECT_TRUE(result == MCP_OK || result == MCP_ERROR_NOT_FOUND);
212+
213+
// Clean up
214+
// Note: No direct destroy function for chains in the API
215+
}
216+
217+
mcp_json_free(config);
218+
}
219+
220+
} // namespace

0 commit comments

Comments
 (0)