#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include "../benchmarks/test_data.hpp" #include "commit_request.hpp" #include #include TEST_CASE("CommitRequest basic parsing") { CommitRequest request; SUBCASE("Simple commit request") { std::string json = R"({ "request_id": "test123", "leader_id": "leader456", "read_version": 12345 })"; REQUIRE(request.parse_json(const_cast(json.data()), json.size())); REQUIRE(request.request_id().has_value()); REQUIRE(request.request_id().value() == "test123"); REQUIRE(request.leader_id() == "leader456"); REQUIRE(request.read_version() == 12345); } SUBCASE("With preconditions") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "preconditions": [ { "type": "point_read", "version": 12340, "key": "dGVzdA==" } ] })"; REQUIRE(request.parse_json(const_cast(json.data()), json.size())); REQUIRE(request.preconditions().size() == 1); REQUIRE(request.preconditions()[0].type == Precondition::Type::PointRead); REQUIRE(request.preconditions()[0].version == 12340); REQUIRE(request.preconditions()[0].begin == "test"); // "dGVzdA==" decoded REQUIRE(request.preconditions()[0].end == ""); } SUBCASE("With operations") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "operations": [ { "type": "write", "key": "dGVzdA==", "value": "dmFsdWU=" }, { "type": "delete", "key": "dGVzdDI=" } ] })"; REQUIRE(request.parse_json(const_cast(json.data()), json.size())); REQUIRE(request.operations().size() == 2); REQUIRE(request.operations()[0].type == Operation::Type::Write); REQUIRE(request.operations()[0].param1 == "test"); // key REQUIRE(request.operations()[0].param2 == "value"); // value REQUIRE(request.operations()[1].type == Operation::Type::Delete); REQUIRE(request.operations()[1].param1 == "test2"); // key REQUIRE(request.operations()[1].param2 == ""); // unused for delete } SUBCASE("Invalid JSON") { std::string json = R"({ "leader_id": "leader456", "read_version": "not_a_number" })"; REQUIRE_FALSE( request.parse_json(const_cast(json.data()), json.size())); } SUBCASE("Missing required leader_id") { std::string json = R"({ "request_id": "test123", "read_version": 12345 })"; REQUIRE_FALSE( request.parse_json(const_cast(json.data()), json.size())); REQUIRE_FALSE(request.is_parse_complete()); } SUBCASE("Missing required read_version") { std::string json = R"({ "request_id": "test123", "leader_id": "leader456" })"; REQUIRE_FALSE( request.parse_json(const_cast(json.data()), json.size())); REQUIRE_FALSE(request.is_parse_complete()); } SUBCASE("Empty leader_id") { std::string json = R"({ "request_id": "test123", "leader_id": "", "read_version": 12345 })"; REQUIRE_FALSE( request.parse_json(const_cast(json.data()), json.size())); REQUIRE_FALSE(request.is_parse_complete()); } SUBCASE("Missing both leader_id and read_version") { std::string json = R"({ "request_id": "test123" })"; REQUIRE_FALSE( request.parse_json(const_cast(json.data()), json.size())); REQUIRE_FALSE(request.is_parse_complete()); } SUBCASE("request_id is optional") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345 })"; REQUIRE(request.parse_json(const_cast(json.data()), json.size())); REQUIRE(request.is_parse_complete()); REQUIRE_FALSE(request.request_id().has_value()); REQUIRE(request.leader_id() == "leader456"); REQUIRE(request.read_version() == 12345); } } TEST_CASE("CommitRequest precondition and operation validation") { CommitRequest request; SUBCASE("Valid point_read precondition") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "preconditions": [ { "type": "point_read", "key": "dGVzdA==" } ] })"; REQUIRE(request.parse_json(const_cast(json.data()), json.size())); REQUIRE(request.is_parse_complete()); } SUBCASE("Invalid point_read precondition - missing key") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "preconditions": [ { "type": "point_read" } ] })"; REQUIRE_FALSE( request.parse_json(const_cast(json.data()), json.size())); } SUBCASE("Valid point_read precondition - empty key") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "preconditions": [ { "type": "point_read", "key": "" } ] })"; REQUIRE(request.parse_json(const_cast(json.data()), json.size())); REQUIRE(request.is_parse_complete()); } SUBCASE("Valid range_read precondition") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "preconditions": [ { "type": "range_read", "version": 12340, "begin": "dGVzdA==", "end": "dGVzdFo=" } ] })"; bool parse_result = request.parse_json(const_cast(json.data()), json.size()); INFO("Parse result: " << parse_result); INFO("Parse complete: " << request.is_parse_complete()); INFO("Parse error: " << request.has_parse_error()); const char *error_msg = request.get_parse_error(); INFO("Parse error message: " << (error_msg ? std::string(error_msg) : "none")); INFO("Leader ID: '" << request.leader_id() << "'"); INFO("Read version: " << request.read_version()); REQUIRE(parse_result); REQUIRE(request.is_parse_complete()); } SUBCASE("Valid range_read precondition - empty begin/end") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "preconditions": [ { "type": "range_read", "begin": "", "end": "" } ] })"; REQUIRE(request.parse_json(const_cast(json.data()), json.size())); REQUIRE(request.is_parse_complete()); } SUBCASE("Invalid range_read precondition - missing begin") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "preconditions": [ { "type": "range_read", "end": "dGVzdFo=" } ] })"; REQUIRE_FALSE( request.parse_json(const_cast(json.data()), json.size())); } SUBCASE("Invalid range_read precondition - missing end") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "preconditions": [ { "type": "range_read", "begin": "dGVzdA==" } ] })"; REQUIRE_FALSE( request.parse_json(const_cast(json.data()), json.size())); } SUBCASE("Valid write operation") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "operations": [ { "type": "write", "key": "dGVzdA==", "value": "dmFsdWU=" } ] })"; REQUIRE(request.parse_json(const_cast(json.data()), json.size())); REQUIRE(request.is_parse_complete()); } SUBCASE("Valid write operation - empty key and value") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "operations": [ { "type": "write", "key": "", "value": "" } ] })"; REQUIRE(request.parse_json(const_cast(json.data()), json.size())); REQUIRE(request.is_parse_complete()); } SUBCASE("Invalid write operation - missing key") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "operations": [ { "type": "write", "value": "dmFsdWU=" } ] })"; REQUIRE_FALSE( request.parse_json(const_cast(json.data()), json.size())); } SUBCASE("Invalid write operation - missing value") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "operations": [ { "type": "write", "key": "dGVzdA==" } ] })"; REQUIRE_FALSE( request.parse_json(const_cast(json.data()), json.size())); } SUBCASE("Valid delete operation") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "operations": [ { "type": "delete", "key": "dGVzdA==" } ] })"; REQUIRE(request.parse_json(const_cast(json.data()), json.size())); REQUIRE(request.is_parse_complete()); } SUBCASE("Invalid delete operation - missing key") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "operations": [ { "type": "delete" } ] })"; REQUIRE_FALSE( request.parse_json(const_cast(json.data()), json.size())); } SUBCASE("Valid range_delete operation") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "operations": [ { "type": "range_delete", "begin": "dGVzdA==", "end": "dGVzdFo=" } ] })"; REQUIRE(request.parse_json(const_cast(json.data()), json.size())); REQUIRE(request.is_parse_complete()); } SUBCASE("Invalid range_delete operation - missing begin") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "operations": [ { "type": "range_delete", "end": "dGVzdFo=" } ] })"; REQUIRE_FALSE( request.parse_json(const_cast(json.data()), json.size())); } SUBCASE("Invalid range_delete operation - missing end") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "operations": [ { "type": "range_delete", "begin": "dGVzdA==" } ] })"; REQUIRE_FALSE( request.parse_json(const_cast(json.data()), json.size())); } SUBCASE("Mixed valid and invalid operations") { std::string json = R"({ "leader_id": "leader456", "read_version": 12345, "operations": [ { "type": "write", "key": "dGVzdA==", "value": "dmFsdWU=" }, { "type": "delete" } ] })"; REQUIRE_FALSE( request.parse_json(const_cast(json.data()), json.size())); } } TEST_CASE("CommitRequest memory management") { CommitRequest request; std::string json = R"({ "request_id": "test123", "leader_id": "leader456", "read_version": 12345, "operations": [ { "type": "write", "key": "dGVzdA==", "value": "dmFsdWU=" } ] })"; REQUIRE(request.parse_json(json.data(), json.size())); // Check that arena allocation worked REQUIRE(request.total_allocated() > 0); REQUIRE(request.used_bytes() > 0); // Test reset request.reset(); REQUIRE(request.request_id().has_value() == false); REQUIRE(request.leader_id().empty()); REQUIRE(request.read_version() == 0); REQUIRE(request.operations().empty()); } TEST_CASE("CommitRequest streaming parsing") { CommitRequest request; SUBCASE("Simple streaming parse") { std::string json = R"({ "request_id": "test123", "leader_id": "leader456", "read_version": 12345 })"; REQUIRE(request.begin_streaming_parse()); // Parse in small chunks to simulate network reception std::string mutable_json = json; size_t chunk_size = 10; size_t offset = 0; CommitRequest::ParseStatus status = CommitRequest::ParseStatus::Incomplete; while (offset < mutable_json.size() && status == CommitRequest::ParseStatus::Incomplete) { size_t len = std::min(chunk_size, mutable_json.size() - offset); status = request.parse_chunk(mutable_json.data() + offset, len); offset += len; } if (status == CommitRequest::ParseStatus::Incomplete) { status = request.finish_streaming_parse(); } REQUIRE(status == CommitRequest::ParseStatus::Complete); REQUIRE(request.is_parse_complete()); REQUIRE_FALSE(request.has_parse_error()); REQUIRE(request.request_id().has_value()); REQUIRE(request.request_id().value() == "test123"); REQUIRE(request.leader_id() == "leader456"); REQUIRE(request.read_version() == 12345); } SUBCASE("Streaming parse with complex data") { std::string json = R"({ "request_id": "streaming-test", "leader_id": "leader789", "read_version": 98765, "preconditions": [ { "type": "point_read", "version": 98764, "key": "dGVzdEtleQ==" } ], "operations": [ { "type": "write", "key": "dGVzdEtleQ==", "value": "dGVzdFZhbHVl" }, { "type": "delete", "key": "ZGVsZXRlS2V5" } ] })"; REQUIRE(request.begin_streaming_parse()); // Parse one character at a time to really stress test streaming std::string mutable_json = json; CommitRequest::ParseStatus status = CommitRequest::ParseStatus::Incomplete; for (size_t i = 0; i < mutable_json.size() && status == CommitRequest::ParseStatus::Incomplete; ++i) { status = request.parse_chunk(mutable_json.data() + i, 1); } if (status == CommitRequest::ParseStatus::Incomplete) { status = request.finish_streaming_parse(); } REQUIRE(status == CommitRequest::ParseStatus::Complete); REQUIRE(request.is_parse_complete()); REQUIRE(request.request_id().value() == "streaming-test"); REQUIRE(request.leader_id() == "leader789"); REQUIRE(request.read_version() == 98765); REQUIRE(request.preconditions().size() == 1); REQUIRE(request.operations().size() == 2); // Verify precondition was parsed correctly REQUIRE(request.preconditions()[0].type == Precondition::Type::PointRead); REQUIRE(request.preconditions()[0].version == 98764); REQUIRE(request.preconditions()[0].begin == "testKey"); // key stored in begin for point_read REQUIRE(request.preconditions()[0].end == ""); // Verify operations were parsed correctly REQUIRE(request.operations()[0].type == Operation::Type::Write); REQUIRE(request.operations()[0].param1 == "testKey"); // key REQUIRE(request.operations()[0].param2 == "testValue"); // value REQUIRE(request.operations()[1].type == Operation::Type::Delete); REQUIRE(request.operations()[1].param1 == "deleteKey"); // key REQUIRE(request.operations()[1].param2 == ""); // unused for delete } SUBCASE("Streaming parse error handling") { std::string invalid_json = R"({ "leader_id": "leader456", "read_version": "invalid_number" })"; REQUIRE(request.begin_streaming_parse()); std::string mutable_json = invalid_json; CommitRequest::ParseStatus status = request.parse_chunk(mutable_json.data(), mutable_json.size()); if (status == CommitRequest::ParseStatus::Incomplete) { status = request.finish_streaming_parse(); } REQUIRE(status == CommitRequest::ParseStatus::Error); REQUIRE(request.has_parse_error()); REQUIRE_FALSE(request.is_parse_complete()); } SUBCASE("Complete document in single chunk") { std::string json = R"({"leader_id": "test", "read_version": 123})"; REQUIRE(request.begin_streaming_parse()); std::string mutable_json = json; CommitRequest::ParseStatus status = request.parse_chunk(mutable_json.data(), mutable_json.size()); // Should still be incomplete (streaming parser doesn't know if more data is // coming) REQUIRE(status == CommitRequest::ParseStatus::Incomplete); // Signal end of input to complete parsing status = request.finish_streaming_parse(); REQUIRE(status == CommitRequest::ParseStatus::Complete); REQUIRE(request.is_parse_complete()); REQUIRE(request.leader_id() == "test"); REQUIRE(request.read_version() == 123); } SUBCASE("Streaming parse missing required leader_id") { std::string json = R"({"request_id": "test", "read_version": 123})"; REQUIRE(request.begin_streaming_parse()); std::string mutable_json = json; CommitRequest::ParseStatus status = request.parse_chunk(mutable_json.data(), mutable_json.size()); if (status == CommitRequest::ParseStatus::Incomplete) { status = request.finish_streaming_parse(); } REQUIRE(status == CommitRequest::ParseStatus::Complete); REQUIRE_FALSE(request.is_parse_complete()); // Should fail validation } SUBCASE("Streaming parse missing required read_version") { std::string json = R"({"request_id": "test", "leader_id": "leader123"})"; REQUIRE(request.begin_streaming_parse()); std::string mutable_json = json; CommitRequest::ParseStatus status = request.parse_chunk(mutable_json.data(), mutable_json.size()); if (status == CommitRequest::ParseStatus::Incomplete) { status = request.finish_streaming_parse(); } REQUIRE(status == CommitRequest::ParseStatus::Complete); REQUIRE_FALSE(request.is_parse_complete()); // Should fail validation } } TEST_CASE("CommitRequest arena debug dump") { CommitRequest request; SUBCASE("Arena debug dump with COMPLEX_JSON") { // Parse the complex JSON to populate the arena with various data structures std::string json = weaseldb::test_data::COMPLEX_JSON; REQUIRE(request.parse_json(const_cast(json.data()), json.size())); REQUIRE(request.is_parse_complete()); // Verify the request was parsed correctly REQUIRE(request.request_id().has_value()); REQUIRE(request.request_id().value() == "complex-batch-operation-12345"); REQUIRE(request.leader_id() == "leader789abcdef"); REQUIRE(request.read_version() == 999999999); REQUIRE(request.preconditions().size() == 3); REQUIRE(request.operations().size() == 5); // Check that arena has been used REQUIRE(request.total_allocated() > 0); REQUIRE(request.used_bytes() > 0); // Capture debug output to string stream with content visualization std::ostringstream debug_output; request.arena().debug_dump(debug_output, true, true, 512); // Include memory map and content std::string debug_str = debug_output.str(); // Verify debug output contains expected information REQUIRE(debug_str.find("=== Arena Debug Dump ===") != std::string::npos); REQUIRE(debug_str.find("Total allocated:") != std::string::npos); REQUIRE(debug_str.find("Currently used:") != std::string::npos); REQUIRE(debug_str.find("Block Chain") != std::string::npos); REQUIRE(debug_str.find("Block addresses") != std::string::npos); REQUIRE(debug_str.find("Memory Contents:") != std::string::npos); REQUIRE(debug_str.find("0x0000:") != std::string::npos); // Hex dump should be present // Print debug output to console for manual inspection MESSAGE("Arena Debug Dump for COMPLEX_JSON:"); MESSAGE(debug_str); // Test that multiple blocks might be allocated for complex data MESSAGE("Arena statistics:"); MESSAGE(" Total allocated: " << request.total_allocated() << " bytes"); MESSAGE(" Used: " << request.used_bytes() << " bytes"); MESSAGE(" Utilization: " << (100.0 * request.used_bytes() / request.total_allocated()) << "%"); MESSAGE(" Number of blocks: " << request.arena().num_blocks()); } SUBCASE("Arena debug dump comparison: before and after parsing") { // Debug dump of empty arena std::ostringstream empty_output; request.arena().debug_dump(empty_output); std::string empty_debug = empty_output.str(); REQUIRE(empty_debug.find("Arena is empty") != std::string::npos); // Parse complex JSON std::string json = weaseldb::test_data::COMPLEX_JSON; REQUIRE(request.parse_json(const_cast(json.data()), json.size())); // Debug dump after parsing std::ostringstream used_output; request.arena().debug_dump(used_output, true); std::string used_debug = used_output.str(); // Compare the outputs - they should be different REQUIRE(empty_debug != used_debug); REQUIRE(used_debug.find("Arena is empty") == std::string::npos); MESSAGE("Empty arena debug:"); MESSAGE(empty_debug); MESSAGE("After parsing COMPLEX_JSON:"); MESSAGE(used_debug); } SUBCASE("Arena debug dump after reset") { // Parse complex JSON first std::string json = weaseldb::test_data::COMPLEX_JSON; REQUIRE(request.parse_json(const_cast(json.data()), json.size())); size_t allocated_before_reset = request.total_allocated(); size_t used_before_reset = request.used_bytes(); // Reset the request (this should reset the arena) request.reset(); // Arena should still have some allocated memory (first block is kept) size_t allocated_after_reset = request.total_allocated(); size_t used_after_reset = request.used_bytes(); // After reset, used bytes should be 0 or very small REQUIRE(used_after_reset < used_before_reset); // Total allocated should be less than or equal to before (extra blocks // freed) REQUIRE(allocated_after_reset <= allocated_before_reset); std::ostringstream reset_output; request.arena().debug_dump(reset_output); std::string reset_debug = reset_output.str(); MESSAGE("Arena after reset:"); MESSAGE(reset_debug); // Verify debug output shows minimal usage if (allocated_after_reset > 0) { REQUIRE(reset_debug.find("Total allocated:") != std::string::npos); // Should show very low utilization double utilization = allocated_after_reset > 0 ? (100.0 * used_after_reset / allocated_after_reset) : 0.0; REQUIRE(utilization < 10.0); // Less than 10% utilization after reset } else { REQUIRE(reset_debug.find("Arena is empty") != std::string::npos); } } SUBCASE("Arena memory content visualization") { // Parse COMPLEX_JSON to get diverse content in memory std::string json = weaseldb::test_data::COMPLEX_JSON; REQUIRE(request.parse_json(const_cast(json.data()), json.size())); // Test different content visualization options std::ostringstream no_content; request.arena().debug_dump(no_content, false, false); // No content visualization std::string no_content_str = no_content.str(); REQUIRE(no_content_str.find("Memory Contents:") == std::string::npos); std::ostringstream with_content; request.arena().debug_dump(with_content, true, true, 128); // Limited content std::string content_str = with_content.str(); REQUIRE(content_str.find("Memory Contents:") != std::string::npos); std::ostringstream full_content; request.arena().debug_dump(full_content, true, true, 2048); // Full content std::string full_content_str = full_content.str(); MESSAGE("Arena with limited content visualization (128 bytes):"); MESSAGE(content_str); // Verify that hex dump contains some expected strings from COMPLEX_JSON // The arena should contain the parsed strings like request_id, leader_id, // etc. REQUIRE(content_str.find("|") != std::string::npos); // ASCII section markers // Check that we can see some of the parsed data in ASCII representation // Note: The exact strings might be base64 decoded or processed bool found_readable_content = content_str.find("complex") != std::string::npos || content_str.find("leader") != std::string::npos || content_str.find("operation") != std::string::npos || content_str.find("precondition") != std::string::npos; if (found_readable_content) { MESSAGE("Found readable content in memory dump - strings are visible!"); } else { MESSAGE("Strings may be encoded or fragmented in memory - hex dump shows " "raw allocation patterns"); } // At minimum, we should see some hex content REQUIRE(content_str.find("0x0000:") != std::string::npos); REQUIRE(content_str.find("0x00") != std::string::npos); // Should have hex addresses } }