Files
weaseldb/tests/test_commit_request.cpp

648 lines
19 KiB
C++

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "commit_request.hpp"
#include <doctest/doctest.h>
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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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<char *>(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
}
}