test_project_addressing.cpp 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. // v2.3 — project-addressing parser tests.
  2. //
  3. // Locks in the edge-case semantics. Anything that relies on the parser
  4. // (gRPC handlers, mirror routing, client) builds on these guarantees.
  5. #include <cstdlib>
  6. #include <iostream>
  7. #include <stdexcept>
  8. #include "project_addressing.hpp"
  9. using smartbotic::database::kDefaultProject;
  10. using smartbotic::database::ProjectCollection;
  11. using smartbotic::database::isValidProjectName;
  12. using smartbotic::database::parseProjectCollection;
  13. namespace {
  14. int g_pass = 0;
  15. int g_fail = 0;
  16. void check(bool cond, const char* msg) {
  17. if (cond) ++g_pass;
  18. else { ++g_fail; std::cerr << "FAIL: " << msg << "\n"; }
  19. }
  20. template <typename Fn>
  21. void check_throws(Fn fn, const char* msg) {
  22. try {
  23. fn();
  24. ++g_fail;
  25. std::cerr << "FAIL (did not throw): " << msg << "\n";
  26. } catch (const std::invalid_argument&) {
  27. ++g_pass;
  28. } catch (...) {
  29. ++g_fail;
  30. std::cerr << "FAIL (wrong exception type): " << msg << "\n";
  31. }
  32. }
  33. void test_unqualified_resolves_to_default() {
  34. auto r = parseProjectCollection("users");
  35. check(r.project == kDefaultProject, "unqualified: project == default");
  36. check(r.collection == "users", "unqualified: collection preserved");
  37. }
  38. void test_qualified_splits_on_separator() {
  39. auto r = parseProjectCollection("my_app:users");
  40. check(r.project == "my_app", "qualified: project parsed");
  41. check(r.collection == "users", "qualified: collection parsed");
  42. }
  43. void test_long_collection_name() {
  44. auto r = parseProjectCollection("my_app:some_long_collection_name");
  45. check(r.collection == "some_long_collection_name",
  46. "qualified: long collection preserved");
  47. }
  48. void test_empty_input_throws() {
  49. check_throws([] { parseProjectCollection(""); },
  50. "empty input must throw");
  51. }
  52. void test_leading_colon_throws() {
  53. check_throws([] { parseProjectCollection(":users"); },
  54. "leading ':' must throw (empty project)");
  55. }
  56. void test_trailing_colon_throws() {
  57. check_throws([] { parseProjectCollection("my_app:"); },
  58. "trailing ':' must throw (empty collection)");
  59. }
  60. void test_double_separator_throws() {
  61. check_throws([] { parseProjectCollection("a:b:c"); },
  62. "double ':' must throw (ambiguous)");
  63. }
  64. void test_just_colon_throws() {
  65. check_throws([] { parseProjectCollection(":"); },
  66. "':' alone must throw");
  67. }
  68. void test_valid_project_names() {
  69. check(isValidProjectName("default"), "'default' is valid");
  70. check(isValidProjectName("my_app"), "'my_app' is valid");
  71. check(isValidProjectName("App1"), "'App1' is valid");
  72. check(isValidProjectName("a-b-c"), "'a-b-c' is valid (dash allowed)");
  73. check(isValidProjectName("_starts_underscore") == false,
  74. "'_-prefix is reserved (system)");
  75. check(isValidProjectName("") == false, "empty is invalid");
  76. check(isValidProjectName("9starts_digit") == false,
  77. "digit-start is invalid");
  78. check(isValidProjectName("has:colon") == false,
  79. "':' inside name is invalid");
  80. check(isValidProjectName("has/slash") == false,
  81. "'/' inside name is invalid (filesystem safety)");
  82. check(isValidProjectName("has.dot") == false,
  83. "'.' inside name is invalid");
  84. check(isValidProjectName("has space") == false,
  85. "whitespace inside name is invalid");
  86. std::string too_long(64, 'a');
  87. check(isValidProjectName(too_long) == false,
  88. "64+ chars exceeds kMaxProjectNameLen");
  89. std::string max_len(63, 'a');
  90. check(isValidProjectName(max_len), "63 chars is the boundary");
  91. }
  92. } // namespace
  93. int main() {
  94. test_unqualified_resolves_to_default();
  95. test_qualified_splits_on_separator();
  96. test_long_collection_name();
  97. test_empty_input_throws();
  98. test_leading_colon_throws();
  99. test_trailing_colon_throws();
  100. test_double_separator_throws();
  101. test_just_colon_throws();
  102. test_valid_project_names();
  103. std::cout << "project_addressing: " << g_pass << " passed, "
  104. << g_fail << " failed\n";
  105. return g_fail == 0 ? 0 : 1;
  106. }