|
| 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