|
|
@@ -20,6 +20,7 @@
|
|
|
#include <cstdint>
|
|
|
#include <iostream>
|
|
|
#include <nlohmann/json.hpp>
|
|
|
+#include <optional>
|
|
|
#include <set>
|
|
|
#include <string>
|
|
|
|
|
|
@@ -31,6 +32,18 @@ using smartbotic::database::MemoryStore;
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
+int g_pass = 0;
|
|
|
+int g_fail = 0;
|
|
|
+
|
|
|
+void check(bool cond, const char* msg) {
|
|
|
+ if (cond) {
|
|
|
+ ++g_pass;
|
|
|
+ } else {
|
|
|
+ ++g_fail;
|
|
|
+ std::cerr << "FAIL: " << msg << "\n";
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// Anything below 10^15 fits comfortably in the ms-since-epoch range (≈ 2001..),
|
|
|
// anything above is unambiguously ns-since-epoch in the current era.
|
|
|
constexpr uint64_t NS_THRESHOLD = 1'000'000'000'000'000ULL; // 10^15
|
|
|
@@ -49,6 +62,27 @@ Document makeDoc(const std::string& id = "") {
|
|
|
return d;
|
|
|
}
|
|
|
|
|
|
+// Mirrors the partial-update shape of DatabaseGrpcImpl::ConfigureCollection
|
|
|
+// (service/src/database_grpc_impl.cpp): start from the collection's current
|
|
|
+// config and overlay only the fields the caller actually set. An empty
|
|
|
+// precision string and a nullopt relations_enforced both mean "leave
|
|
|
+// unchanged" here, exactly as they do on the wire.
|
|
|
+void configureCollection(CollectionConfigManager& mgr,
|
|
|
+ const std::string& collection,
|
|
|
+ const std::string& precision,
|
|
|
+ std::optional<bool> relationsEnforced) {
|
|
|
+ CollectionCfg cfg = mgr.configFor(collection);
|
|
|
+ if (!precision.empty()) {
|
|
|
+ cfg.timestampPrecision = precision;
|
|
|
+ }
|
|
|
+ if (relationsEnforced.has_value()) {
|
|
|
+ cfg.relationsEnforced = relationsEnforced.value();
|
|
|
+ }
|
|
|
+ std::string err;
|
|
|
+ bool ok = mgr.setConfig(collection, cfg, err);
|
|
|
+ check(ok, "setConfig should succeed for a partial-update configure call");
|
|
|
+}
|
|
|
+
|
|
|
} // anonymous namespace
|
|
|
|
|
|
void test_default_is_ns() {
|
|
|
@@ -195,6 +229,38 @@ void test_default_for_unknown_collection() {
|
|
|
std::cout << "PASS: default config returned for unknown collection\n";
|
|
|
}
|
|
|
|
|
|
+// v2.11.0 T8 — relations_enforced is a partial update, same shape as
|
|
|
+// versioning_enabled (v2.4.5). Declaring a relation names its child and
|
|
|
+// parent explicitly, so the declaration IS the opt-in: the default must be
|
|
|
+// enforced, and an unrelated configure call (e.g. precision-only) must never
|
|
|
+// silently disable it.
|
|
|
+void test_relations_enforced_is_a_partial_update() {
|
|
|
+ MemoryStore store(defaultConfig());
|
|
|
+ store.start();
|
|
|
+ CollectionConfigManager mgr(store);
|
|
|
+ store.setConfigManager(&mgr);
|
|
|
+
|
|
|
+ CollectionCfg cfg = mgr.configFor("c");
|
|
|
+ check(cfg.relationsEnforced,
|
|
|
+ "defaults to enforced - declaring a relation IS the opt-in, so a "
|
|
|
+ "declared constraint must not silently do nothing");
|
|
|
+
|
|
|
+ // Change ONLY precision. A plain proto3 bool defaults to false and would
|
|
|
+ // silently disable enforcement here - the exact trap v2.4.5 hit with
|
|
|
+ // versioning_enabled, which is why the field is `optional`.
|
|
|
+ configureCollection(mgr, "c", /*precision=*/"ms", /*relationsEnforced=*/std::nullopt);
|
|
|
+ check(mgr.configFor("c").relationsEnforced,
|
|
|
+ "a precision-only call leaves enforcement alone");
|
|
|
+
|
|
|
+ configureCollection(mgr, "c", "", /*relationsEnforced=*/false);
|
|
|
+ check(!mgr.configFor("c").relationsEnforced, "and it can be turned off");
|
|
|
+ check(mgr.configFor("c").timestampPrecision == "ms",
|
|
|
+ "without resetting precision");
|
|
|
+
|
|
|
+ store.stop();
|
|
|
+ std::cout << "PASS: relations_enforced is a partial update\n";
|
|
|
+}
|
|
|
+
|
|
|
int main() {
|
|
|
test_default_is_ns();
|
|
|
test_ns_yields_unique_timestamps_in_tight_loop();
|
|
|
@@ -202,6 +268,12 @@ int main() {
|
|
|
test_idempotent_configure();
|
|
|
test_cache_reflects_configure_immediately();
|
|
|
test_default_for_unknown_collection();
|
|
|
- std::cout << "\nAll timestamp precision tests PASSED!\n";
|
|
|
+ test_relations_enforced_is_a_partial_update();
|
|
|
+
|
|
|
+ if (g_fail > 0) {
|
|
|
+ std::cerr << "\n" << g_fail << " check(s) FAILED (" << g_pass << " passed)\n";
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ std::cout << "\nAll timestamp precision tests PASSED! (" << g_pass << " checks)\n";
|
|
|
return 0;
|
|
|
}
|