test_doc_binary.cpp 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. #include <cassert>
  2. #include <iostream>
  3. #include <optional>
  4. #include <string>
  5. #include <nlohmann/json.hpp>
  6. #include "doc_binary.hpp"
  7. using nlohmann::json;
  8. using smartbotic::db::doc_binary::Doc;
  9. using smartbotic::db::doc_binary::encode;
  10. using smartbotic::db::doc_binary::decode;
  11. using smartbotic::db::doc_binary::get_field;
  12. using smartbotic::db::doc_binary::to_json_text;
  13. namespace {
  14. void check(bool cond, const char* msg) {
  15. if (!cond) {
  16. std::cerr << "FAIL: " << msg << "\n";
  17. std::abort();
  18. }
  19. }
  20. void roundtrip(const json& j, const char* label) {
  21. auto doc = encode(j);
  22. auto back = decode(doc);
  23. check(back == j, label);
  24. }
  25. void test_default_doc_is_empty() {
  26. Doc d;
  27. check(d.empty(), "default-constructed Doc is empty");
  28. check(d.root() == nullptr, "empty Doc has null root");
  29. auto back = decode(d);
  30. check(back.is_null(), "decoding empty Doc returns null");
  31. }
  32. void test_primitives_roundtrip() {
  33. roundtrip(json(nullptr), "null");
  34. roundtrip(json(true), "true");
  35. roundtrip(json(false), "false");
  36. roundtrip(json(0), "int zero");
  37. roundtrip(json(42), "int 42");
  38. roundtrip(json(-7), "negative int");
  39. roundtrip(json(9223372036854775807LL), "int64 max");
  40. roundtrip(json(3.14), "double");
  41. roundtrip(json("hello"), "string");
  42. roundtrip(json(""), "empty string");
  43. }
  44. void test_collections_roundtrip() {
  45. roundtrip(json::array(), "empty array");
  46. roundtrip(json::object(), "empty object");
  47. roundtrip(json::array({1, 2, 3}), "int array");
  48. roundtrip(json::array({1, "two", 3.0, true, nullptr}), "mixed array");
  49. roundtrip(json({{"k", "v"}, {"n", 1}}), "small object");
  50. }
  51. void test_zoe_shape_roundtrip() {
  52. json zoe = {
  53. {"_id", "doc-1"},
  54. {"_created_at", 1731628800123LL},
  55. {"name", "Zoe"},
  56. {"tags", json::array({"hot", "pinned"})},
  57. {"_vector", json::array({0.1, -0.2, 0.3, 0.4})},
  58. {"meta", {{"flag", true}, {"score", 0.93}}},
  59. };
  60. roundtrip(zoe, "Zoe-shape");
  61. }
  62. void test_unicode_roundtrip() {
  63. roundtrip(
  64. json({{"hu", "Árvíztűrő tükörfúrógép"}, {"emoji", "🚀"}}),
  65. "unicode strings");
  66. }
  67. void test_get_field_present() {
  68. json j = {{"name", "Zoe"}, {"count", 42}, {"flag", true}};
  69. auto doc = encode(j);
  70. auto name = get_field(doc, "name");
  71. check(name.has_value(), "name present");
  72. check(name->get<std::string>() == "Zoe", "name value");
  73. auto count = get_field(doc, "count");
  74. check(count.has_value(), "count present");
  75. check(count->get<int>() == 42, "count value");
  76. auto flag = get_field(doc, "flag");
  77. check(flag.has_value(), "flag present");
  78. check(flag->get<bool>() == true, "flag value");
  79. }
  80. void test_get_field_missing() {
  81. json j = {{"name", "Zoe"}};
  82. auto doc = encode(j);
  83. auto absent = get_field(doc, "nonexistent");
  84. check(!absent.has_value(), "missing field returns nullopt");
  85. }
  86. void test_get_field_on_non_object() {
  87. auto doc = encode(json::array({1, 2, 3}));
  88. auto v = get_field(doc, "anything");
  89. check(!v.has_value(), "get_field on array returns nullopt");
  90. }
  91. void test_get_field_on_empty() {
  92. Doc d;
  93. auto v = get_field(d, "anything");
  94. check(!v.has_value(), "get_field on empty Doc returns nullopt");
  95. }
  96. void test_to_json_text_roundtrip() {
  97. json j = {{"a", 1}, {"b", "two"}, {"c", json::array({1, 2})}};
  98. auto doc = encode(j);
  99. std::string text = to_json_text(doc);
  100. json parsed = json::parse(text);
  101. check(parsed == j, "to_json_text round-trips through nlohmann::json::parse");
  102. }
  103. void test_to_json_text_empty() {
  104. Doc d;
  105. check(to_json_text(d) == "null", "to_json_text of empty Doc is \"null\"");
  106. }
  107. void test_doc_is_move_only() {
  108. auto d1 = encode(json({{"a", 1}}));
  109. check(!d1.empty(), "encoded doc not empty");
  110. Doc d2 = std::move(d1);
  111. check(d1.empty(), "moved-from Doc is empty");
  112. check(!d2.empty(), "moved-to Doc holds the data");
  113. auto field = get_field(d2, "a");
  114. check(field.has_value() && field->get<int>() == 1, "moved Doc still readable");
  115. }
  116. } // namespace
  117. int main() {
  118. test_default_doc_is_empty();
  119. test_primitives_roundtrip();
  120. test_collections_roundtrip();
  121. test_zoe_shape_roundtrip();
  122. test_unicode_roundtrip();
  123. test_get_field_present();
  124. test_get_field_missing();
  125. test_get_field_on_non_object();
  126. test_get_field_on_empty();
  127. test_to_json_text_roundtrip();
  128. test_to_json_text_empty();
  129. test_doc_is_move_only();
  130. std::cout << "test_doc_binary: all passed\n";
  131. return 0;
  132. }