|
|
@@ -0,0 +1,203 @@
|
|
|
+/**
|
|
|
+ * Unit tests for per-collection timestamp precision (v1.6.0 Task 7).
|
|
|
+ *
|
|
|
+ * Exercises MemoryStore + CollectionConfigManager directly — no gRPC server.
|
|
|
+ *
|
|
|
+ * Test cases:
|
|
|
+ * 1. ms is the default precision
|
|
|
+ * 2. ns yields unique timestamps in a tight 1000-doc insert loop
|
|
|
+ * 3. ms stamps fit in the ms range (< 10^15) over 1000 docs
|
|
|
+ * 4. setConfig() is idempotent
|
|
|
+ * 5. configFor() reflects setConfig() immediately (no reload required)
|
|
|
+ * 6. Unknown collections return the default config
|
|
|
+ */
|
|
|
+
|
|
|
+#include "../service/src/memory_store.hpp"
|
|
|
+#include "../service/src/config/collection_config_manager.hpp"
|
|
|
+#include "../service/src/document.hpp"
|
|
|
+
|
|
|
+#include <cassert>
|
|
|
+#include <cstdint>
|
|
|
+#include <iostream>
|
|
|
+#include <nlohmann/json.hpp>
|
|
|
+#include <set>
|
|
|
+#include <string>
|
|
|
+
|
|
|
+using smartbotic::database::CollectionCfg;
|
|
|
+using smartbotic::database::CollectionConfigManager;
|
|
|
+using smartbotic::database::CollectionOptions;
|
|
|
+using smartbotic::database::Document;
|
|
|
+using smartbotic::database::MemoryStore;
|
|
|
+
|
|
|
+namespace {
|
|
|
+
|
|
|
+// 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
|
|
|
+
|
|
|
+MemoryStore::Config defaultConfig() {
|
|
|
+ MemoryStore::Config cfg;
|
|
|
+ cfg.nodeId = "test";
|
|
|
+ cfg.maxMemoryBytes = 256ULL * 1024 * 1024;
|
|
|
+ return cfg;
|
|
|
+}
|
|
|
+
|
|
|
+Document makeDoc(const std::string& id = "") {
|
|
|
+ Document d;
|
|
|
+ if (!id.empty()) d.id = id;
|
|
|
+ d.data = nlohmann::json{{"value", "x"}};
|
|
|
+ return d;
|
|
|
+}
|
|
|
+
|
|
|
+} // anonymous namespace
|
|
|
+
|
|
|
+void test_default_is_ms() {
|
|
|
+ MemoryStore store(defaultConfig());
|
|
|
+ store.start();
|
|
|
+ CollectionConfigManager mgr(store);
|
|
|
+ store.setConfigManager(&mgr);
|
|
|
+
|
|
|
+ store.createCollection("docs_default", CollectionOptions{});
|
|
|
+
|
|
|
+ auto id = store.insert("docs_default", makeDoc());
|
|
|
+ auto doc = store.get("docs_default", id);
|
|
|
+ assert(doc.has_value());
|
|
|
+ assert(doc->createdAt > 0);
|
|
|
+ assert(doc->createdAt < NS_THRESHOLD); // ms range
|
|
|
+ assert(doc->updatedAt > 0);
|
|
|
+ assert(doc->updatedAt < NS_THRESHOLD);
|
|
|
+
|
|
|
+ store.stop();
|
|
|
+ std::cout << "PASS: default precision is ms (createdAt=" << doc->createdAt << ")\n";
|
|
|
+}
|
|
|
+
|
|
|
+void test_ns_yields_unique_timestamps_in_tight_loop() {
|
|
|
+ MemoryStore store(defaultConfig());
|
|
|
+ store.start();
|
|
|
+ CollectionConfigManager mgr(store);
|
|
|
+ store.setConfigManager(&mgr);
|
|
|
+
|
|
|
+ store.createCollection("docs_ns", CollectionOptions{});
|
|
|
+
|
|
|
+ CollectionCfg ns_cfg;
|
|
|
+ ns_cfg.timestampPrecision = "ns";
|
|
|
+ std::string err;
|
|
|
+ bool ok = mgr.setConfig("docs_ns", ns_cfg, err);
|
|
|
+ assert(ok);
|
|
|
+
|
|
|
+ std::set<uint64_t> seen;
|
|
|
+ constexpr int N = 1000;
|
|
|
+ for (int i = 0; i < N; ++i) {
|
|
|
+ auto id = store.insert("docs_ns", makeDoc());
|
|
|
+ auto doc = store.get("docs_ns", id);
|
|
|
+ assert(doc.has_value());
|
|
|
+ assert(doc->createdAt >= NS_THRESHOLD); // ns range
|
|
|
+ auto [it, inserted] = seen.insert(doc->createdAt);
|
|
|
+ assert(inserted); // every timestamp must be unique
|
|
|
+ }
|
|
|
+ assert(seen.size() == static_cast<size_t>(N));
|
|
|
+
|
|
|
+ store.stop();
|
|
|
+ std::cout << "PASS: ns precision produces " << N
|
|
|
+ << " unique timestamps in tight loop\n";
|
|
|
+}
|
|
|
+
|
|
|
+void test_ms_baseline_fits_in_ms_range() {
|
|
|
+ MemoryStore store(defaultConfig());
|
|
|
+ store.start();
|
|
|
+ CollectionConfigManager mgr(store);
|
|
|
+ store.setConfigManager(&mgr);
|
|
|
+
|
|
|
+ store.createCollection("docs_ms", CollectionOptions{});
|
|
|
+
|
|
|
+ CollectionCfg ms_cfg;
|
|
|
+ ms_cfg.timestampPrecision = "ms";
|
|
|
+ std::string err;
|
|
|
+ bool ok = mgr.setConfig("docs_ms", ms_cfg, err);
|
|
|
+ assert(ok);
|
|
|
+
|
|
|
+ constexpr int N = 1000;
|
|
|
+ for (int i = 0; i < N; ++i) {
|
|
|
+ auto id = store.insert("docs_ms", makeDoc());
|
|
|
+ auto doc = store.get("docs_ms", id);
|
|
|
+ assert(doc.has_value());
|
|
|
+ assert(doc->createdAt < NS_THRESHOLD); // ms range
|
|
|
+ }
|
|
|
+ // We deliberately do NOT assert on tie-production here — it's machine-
|
|
|
+ // dependent. The invariant that ms-stamped docs stay in the ms range is
|
|
|
+ // what the default-preservation story needs.
|
|
|
+
|
|
|
+ store.stop();
|
|
|
+ std::cout << "PASS: ms precision stamps fit in ms range (" << N << " docs)\n";
|
|
|
+}
|
|
|
+
|
|
|
+void test_idempotent_configure() {
|
|
|
+ MemoryStore store(defaultConfig());
|
|
|
+ store.start();
|
|
|
+ CollectionConfigManager mgr(store);
|
|
|
+ store.setConfigManager(&mgr);
|
|
|
+
|
|
|
+ CollectionCfg cfg;
|
|
|
+ cfg.timestampPrecision = "ns";
|
|
|
+ std::string err;
|
|
|
+
|
|
|
+ assert(mgr.setConfig("docs_idem", cfg, err));
|
|
|
+ assert(mgr.configFor("docs_idem").timestampPrecision == "ns");
|
|
|
+ assert(mgr.hasExplicitConfig("docs_idem"));
|
|
|
+
|
|
|
+ // Second call with the same value — should succeed and leave state intact.
|
|
|
+ assert(mgr.setConfig("docs_idem", cfg, err));
|
|
|
+ assert(mgr.configFor("docs_idem").timestampPrecision == "ns");
|
|
|
+ assert(mgr.hasExplicitConfig("docs_idem"));
|
|
|
+
|
|
|
+ store.stop();
|
|
|
+ std::cout << "PASS: setConfig is idempotent\n";
|
|
|
+}
|
|
|
+
|
|
|
+void test_cache_reflects_configure_immediately() {
|
|
|
+ MemoryStore store(defaultConfig());
|
|
|
+ store.start();
|
|
|
+ CollectionConfigManager mgr(store);
|
|
|
+ store.setConfigManager(&mgr);
|
|
|
+
|
|
|
+ // Before configure: default ms, not explicit.
|
|
|
+ assert(mgr.configFor("docs_cache").timestampPrecision == "ms");
|
|
|
+ assert(!mgr.hasExplicitConfig("docs_cache"));
|
|
|
+
|
|
|
+ CollectionCfg cfg;
|
|
|
+ cfg.timestampPrecision = "ns";
|
|
|
+ std::string err;
|
|
|
+ assert(mgr.setConfig("docs_cache", cfg, err));
|
|
|
+
|
|
|
+ // After configure: ns, immediately — no reloadFromStore() required.
|
|
|
+ assert(mgr.configFor("docs_cache").timestampPrecision == "ns");
|
|
|
+ assert(mgr.hasExplicitConfig("docs_cache"));
|
|
|
+
|
|
|
+ store.stop();
|
|
|
+ std::cout << "PASS: cache reflects setConfig immediately\n";
|
|
|
+}
|
|
|
+
|
|
|
+void test_default_for_unknown_collection() {
|
|
|
+ MemoryStore store(defaultConfig());
|
|
|
+ store.start();
|
|
|
+ CollectionConfigManager mgr(store);
|
|
|
+ store.setConfigManager(&mgr);
|
|
|
+
|
|
|
+ auto cfg = mgr.configFor("never_configured_collection");
|
|
|
+ assert(cfg.timestampPrecision == "ms");
|
|
|
+ assert(!mgr.hasExplicitConfig("never_configured_collection"));
|
|
|
+
|
|
|
+ store.stop();
|
|
|
+ std::cout << "PASS: default config returned for unknown collection\n";
|
|
|
+}
|
|
|
+
|
|
|
+int main() {
|
|
|
+ test_default_is_ms();
|
|
|
+ test_ns_yields_unique_timestamps_in_tight_loop();
|
|
|
+ test_ms_baseline_fits_in_ms_range();
|
|
|
+ test_idempotent_configure();
|
|
|
+ test_cache_reflects_configure_immediately();
|
|
|
+ test_default_for_unknown_collection();
|
|
|
+ std::cout << "\nAll timestamp precision tests PASSED!\n";
|
|
|
+ return 0;
|
|
|
+}
|