Bladeren bron

test(config): tests for per-collection timestamp precision

- Default precision is ms
- ns produces 1000 unique timestamps in tight insert loop
- ms stamps fit in ms range (< 10^15)
- Idempotent configure
- Cache reflects configure immediately (no reload)
- Unknown collections return default ms config
fszontagh 3 maanden geleden
bovenliggende
commit
e57c3850f7
2 gewijzigde bestanden met toevoegingen van 233 en 0 verwijderingen
  1. 30 0
      tests/CMakeLists.txt
  2. 203 0
      tests/test_timestamp_precision.cpp

+ 30 - 0
tests/CMakeLists.txt

@@ -52,7 +52,37 @@ else()
     target_include_directories(test_views PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
 endif()
 
+# Per-collection timestamp precision test
+set(TEST_TIMESTAMP_PRECISION_SOURCES
+    test_timestamp_precision.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/memory_store.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/config/collection_config_manager.cpp
+)
+
+add_executable(test_timestamp_precision ${TEST_TIMESTAMP_PRECISION_SOURCES})
+
+target_include_directories(test_timestamp_precision PRIVATE
+    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
+)
+
+if(TARGET nlohmann_json::nlohmann_json)
+    target_link_libraries(test_timestamp_precision PRIVATE nlohmann_json::nlohmann_json)
+else()
+    target_include_directories(test_timestamp_precision PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
+endif()
+
+if(TARGET spdlog::spdlog)
+    target_link_libraries(test_timestamp_precision PRIVATE spdlog::spdlog)
+else()
+    target_link_libraries(test_timestamp_precision PRIVATE ${SPDLOG_LIBRARIES})
+    target_include_directories(test_timestamp_precision PRIVATE ${SPDLOG_INCLUDE_DIRS})
+endif()
+
+find_package(Threads REQUIRED)
+target_link_libraries(test_timestamp_precision PRIVATE Threads::Threads)
+
 # Register with CTest
 enable_testing()
 add_test(NAME vector_storage COMMAND test_vector_storage)
 add_test(NAME views COMMAND test_views)
+add_test(NAME timestamp_precision COMMAND test_timestamp_precision)

+ 203 - 0
tests/test_timestamp_precision.cpp

@@ -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;
+}