Database System 0.1.0
Advanced C++20 Database System with Multi-Backend Support
Loading...
Searching...
No Matches
test_protocol_serializer.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
10#include <gtest/gtest.h>
11#include <cstdint>
12#include <string>
13#include <vector>
14#include <map>
15
17
18using namespace database::protocol;
19
20//=============================================================================
21// message_header Tests
22//=============================================================================
23
24class MessageHeaderTest : public ::testing::Test {};
25
26TEST_F(MessageHeaderTest, DefaultMagicAndVersion) {
27 message_header header;
28 EXPECT_EQ(header.magic, message_header::MAGIC);
30 EXPECT_TRUE(header.is_valid());
31}
32
33TEST_F(MessageHeaderTest, InvalidMagic) {
34 message_header header;
35 header.magic = 0xDEADBEEF;
36 EXPECT_FALSE(header.is_valid());
37}
38
39TEST_F(MessageHeaderTest, InvalidVersion) {
40 message_header header;
41 header.version = 99;
42 EXPECT_FALSE(header.is_valid());
43}
44
45TEST_F(MessageHeaderTest, SerializeDeserializeRoundTrip) {
46 message_header original;
47 original.type = message_type::QUERY_REQUEST;
48 original.request_id = 12345;
49 original.payload_size = 100;
50
51 auto bytes = protocol_serializer::serialize_header(original);
52 EXPECT_EQ(bytes.size(), 20u);
53
54 auto result = protocol_serializer::deserialize_header(bytes);
55 ASSERT_TRUE(result.is_ok());
56
57 auto recovered = result.value();
58 EXPECT_EQ(recovered.magic, original.magic);
59 EXPECT_EQ(recovered.version, original.version);
60 EXPECT_EQ(recovered.type, original.type);
61 EXPECT_EQ(recovered.request_id, original.request_id);
62 EXPECT_EQ(recovered.payload_size, original.payload_size);
63}
64
65TEST_F(MessageHeaderTest, DeserializeHeaderTooSmall) {
66 std::vector<uint8_t> data = {0x01, 0x02, 0x03};
68 EXPECT_TRUE(result.is_err());
69}
70
71TEST_F(MessageHeaderTest, DeserializeInvalidMagicReturnsError) {
72 // Build a 20-byte buffer with wrong magic
73 std::vector<uint8_t> data(20, 0);
74 // Write garbage magic (little-endian)
75 data[0] = 0xEF; data[1] = 0xBE; data[2] = 0xAD; data[3] = 0xDE;
76 // Write valid version (1, little-endian)
77 data[4] = 0x01; data[5] = 0x00;
78
80 EXPECT_TRUE(result.is_err());
81}
82
83TEST_F(MessageHeaderTest, AllMessageTypes) {
84 const message_type types[] = {
85 message_type::CONNECT_REQUEST, message_type::CONNECT_RESPONSE,
86 message_type::DISCONNECT, message_type::PING, message_type::PONG,
87 message_type::QUERY_REQUEST, message_type::QUERY_RESPONSE,
88 message_type::BEGIN_TRANSACTION, message_type::COMMIT_TRANSACTION,
89 message_type::ROLLBACK_TRANSACTION, message_type::TRANSACTION_RESPONSE,
90 message_type::PREPARE_STATEMENT, message_type::EXECUTE_PREPARED,
91 message_type::CLOSE_PREPARED, message_type::ERROR_RESPONSE
92 };
93
94 for (auto type : types) {
95 message_header header;
96 header.type = type;
97 header.request_id = 42;
98 header.payload_size = 0;
99
100 auto bytes = protocol_serializer::serialize_header(header);
101 auto result = protocol_serializer::deserialize_header(bytes);
102 ASSERT_TRUE(result.is_ok()) << "Failed for message_type " << static_cast<uint16_t>(type);
103 EXPECT_EQ(result.value().type, type);
104 }
105}
106
107TEST_F(MessageHeaderTest, LargeRequestIdAndPayload) {
108 message_header header;
109 header.type = message_type::QUERY_REQUEST;
110 header.request_id = UINT64_MAX;
111 header.payload_size = UINT32_MAX;
112
113 auto bytes = protocol_serializer::serialize_header(header);
114 auto result = protocol_serializer::deserialize_header(bytes);
115 ASSERT_TRUE(result.is_ok());
116 EXPECT_EQ(result.value().request_id, UINT64_MAX);
117 EXPECT_EQ(result.value().payload_size, UINT32_MAX);
118}
119
120//=============================================================================
121// connect_request / connect_response Tests
122//=============================================================================
123
124class ConnectProtocolTest : public ::testing::Test {};
125
126TEST_F(ConnectProtocolTest, ConnectRequestRoundTrip) {
127 connect_request original;
128 original.database_type = "postgresql";
129 original.connection_string = "host=localhost port=5432 dbname=testdb";
130 original.options = {{"timeout", "30"}, {"ssl", "true"}};
131
132 auto bytes = protocol_serializer::serialize(original);
133 EXPECT_FALSE(bytes.empty());
134
136 ASSERT_TRUE(result.is_ok());
137
138 auto recovered = result.value();
139 EXPECT_EQ(recovered.database_type, original.database_type);
140 EXPECT_EQ(recovered.connection_string, original.connection_string);
141 EXPECT_EQ(recovered.options.size(), original.options.size());
142 EXPECT_EQ(recovered.options.at("timeout"), "30");
143 EXPECT_EQ(recovered.options.at("ssl"), "true");
144}
145
146TEST_F(ConnectProtocolTest, ConnectRequestEmptyOptions) {
147 connect_request original;
148 original.database_type = "sqlite";
149 original.connection_string = "file:test.db";
150
151 auto bytes = protocol_serializer::serialize(original);
153 ASSERT_TRUE(result.is_ok());
154
155 auto recovered = result.value();
156 EXPECT_EQ(recovered.database_type, "sqlite");
157 EXPECT_EQ(recovered.connection_string, "file:test.db");
158 EXPECT_TRUE(recovered.options.empty());
159}
160
161TEST_F(ConnectProtocolTest, ConnectRequestTooSmall) {
162 std::vector<uint8_t> data = {0x01, 0x02};
164 EXPECT_TRUE(result.is_err());
165}
166
167TEST_F(ConnectProtocolTest, ConnectResponseSuccessRoundTrip) {
168 connect_response original;
169 original.success = true;
170 original.session_id = "sess-abc-123-def-456";
171 original.error_message = "";
172
173 auto bytes = protocol_serializer::serialize(original);
175 ASSERT_TRUE(result.is_ok());
176
177 auto recovered = result.value();
178 EXPECT_TRUE(recovered.success);
179 EXPECT_EQ(recovered.session_id, "sess-abc-123-def-456");
180 EXPECT_TRUE(recovered.error_message.empty());
181}
182
183TEST_F(ConnectProtocolTest, ConnectResponseFailureRoundTrip) {
184 connect_response original;
185 original.success = false;
186 original.session_id = "";
187 original.error_message = "Authentication failed: invalid credentials";
188
189 auto bytes = protocol_serializer::serialize(original);
191 ASSERT_TRUE(result.is_ok());
192
193 auto recovered = result.value();
194 EXPECT_FALSE(recovered.success);
195 EXPECT_TRUE(recovered.session_id.empty());
196 EXPECT_EQ(recovered.error_message, "Authentication failed: invalid credentials");
197}
198
199TEST_F(ConnectProtocolTest, ConnectResponseEmptyData) {
200 std::vector<uint8_t> data;
202 EXPECT_TRUE(result.is_err());
203}
204
205//=============================================================================
206// query_request / query_response Tests
207//=============================================================================
208
209class QueryProtocolTest : public ::testing::Test {};
210
211TEST_F(QueryProtocolTest, QueryRequestSelectRoundTrip) {
212 query_request original;
213 original.operation = query_operation::SELECT;
214 original.query_string = "SELECT * FROM users WHERE id = ?";
215 original.parameters = {"42"};
216
217 auto bytes = protocol_serializer::serialize(original);
219 ASSERT_TRUE(result.is_ok());
220
221 auto recovered = result.value();
222 EXPECT_EQ(recovered.operation, query_operation::SELECT);
223 EXPECT_EQ(recovered.query_string, original.query_string);
224 ASSERT_EQ(recovered.parameters.size(), 1u);
225 EXPECT_EQ(recovered.parameters[0], "42");
226}
227
228TEST_F(QueryProtocolTest, QueryRequestInsertWithMultipleParams) {
229 query_request original;
230 original.operation = query_operation::INSERT;
231 original.query_string = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
232 original.parameters = {"Alice", "alice@example.com", "30"};
233
234 auto bytes = protocol_serializer::serialize(original);
236 ASSERT_TRUE(result.is_ok());
237
238 auto recovered = result.value();
239 EXPECT_EQ(recovered.operation, query_operation::INSERT);
240 ASSERT_EQ(recovered.parameters.size(), 3u);
241 EXPECT_EQ(recovered.parameters[0], "Alice");
242 EXPECT_EQ(recovered.parameters[1], "alice@example.com");
243 EXPECT_EQ(recovered.parameters[2], "30");
244}
245
246TEST_F(QueryProtocolTest, QueryRequestNoParameters) {
247 query_request original;
248 original.operation = query_operation::CREATE;
249 original.query_string = "CREATE TABLE test (id INTEGER PRIMARY KEY)";
250
251 auto bytes = protocol_serializer::serialize(original);
253 ASSERT_TRUE(result.is_ok());
254
255 auto recovered = result.value();
256 EXPECT_EQ(recovered.operation, query_operation::CREATE);
257 EXPECT_TRUE(recovered.parameters.empty());
258}
259
260TEST_F(QueryProtocolTest, QueryRequestAllOperationTypes) {
261 const query_operation ops[] = {
262 query_operation::SELECT, query_operation::INSERT,
263 query_operation::UPDATE, query_operation::DELETE,
264 query_operation::CREATE, query_operation::ALTER,
265 query_operation::DROP, query_operation::OTHER
266 };
267
268 for (auto op : ops) {
269 query_request original;
270 original.operation = op;
271 original.query_string = "test query";
272
273 auto bytes = protocol_serializer::serialize(original);
275 ASSERT_TRUE(result.is_ok()) << "Failed for operation " << static_cast<int>(op);
276 EXPECT_EQ(result.value().operation, op);
277 }
278}
279
280TEST_F(QueryProtocolTest, QueryRequestSpecialCharacters) {
281 query_request original;
282 original.operation = query_operation::SELECT;
283 original.query_string = "SELECT * FROM users WHERE name LIKE ?";
284 original.parameters = {"O'Brien", "50%", "tab\tchar", "newline\nchar"};
285
286 auto bytes = protocol_serializer::serialize(original);
288 ASSERT_TRUE(result.is_ok());
289
290 auto recovered = result.value();
291 ASSERT_EQ(recovered.parameters.size(), 4u);
292 EXPECT_EQ(recovered.parameters[0], "O'Brien");
293 EXPECT_EQ(recovered.parameters[1], "50%");
294 EXPECT_EQ(recovered.parameters[2], "tab\tchar");
295 EXPECT_EQ(recovered.parameters[3], "newline\nchar");
296}
297
298TEST_F(QueryProtocolTest, QueryRequestEmptyData) {
299 std::vector<uint8_t> data;
301 EXPECT_TRUE(result.is_err());
302}
303
304TEST_F(QueryProtocolTest, QueryResponseSuccessWithRows) {
305 query_response original;
306 original.success = true;
307 original.affected_rows = 0;
308 original.last_insert_id = 0;
309 original.error_code = 0;
310 original.column_names = {"id", "name", "email"};
311 original.rows = {
312 {{"id", "1"}, {"name", "Alice"}, {"email", "alice@test.com"}},
313 {{"id", "2"}, {"name", "Bob"}, {"email", "bob@test.com"}}
314 };
315
316 auto bytes = protocol_serializer::serialize(original);
318 ASSERT_TRUE(result.is_ok());
319
320 auto recovered = result.value();
321 EXPECT_TRUE(recovered.success);
322 EXPECT_EQ(recovered.affected_rows, 0u);
323 ASSERT_EQ(recovered.column_names.size(), 3u);
324 EXPECT_EQ(recovered.column_names[0], "id");
325 EXPECT_EQ(recovered.column_names[1], "name");
326 EXPECT_EQ(recovered.column_names[2], "email");
327 ASSERT_EQ(recovered.rows.size(), 2u);
328 EXPECT_EQ(recovered.rows[0].at("name"), "Alice");
329 EXPECT_EQ(recovered.rows[1].at("email"), "bob@test.com");
330}
331
332TEST_F(QueryProtocolTest, QueryResponseInsertResult) {
333 query_response original;
334 original.success = true;
335 original.affected_rows = 1;
336 original.last_insert_id = 42;
337 original.error_code = 0;
338
339 auto bytes = protocol_serializer::serialize(original);
341 ASSERT_TRUE(result.is_ok());
342
343 auto recovered = result.value();
344 EXPECT_TRUE(recovered.success);
345 EXPECT_EQ(recovered.affected_rows, 1u);
346 EXPECT_EQ(recovered.last_insert_id, 42u);
347 EXPECT_TRUE(recovered.column_names.empty());
348 EXPECT_TRUE(recovered.rows.empty());
349}
350
351TEST_F(QueryProtocolTest, QueryResponseFailure) {
352 query_response original;
353 original.success = false;
354 original.error_code = 1045;
355 original.error_message = "Access denied for user 'root'@'localhost'";
356
357 auto bytes = protocol_serializer::serialize(original);
359 ASSERT_TRUE(result.is_ok());
360
361 auto recovered = result.value();
362 EXPECT_FALSE(recovered.success);
363 EXPECT_EQ(recovered.error_code, 1045);
364 EXPECT_EQ(recovered.error_message, "Access denied for user 'root'@'localhost'");
365}
366
367TEST_F(QueryProtocolTest, QueryResponseEmptyData) {
368 std::vector<uint8_t> data;
370 EXPECT_TRUE(result.is_err());
371}
372
373TEST_F(QueryProtocolTest, QueryResponseLargePayload) {
374 query_response original;
375 original.success = true;
376 original.column_names = {"id", "data"};
377
378 for (int i = 0; i < 100; ++i) {
379 original.rows.push_back({
380 {"id", std::to_string(i)},
381 {"data", std::string(256, 'x')}
382 });
383 }
384
385 auto bytes = protocol_serializer::serialize(original);
387 ASSERT_TRUE(result.is_ok());
388
389 auto recovered = result.value();
390 ASSERT_EQ(recovered.rows.size(), 100u);
391 EXPECT_EQ(recovered.rows[50].at("id"), "50");
392 EXPECT_EQ(recovered.rows[50].at("data").size(), 256u);
393}
394
395//=============================================================================
396// transaction_request / transaction_response Tests
397//=============================================================================
398
399class TransactionProtocolTest : public ::testing::Test {};
400
401TEST_F(TransactionProtocolTest, BeginTransactionRoundTrip) {
402 transaction_request original;
403 original.operation = message_type::BEGIN_TRANSACTION;
404
405 auto bytes = protocol_serializer::serialize(original);
407 ASSERT_TRUE(result.is_ok());
408 EXPECT_EQ(result.value().operation, message_type::BEGIN_TRANSACTION);
409}
410
411TEST_F(TransactionProtocolTest, CommitTransactionRoundTrip) {
412 transaction_request original;
413 original.operation = message_type::COMMIT_TRANSACTION;
414
415 auto bytes = protocol_serializer::serialize(original);
417 ASSERT_TRUE(result.is_ok());
418 EXPECT_EQ(result.value().operation, message_type::COMMIT_TRANSACTION);
419}
420
421TEST_F(TransactionProtocolTest, RollbackTransactionRoundTrip) {
422 transaction_request original;
423 original.operation = message_type::ROLLBACK_TRANSACTION;
424
425 auto bytes = protocol_serializer::serialize(original);
427 ASSERT_TRUE(result.is_ok());
428 EXPECT_EQ(result.value().operation, message_type::ROLLBACK_TRANSACTION);
429}
430
431TEST_F(TransactionProtocolTest, TransactionRequestTooSmall) {
432 std::vector<uint8_t> data = {0x01};
434 EXPECT_TRUE(result.is_err());
435}
436
437TEST_F(TransactionProtocolTest, TransactionResponseSuccessRoundTrip) {
438 transaction_response original;
439 original.success = true;
440 original.error_message = "";
441
442 auto bytes = protocol_serializer::serialize(original);
444 ASSERT_TRUE(result.is_ok());
445
446 auto recovered = result.value();
447 EXPECT_TRUE(recovered.success);
448 EXPECT_TRUE(recovered.error_message.empty());
449}
450
451TEST_F(TransactionProtocolTest, TransactionResponseFailureRoundTrip) {
452 transaction_response original;
453 original.success = false;
454 original.error_message = "Deadlock detected";
455
456 auto bytes = protocol_serializer::serialize(original);
458 ASSERT_TRUE(result.is_ok());
459
460 auto recovered = result.value();
461 EXPECT_FALSE(recovered.success);
462 EXPECT_EQ(recovered.error_message, "Deadlock detected");
463}
464
465TEST_F(TransactionProtocolTest, TransactionResponseEmptyData) {
466 std::vector<uint8_t> data;
468 EXPECT_TRUE(result.is_err());
469}
470
471//=============================================================================
472// error_response Tests
473//=============================================================================
474
475class ErrorProtocolTest : public ::testing::Test {};
476
477TEST_F(ErrorProtocolTest, ErrorResponseRoundTrip) {
478 error_response original;
479 original.error_code = 1001;
480 original.error_message = "Table not found";
481 original.error_context = "SELECT * FROM nonexistent_table";
482
483 auto bytes = protocol_serializer::serialize(original);
485 ASSERT_TRUE(result.is_ok());
486
487 auto recovered = result.value();
488 EXPECT_EQ(recovered.error_code, 1001);
489 EXPECT_EQ(recovered.error_message, "Table not found");
490 EXPECT_EQ(recovered.error_context, "SELECT * FROM nonexistent_table");
491}
492
493TEST_F(ErrorProtocolTest, ErrorResponseNegativeCode) {
494 error_response original;
495 original.error_code = -1;
496 original.error_message = "Internal server error";
497 original.error_context = "";
498
499 auto bytes = protocol_serializer::serialize(original);
501 ASSERT_TRUE(result.is_ok());
502
503 auto recovered = result.value();
504 EXPECT_EQ(recovered.error_code, -1);
505 EXPECT_EQ(recovered.error_message, "Internal server error");
506 EXPECT_TRUE(recovered.error_context.empty());
507}
508
509TEST_F(ErrorProtocolTest, ErrorResponseTooSmall) {
510 std::vector<uint8_t> data = {0x01, 0x02};
512 EXPECT_TRUE(result.is_err());
513}
514
515//=============================================================================
516// Cross-cutting serialization concerns
517//=============================================================================
518
519class SerializerEdgeCaseTest : public ::testing::Test {};
520
521TEST_F(SerializerEdgeCaseTest, EmptyStringFields) {
522 connect_request original;
523 original.database_type = "";
524 original.connection_string = "";
525
526 auto bytes = protocol_serializer::serialize(original);
528 ASSERT_TRUE(result.is_ok());
529
530 auto recovered = result.value();
531 EXPECT_TRUE(recovered.database_type.empty());
532 EXPECT_TRUE(recovered.connection_string.empty());
533}
534
535TEST_F(SerializerEdgeCaseTest, LongStringPreserved) {
536 connect_request original;
537 original.database_type = "postgresql";
538 original.connection_string = std::string(4096, 'A');
539
540 auto bytes = protocol_serializer::serialize(original);
542 ASSERT_TRUE(result.is_ok());
543 EXPECT_EQ(result.value().connection_string.size(), 4096u);
544}
545
546TEST_F(SerializerEdgeCaseTest, UnicodeStringPreserved) {
547 connect_request original;
548 original.database_type = "postgresql";
549 original.connection_string = "dbname=\xE4\xB8\xAD\xE6\x96\x87\xE6\xB5\x8B\xE8\xAF\x95";
550
551 auto bytes = protocol_serializer::serialize(original);
553 ASSERT_TRUE(result.is_ok());
554 EXPECT_EQ(result.value().connection_string, original.connection_string);
555}
556
557TEST_F(SerializerEdgeCaseTest, ManyOptionsMapPreserved) {
558 connect_request original;
559 original.database_type = "postgresql";
560 original.connection_string = "localhost";
561
562 for (int i = 0; i < 50; ++i) {
563 original.options["key_" + std::to_string(i)] = "val_" + std::to_string(i);
564 }
565
566 auto bytes = protocol_serializer::serialize(original);
568 ASSERT_TRUE(result.is_ok());
569
570 auto recovered = result.value();
571 EXPECT_EQ(recovered.options.size(), 50u);
572 EXPECT_EQ(recovered.options.at("key_25"), "val_25");
573}
574
575TEST_F(SerializerEdgeCaseTest, QueryRequestManyParameters) {
576 query_request original;
577 original.operation = query_operation::SELECT;
578 original.query_string = "BULK QUERY";
579
580 for (int i = 0; i < 100; ++i) {
581 original.parameters.push_back("param_" + std::to_string(i));
582 }
583
584 auto bytes = protocol_serializer::serialize(original);
586 ASSERT_TRUE(result.is_ok());
587
588 auto recovered = result.value();
589 ASSERT_EQ(recovered.parameters.size(), 100u);
590 EXPECT_EQ(recovered.parameters[0], "param_0");
591 EXPECT_EQ(recovered.parameters[99], "param_99");
592}
593
594TEST_F(SerializerEdgeCaseTest, HeaderZeroRequestId) {
595 message_header header;
596 header.type = message_type::PING;
597 header.request_id = 0;
598 header.payload_size = 0;
599
600 auto bytes = protocol_serializer::serialize_header(header);
601 auto result = protocol_serializer::deserialize_header(bytes);
602 ASSERT_TRUE(result.is_ok());
603 EXPECT_EQ(result.value().request_id, 0u);
604 EXPECT_EQ(result.value().payload_size, 0u);
605}
static kcenon::common::Result< connect_request > deserialize_connect_request(const std::vector< uint8_t > &data)
Deserialize connect request.
static kcenon::common::Result< message_header > deserialize_header(const std::vector< uint8_t > &data)
Deserialize message header from bytes.
static std::vector< uint8_t > serialize(const connect_request &request)
Serialize connect request.
static kcenon::common::Result< query_request > deserialize_query_request(const std::vector< uint8_t > &data)
Deserialize query request.
static kcenon::common::Result< connect_response > deserialize_connect_response(const std::vector< uint8_t > &data)
Deserialize connect response.
static std::vector< uint8_t > serialize_header(const message_header &header)
Serialize message header to bytes.
static kcenon::common::Result< transaction_response > deserialize_transaction_response(const std::vector< uint8_t > &data)
Deserialize transaction response.
static kcenon::common::Result< query_response > deserialize_query_response(const std::vector< uint8_t > &data)
Deserialize query response.
static kcenon::common::Result< error_response > deserialize_error_response(const std::vector< uint8_t > &data)
Deserialize error response.
static kcenon::common::Result< transaction_request > deserialize_transaction_request(const std::vector< uint8_t > &data)
Deserialize transaction request.
message_type
Database protocol message types.
query_operation
Type of query operation.
std::map< std::string, std::string > options
Common header for all protocol messages.
static constexpr uint32_t MAGIC
bool is_valid() const
Validate message header.
static constexpr uint16_t PROTOCOL_VERSION
std::vector< std::string > parameters
std::vector< std::map< std::string, std::string > > rows
std::vector< std::string > column_names
#define ASSERT_EQ(expected, actual, message)
#define ASSERT_TRUE(condition, message)
TEST_F(MessageHeaderTest, DefaultMagicAndVersion)