| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- #include <cassert>
- #include <iostream>
- #include <optional>
- #include <string>
- #include <nlohmann/json.hpp>
- #include "doc_binary.hpp"
- using nlohmann::json;
- using smartbotic::db::doc_binary::Doc;
- using smartbotic::db::doc_binary::encode;
- using smartbotic::db::doc_binary::decode;
- using smartbotic::db::doc_binary::get_field;
- using smartbotic::db::doc_binary::to_json_text;
- namespace {
- void check(bool cond, const char* msg) {
- if (!cond) {
- std::cerr << "FAIL: " << msg << "\n";
- std::abort();
- }
- }
- void roundtrip(const json& j, const char* label) {
- auto doc = encode(j);
- auto back = decode(doc);
- check(back == j, label);
- }
- void test_default_doc_is_empty() {
- Doc d;
- check(d.empty(), "default-constructed Doc is empty");
- check(d.root() == nullptr, "empty Doc has null root");
- auto back = decode(d);
- check(back.is_null(), "decoding empty Doc returns null");
- }
- void test_primitives_roundtrip() {
- roundtrip(json(nullptr), "null");
- roundtrip(json(true), "true");
- roundtrip(json(false), "false");
- roundtrip(json(0), "int zero");
- roundtrip(json(42), "int 42");
- roundtrip(json(-7), "negative int");
- roundtrip(json(9223372036854775807LL), "int64 max");
- roundtrip(json(3.14), "double");
- roundtrip(json("hello"), "string");
- roundtrip(json(""), "empty string");
- }
- void test_collections_roundtrip() {
- roundtrip(json::array(), "empty array");
- roundtrip(json::object(), "empty object");
- roundtrip(json::array({1, 2, 3}), "int array");
- roundtrip(json::array({1, "two", 3.0, true, nullptr}), "mixed array");
- roundtrip(json({{"k", "v"}, {"n", 1}}), "small object");
- }
- void test_zoe_shape_roundtrip() {
- json zoe = {
- {"_id", "doc-1"},
- {"_created_at", 1731628800123LL},
- {"name", "Zoe"},
- {"tags", json::array({"hot", "pinned"})},
- {"_vector", json::array({0.1, -0.2, 0.3, 0.4})},
- {"meta", {{"flag", true}, {"score", 0.93}}},
- };
- roundtrip(zoe, "Zoe-shape");
- }
- void test_unicode_roundtrip() {
- roundtrip(
- json({{"hu", "Árvíztűrő tükörfúrógép"}, {"emoji", "🚀"}}),
- "unicode strings");
- }
- void test_get_field_present() {
- json j = {{"name", "Zoe"}, {"count", 42}, {"flag", true}};
- auto doc = encode(j);
- auto name = get_field(doc, "name");
- check(name.has_value(), "name present");
- check(name->get<std::string>() == "Zoe", "name value");
- auto count = get_field(doc, "count");
- check(count.has_value(), "count present");
- check(count->get<int>() == 42, "count value");
- auto flag = get_field(doc, "flag");
- check(flag.has_value(), "flag present");
- check(flag->get<bool>() == true, "flag value");
- }
- void test_get_field_missing() {
- json j = {{"name", "Zoe"}};
- auto doc = encode(j);
- auto absent = get_field(doc, "nonexistent");
- check(!absent.has_value(), "missing field returns nullopt");
- }
- void test_get_field_on_non_object() {
- auto doc = encode(json::array({1, 2, 3}));
- auto v = get_field(doc, "anything");
- check(!v.has_value(), "get_field on array returns nullopt");
- }
- void test_get_field_on_empty() {
- Doc d;
- auto v = get_field(d, "anything");
- check(!v.has_value(), "get_field on empty Doc returns nullopt");
- }
- void test_to_json_text_roundtrip() {
- json j = {{"a", 1}, {"b", "two"}, {"c", json::array({1, 2})}};
- auto doc = encode(j);
- std::string text = to_json_text(doc);
- json parsed = json::parse(text);
- check(parsed == j, "to_json_text round-trips through nlohmann::json::parse");
- }
- void test_to_json_text_empty() {
- Doc d;
- check(to_json_text(d) == "null", "to_json_text of empty Doc is \"null\"");
- }
- void test_doc_supports_move() {
- auto d1 = encode(json({{"a", 1}}));
- check(!d1.empty(), "encoded doc not empty");
- Doc d2 = std::move(d1);
- check(d1.empty(), "moved-from Doc is empty");
- check(!d2.empty(), "moved-to Doc holds the data");
- auto field = get_field(d2, "a");
- check(field.has_value() && field->get<int>() == 1, "moved Doc still readable");
- }
- void test_doc_copy_semantics() {
- // Phase B T3 follow-up: Doc gained copy support because Document is
- // copied by value across the codebase (vector<Document>, replication
- // paths, etc.). Copies must be independent — mutating the source must
- // not affect the copy, and vice versa.
- auto src = encode(json({{"name", "Zoe"}, {"count", 42}}));
- check(!src.empty(), "source doc not empty");
- // Copy-construct.
- Doc copy = src;
- check(!copy.empty(), "copied Doc holds data");
- check(!src.empty(), "source Doc still holds data after copy");
- // Both should decode to the same JSON tree.
- json src_json = decode(src);
- json copy_json = decode(copy);
- check(src_json == copy_json, "copy and source decode to equal trees");
- // Independence check: replace the source with new content via move-
- // assignment; the copy must remain unchanged.
- auto replacement = encode(json({{"name", "Other"}, {"count", 0}}));
- src = std::move(replacement);
- json copy_after = decode(copy);
- check(copy_after == json({{"name", "Zoe"}, {"count", 42}}),
- "copy unaffected by source mutation");
- // Copy-assignment with self-assignment safety.
- Doc d = encode(json({{"k", "v"}}));
- d = d; // NOLINT(clang-diagnostic-self-assign-overloaded)
- auto field = get_field(d, "k");
- check(field.has_value() && field->get<std::string>() == "v",
- "self-copy-assignment preserves contents");
- // Copy-assignment from a non-empty to another non-empty.
- Doc a = encode(json({{"a", 1}}));
- Doc b = encode(json({{"b", 2}}));
- a = b;
- check(decode(a) == json({{"b", 2}}), "copy-assignment overwrites target");
- check(decode(b) == json({{"b", 2}}), "copy-assignment leaves source intact");
- // Copy from an empty Doc → target becomes empty.
- Doc empty;
- Doc target = encode(json({{"x", 1}}));
- target = empty;
- check(target.empty(), "copy-assignment from empty Doc empties target");
- }
- } // namespace
- int main() {
- test_default_doc_is_empty();
- test_primitives_roundtrip();
- test_collections_roundtrip();
- test_zoe_shape_roundtrip();
- test_unicode_roundtrip();
- test_get_field_present();
- test_get_field_missing();
- test_get_field_on_non_object();
- test_get_field_on_empty();
- test_to_json_text_roundtrip();
- test_to_json_text_empty();
- test_doc_supports_move();
- test_doc_copy_semantics();
- std::cout << "test_doc_binary: all passed\n";
- return 0;
- }
|