Skip to content

Commit e09e6bf

Browse files
committed
Add error response body to post failure log and comprehensive unit tests
Include the HTTP response body in the error log when posting the handler response fails, aiding debugging of Lambda runtime API errors. Add unit tests covering invocation_response, http::response, outcome, invocation_request, and version APIs.
1 parent 2c9df5c commit e09e6bf

3 files changed

Lines changed: 277 additions & 2 deletions

File tree

src/runtime.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,10 @@ runtime::post_outcome runtime::do_post(
387387

388388
if (!is_success(aws::http::response_code(http_response_code))) {
389389
logging::log_error(
390-
LOG_TAG, "Failed to post handler success response. Http response code: %ld.", http_response_code);
390+
LOG_TAG,
391+
"Failed to post handler success response. Http response code: %ld. %s",
392+
http_response_code,
393+
resp.get_body().c_str());
391394
return aws::http::response_code(http_response_code);
392395
}
393396

tests/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ if(DEFINED ENV{GITHUB_ACTIONS})
1616

1717
add_executable(unit_tests
1818
unit/no_op_test.cpp
19-
unit/thread_local_curl_test.cpp)
19+
unit/thread_local_curl_test.cpp
20+
unit/unit_tests.cpp)
2021
target_link_libraries(unit_tests PRIVATE gtest_main aws-lambda-runtime)
2122

2223
# Register unit tests
@@ -30,6 +31,7 @@ else()
3031
add_executable(unit_tests
3132
unit/no_op_test.cpp
3233
unit/thread_local_curl_test.cpp
34+
unit/unit_tests.cpp
3335
gtest/gtest-all.cc)
3436
target_include_directories(unit_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
3537
target_link_libraries(unit_tests PRIVATE aws-lambda-runtime pthread)

tests/unit/unit_tests.cpp

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
#include <aws/lambda-runtime/runtime.h>
2+
#include <aws/lambda-runtime/version.h>
3+
#include <aws/lambda-runtime/outcome.h>
4+
#include <aws/http/response.h>
5+
#include "gtest/gtest.h"
6+
7+
using namespace aws::lambda_runtime;
8+
using namespace aws::http;
9+
10+
// --- invocation_response tests ---
11+
12+
TEST(InvocationResponseTest, success_response_has_correct_payload_and_content_type)
13+
{
14+
auto resp = invocation_response::success("hello world", "text/plain");
15+
EXPECT_TRUE(resp.is_success());
16+
EXPECT_EQ("hello world", resp.get_payload());
17+
EXPECT_EQ("text/plain", resp.get_content_type());
18+
}
19+
20+
TEST(InvocationResponseTest, success_response_with_json)
21+
{
22+
auto resp = invocation_response::success(R"({"key":"value"})", "application/json");
23+
EXPECT_TRUE(resp.is_success());
24+
EXPECT_EQ(R"({"key":"value"})", resp.get_payload());
25+
EXPECT_EQ("application/json", resp.get_content_type());
26+
}
27+
28+
TEST(InvocationResponseTest, success_response_with_empty_payload)
29+
{
30+
auto resp = invocation_response::success("", "application/json");
31+
EXPECT_TRUE(resp.is_success());
32+
EXPECT_EQ("", resp.get_payload());
33+
}
34+
35+
TEST(InvocationResponseTest, failure_response_is_not_success)
36+
{
37+
auto resp = invocation_response::failure("something broke", "RuntimeError", "");
38+
EXPECT_FALSE(resp.is_success());
39+
EXPECT_EQ("application/json", resp.get_content_type());
40+
}
41+
42+
TEST(InvocationResponseTest, failure_response_contains_error_message)
43+
{
44+
auto resp = invocation_response::failure("something broke", "RuntimeError", "");
45+
auto const& payload = resp.get_payload();
46+
EXPECT_NE(std::string::npos, payload.find("something broke"));
47+
EXPECT_NE(std::string::npos, payload.find("RuntimeError"));
48+
}
49+
50+
TEST(InvocationResponseTest, failure_response_json_escapes_quotes)
51+
{
52+
auto resp = invocation_response::failure(R"(error with "quotes")", "TestError", "");
53+
auto const& payload = resp.get_payload();
54+
EXPECT_NE(std::string::npos, payload.find(R"(error with \"quotes\")"));
55+
EXPECT_EQ(std::string::npos, payload.find(R"(error with "quotes")"));
56+
}
57+
58+
TEST(InvocationResponseTest, failure_response_json_escapes_backslash)
59+
{
60+
auto resp = invocation_response::failure(R"(path\to\file)", "TestError", "");
61+
auto const& payload = resp.get_payload();
62+
EXPECT_NE(std::string::npos, payload.find(R"(path\\to\\file)"));
63+
}
64+
65+
TEST(InvocationResponseTest, failure_response_json_escapes_newlines)
66+
{
67+
auto resp = invocation_response::failure("line1\nline2\r\n", "TestError", "");
68+
auto const& payload = resp.get_payload();
69+
EXPECT_NE(std::string::npos, payload.find(R"(line1\nline2\r\n)"));
70+
}
71+
72+
TEST(InvocationResponseTest, failure_response_json_escapes_tabs)
73+
{
74+
auto resp = invocation_response::failure("col1\tcol2", "TestError", "");
75+
auto const& payload = resp.get_payload();
76+
EXPECT_NE(std::string::npos, payload.find(R"(col1\tcol2)"));
77+
}
78+
79+
TEST(InvocationResponseTest, failure_response_json_escapes_control_characters)
80+
{
81+
std::string msg = "null\x00 byte";
82+
msg.push_back('\x01');
83+
auto resp = invocation_response::failure(msg, "TestError", "");
84+
auto const& payload = resp.get_payload();
85+
EXPECT_NE(std::string::npos, payload.find("\\u0001"));
86+
}
87+
88+
TEST(InvocationResponseTest, failure_response_preserves_xray_response)
89+
{
90+
auto resp = invocation_response::failure("err", "Type", "xray-data-here");
91+
EXPECT_EQ("xray-data-here", resp.get_xray_response());
92+
}
93+
94+
TEST(InvocationResponseTest, success_response_with_binary_content_type)
95+
{
96+
std::string binary_data(256, '\0');
97+
for (int i = 0; i < 256; ++i) {
98+
binary_data[static_cast<size_t>(i)] = static_cast<char>(i);
99+
}
100+
auto resp = invocation_response::success(binary_data, "application/octet-stream");
101+
EXPECT_TRUE(resp.is_success());
102+
EXPECT_EQ(256u, resp.get_payload().size());
103+
}
104+
105+
TEST(InvocationResponseTest, constructor_based_failure)
106+
{
107+
auto resp = invocation_response(R"({"custom":"error"})", "application/json", false, "xray");
108+
EXPECT_FALSE(resp.is_success());
109+
EXPECT_EQ(R"({"custom":"error"})", resp.get_payload());
110+
EXPECT_EQ("xray", resp.get_xray_response());
111+
}
112+
113+
// --- http::response tests ---
114+
115+
TEST(HttpResponseTest, add_and_retrieve_header)
116+
{
117+
response resp;
118+
resp.add_header("Content-Type", "application/json");
119+
EXPECT_TRUE(resp.has_header("content-type"));
120+
EXPECT_EQ("application/json", resp.get_header("content-type"));
121+
}
122+
123+
TEST(HttpResponseTest, headers_are_lowercased)
124+
{
125+
response resp;
126+
resp.add_header("X-Custom-Header", "some-value");
127+
EXPECT_TRUE(resp.has_header("x-custom-header"));
128+
EXPECT_FALSE(resp.has_header("X-Custom-Header"));
129+
}
130+
131+
TEST(HttpResponseTest, has_header_returns_false_for_missing)
132+
{
133+
response resp;
134+
resp.add_header("Content-Type", "text/plain");
135+
EXPECT_FALSE(resp.has_header("x-missing"));
136+
}
137+
138+
TEST(HttpResponseTest, append_body_accumulates)
139+
{
140+
response resp;
141+
resp.append_body("hello", 5);
142+
resp.append_body(" world", 6);
143+
EXPECT_EQ("hello world", resp.get_body());
144+
}
145+
146+
TEST(HttpResponseTest, append_body_empty)
147+
{
148+
response resp;
149+
EXPECT_EQ("", resp.get_body());
150+
}
151+
152+
TEST(HttpResponseTest, set_response_code)
153+
{
154+
response resp;
155+
resp.set_response_code(response_code::OK);
156+
EXPECT_EQ(response_code::OK, resp.get_response_code());
157+
}
158+
159+
TEST(HttpResponseTest, multiple_headers)
160+
{
161+
response resp;
162+
resp.add_header("lambda-runtime-aws-request-id", "req-123");
163+
resp.add_header("lambda-runtime-trace-id", "trace-456");
164+
resp.add_header("lambda-runtime-deadline-ms", "1234567890");
165+
EXPECT_EQ("req-123", resp.get_header("lambda-runtime-aws-request-id"));
166+
EXPECT_EQ("trace-456", resp.get_header("lambda-runtime-trace-id"));
167+
EXPECT_EQ("1234567890", resp.get_header("lambda-runtime-deadline-ms"));
168+
}
169+
170+
// --- outcome tests ---
171+
172+
TEST(OutcomeTest, success_outcome)
173+
{
174+
outcome<std::string, int> o(std::string("result"));
175+
EXPECT_TRUE(o.is_success());
176+
EXPECT_EQ("result", o.get_result());
177+
}
178+
179+
TEST(OutcomeTest, failure_outcome)
180+
{
181+
outcome<std::string, int> o(42);
182+
EXPECT_FALSE(o.is_success());
183+
EXPECT_EQ(42, o.get_failure());
184+
}
185+
186+
TEST(OutcomeTest, move_success)
187+
{
188+
outcome<std::string, int> o1(std::string("moved"));
189+
outcome<std::string, int> o2(std::move(o1));
190+
EXPECT_TRUE(o2.is_success());
191+
EXPECT_EQ("moved", o2.get_result());
192+
}
193+
194+
TEST(OutcomeTest, move_failure)
195+
{
196+
outcome<std::string, int> o1(99);
197+
outcome<std::string, int> o2(std::move(o1));
198+
EXPECT_FALSE(o2.is_success());
199+
EXPECT_EQ(99, o2.get_failure());
200+
}
201+
202+
TEST(OutcomeTest, with_response_code)
203+
{
204+
using test_outcome = outcome<no_result, response_code>;
205+
test_outcome success(no_result{});
206+
EXPECT_TRUE(success.is_success());
207+
208+
test_outcome failure(response_code::INTERNAL_SERVER_ERROR);
209+
EXPECT_FALSE(failure.is_success());
210+
EXPECT_EQ(response_code::INTERNAL_SERVER_ERROR, failure.get_failure());
211+
}
212+
213+
// --- invocation_request tests ---
214+
215+
TEST(InvocationRequestTest, get_time_remaining_future_deadline)
216+
{
217+
invocation_request req;
218+
req.deadline = std::chrono::system_clock::now() + std::chrono::seconds(30);
219+
auto remaining = req.get_time_remaining();
220+
EXPECT_GT(remaining.count(), 29000);
221+
EXPECT_LE(remaining.count(), 30000);
222+
}
223+
224+
TEST(InvocationRequestTest, get_time_remaining_past_deadline)
225+
{
226+
invocation_request req;
227+
req.deadline = std::chrono::system_clock::now() - std::chrono::seconds(5);
228+
auto remaining = req.get_time_remaining();
229+
EXPECT_LT(remaining.count(), 0);
230+
}
231+
232+
TEST(InvocationRequestTest, default_fields_are_empty)
233+
{
234+
invocation_request req;
235+
EXPECT_TRUE(req.payload.empty());
236+
EXPECT_TRUE(req.request_id.empty());
237+
EXPECT_TRUE(req.xray_trace_id.empty());
238+
EXPECT_TRUE(req.client_context.empty());
239+
EXPECT_TRUE(req.cognito_identity.empty());
240+
EXPECT_TRUE(req.function_arn.empty());
241+
EXPECT_TRUE(req.tenant_id.empty());
242+
}
243+
244+
// --- runtime_response tests ---
245+
246+
TEST(RuntimeResponseTest, constructor_sets_all_fields)
247+
{
248+
runtime_response resp("payload", "application/json", "xray");
249+
EXPECT_EQ("payload", resp.get_payload());
250+
EXPECT_EQ("application/json", resp.get_content_type());
251+
EXPECT_EQ("xray", resp.get_xray_response());
252+
}
253+
254+
// --- version tests (no AWS SDK needed) ---
255+
256+
TEST(VersionTest, version_string_not_empty)
257+
{
258+
EXPECT_NE(nullptr, get_version());
259+
EXPECT_GT(strlen(get_version()), 0u);
260+
}
261+
262+
TEST(VersionTest, version_format)
263+
{
264+
std::string v = get_version();
265+
int dots = 0;
266+
for (char c : v) {
267+
if (c == '.') dots++;
268+
}
269+
EXPECT_EQ(2, dots);
270+
}

0 commit comments

Comments
 (0)