|
@@ -0,0 +1,474 @@
|
|
|
|
|
+#include <gtest/gtest.h>
|
|
|
|
|
+
|
|
|
|
|
+#include <filesystem>
|
|
|
|
|
+#include <thread>
|
|
|
|
|
+
|
|
|
|
|
+#include "smartbotic/database/snapshot.hpp"
|
|
|
|
|
+
|
|
|
|
|
+namespace smartbotic::database {
|
|
|
|
|
+namespace {
|
|
|
|
|
+
|
|
|
|
|
+/// Mock collection provider for testing
|
|
|
|
|
+class MockCollectionProvider : public ICollectionProvider {
|
|
|
|
|
+public:
|
|
|
|
|
+ void AddCollection(std::unique_ptr<Collection> collection) {
|
|
|
|
|
+ std::string name(collection->GetName());
|
|
|
|
|
+ collections_.emplace(name, std::move(collection));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [[nodiscard]] auto GetCollectionNames() const -> std::vector<std::string> override {
|
|
|
|
|
+ std::vector<std::string> names;
|
|
|
|
|
+ names.reserve(collections_.size());
|
|
|
|
|
+ for (const auto& [name, _] : collections_) {
|
|
|
|
|
+ names.push_back(name);
|
|
|
|
|
+ }
|
|
|
|
|
+ return names;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [[nodiscard]] auto GetCollection(std::string_view name) const -> const Collection* override {
|
|
|
|
|
+ auto it = collections_.find(std::string(name));
|
|
|
|
|
+ if (it == collections_.end()) {
|
|
|
|
|
+ return nullptr;
|
|
|
|
|
+ }
|
|
|
|
|
+ return it->second.get();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [[nodiscard]] auto GetAllMetadata() const -> std::unordered_map<std::string, CollectionMeta> override {
|
|
|
|
|
+ std::unordered_map<std::string, CollectionMeta> result;
|
|
|
|
|
+ for (const auto& [name, collection] : collections_) {
|
|
|
|
|
+ result.emplace(name, collection->GetMetadata());
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+private:
|
|
|
|
|
+ std::unordered_map<std::string, std::unique_ptr<Collection>> collections_;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+class SnapshotTest : public ::testing::Test {
|
|
|
|
|
+protected:
|
|
|
|
|
+ void SetUp() override {
|
|
|
|
|
+ // Use a unique test directory for each test
|
|
|
|
|
+ test_dir_ = std::filesystem::temp_directory_path() / ("snapshot_test_" + std::to_string(std::time(nullptr)));
|
|
|
|
|
+ std::filesystem::create_directories(test_dir_);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void TearDown() override {
|
|
|
|
|
+ // Clean up test directory
|
|
|
|
|
+ std::error_code ec;
|
|
|
|
|
+ std::filesystem::remove_all(test_dir_, ec);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [[nodiscard]] auto CreateConfig() -> SnapshotConfig {
|
|
|
|
|
+ SnapshotConfig config;
|
|
|
|
|
+ config.snapshot_dir = test_dir_.string();
|
|
|
|
|
+ config.interval = std::chrono::seconds(3600); // Long interval for tests
|
|
|
|
|
+ config.change_threshold = 0; // Disable threshold for tests
|
|
|
|
|
+ config.enabled = true;
|
|
|
|
|
+ return config;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::filesystem::path test_dir_;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+// Basic Snapshot Tests
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+
|
|
|
|
|
+TEST_F(SnapshotTest, CreateSnapshotSync_EmptyDatabase) {
|
|
|
|
|
+ auto config = CreateConfig();
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+
|
|
|
|
|
+ auto provider = std::make_shared<MockCollectionProvider>();
|
|
|
|
|
+
|
|
|
|
|
+ ASSERT_TRUE(manager.Start(provider));
|
|
|
|
|
+ EXPECT_EQ(manager.GetStatus(), SnapshotManagerStatus::kRunning);
|
|
|
|
|
+
|
|
|
|
|
+ auto result = manager.CreateSnapshotSync();
|
|
|
|
|
+ ASSERT_TRUE(result.IsOk());
|
|
|
|
|
+ EXPECT_EQ(result.value->collections, 0);
|
|
|
|
|
+ EXPECT_EQ(result.value->documents, 0);
|
|
|
|
|
+
|
|
|
|
|
+ // Snapshot file should exist
|
|
|
|
|
+ EXPECT_TRUE(manager.SnapshotExists());
|
|
|
|
|
+
|
|
|
|
|
+ manager.Stop();
|
|
|
|
|
+ EXPECT_EQ(manager.GetStatus(), SnapshotManagerStatus::kStopped);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+TEST_F(SnapshotTest, CreateSnapshotSync_WithCollections) {
|
|
|
|
|
+ auto config = CreateConfig();
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+
|
|
|
|
|
+ auto provider = std::make_shared<MockCollectionProvider>();
|
|
|
|
|
+
|
|
|
|
|
+ // Create a collection with some documents
|
|
|
|
|
+ auto users = std::make_unique<Collection>("users");
|
|
|
|
|
+ (void)users->Create({{"name", "Alice"}, {"email", "alice@example.com"}}, "user-1");
|
|
|
|
|
+ (void)users->Create({{"name", "Bob"}, {"email", "bob@example.com"}}, "user-2");
|
|
|
|
|
+ provider->AddCollection(std::move(users));
|
|
|
|
|
+
|
|
|
|
|
+ // Create another collection
|
|
|
|
|
+ auto posts = std::make_unique<Collection>("posts");
|
|
|
|
|
+ (void)posts->Create({{"title", "Hello World"}, {"content", "First post"}}, "post-1");
|
|
|
|
|
+ provider->AddCollection(std::move(posts));
|
|
|
|
|
+
|
|
|
|
|
+ ASSERT_TRUE(manager.Start(provider));
|
|
|
|
|
+
|
|
|
|
|
+ auto result = manager.CreateSnapshotSync();
|
|
|
|
|
+ ASSERT_TRUE(result.IsOk());
|
|
|
|
|
+ EXPECT_EQ(result.value->collections, 2);
|
|
|
|
|
+ EXPECT_EQ(result.value->documents, 3);
|
|
|
|
|
+ EXPECT_GT(result.value->bytes_written, 0);
|
|
|
|
|
+
|
|
|
|
|
+ manager.Stop();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+TEST_F(SnapshotTest, LoadSnapshot_EmptyFile) {
|
|
|
|
|
+ auto config = CreateConfig();
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+
|
|
|
|
|
+ // No snapshot file exists
|
|
|
|
|
+ EXPECT_FALSE(manager.SnapshotExists());
|
|
|
|
|
+
|
|
|
|
|
+ auto result = manager.LoadSnapshot();
|
|
|
|
|
+ ASSERT_TRUE(result.IsOk());
|
|
|
|
|
+ EXPECT_TRUE(result.value->empty());
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+TEST_F(SnapshotTest, LoadSnapshot_RestoresData) {
|
|
|
|
|
+ auto config = CreateConfig();
|
|
|
|
|
+
|
|
|
|
|
+ // Create and save a snapshot
|
|
|
|
|
+ {
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+ auto provider = std::make_shared<MockCollectionProvider>();
|
|
|
|
|
+
|
|
|
|
|
+ auto users = std::make_unique<Collection>("users");
|
|
|
|
|
+ (void)users->Create({{"name", "Alice"}, {"age", 30}}, "user-1");
|
|
|
|
|
+ (void)users->Create({{"name", "Bob"}, {"age", 25}}, "user-2");
|
|
|
|
|
+ provider->AddCollection(std::move(users));
|
|
|
|
|
+
|
|
|
|
|
+ ASSERT_TRUE(manager.Start(provider));
|
|
|
|
|
+ auto result = manager.CreateSnapshotSync();
|
|
|
|
|
+ ASSERT_TRUE(result.IsOk());
|
|
|
|
|
+ manager.Stop();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Load the snapshot with a new manager
|
|
|
|
|
+ {
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+ auto result = manager.LoadSnapshot();
|
|
|
|
|
+ ASSERT_TRUE(result.IsOk());
|
|
|
|
|
+ EXPECT_EQ(result.value->size(), 1);
|
|
|
|
|
+
|
|
|
|
|
+ auto it = result.value->find("users");
|
|
|
|
|
+ ASSERT_NE(it, result.value->end());
|
|
|
|
|
+
|
|
|
|
|
+ const auto& [meta, documents] = it->second;
|
|
|
|
|
+ EXPECT_EQ(meta.name, "users");
|
|
|
|
|
+ EXPECT_EQ(documents.size(), 2);
|
|
|
|
|
+
|
|
|
|
|
+ // Verify document data
|
|
|
|
|
+ bool found_alice = false;
|
|
|
|
|
+ bool found_bob = false;
|
|
|
|
|
+ for (const auto& doc : documents) {
|
|
|
|
|
+ if (doc.GetId() == "user-1") {
|
|
|
|
|
+ EXPECT_EQ(doc.GetData()["name"], "Alice");
|
|
|
|
|
+ EXPECT_EQ(doc.GetData()["age"], 30);
|
|
|
|
|
+ found_alice = true;
|
|
|
|
|
+ } else if (doc.GetId() == "user-2") {
|
|
|
|
|
+ EXPECT_EQ(doc.GetData()["name"], "Bob");
|
|
|
|
|
+ EXPECT_EQ(doc.GetData()["age"], 25);
|
|
|
|
|
+ found_bob = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ EXPECT_TRUE(found_alice);
|
|
|
|
|
+ EXPECT_TRUE(found_bob);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+TEST_F(SnapshotTest, PreservesCollectionMetadata) {
|
|
|
|
|
+ auto config = CreateConfig();
|
|
|
|
|
+
|
|
|
|
|
+ // Create a collection with custom metadata
|
|
|
|
|
+ {
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+ auto provider = std::make_shared<MockCollectionProvider>();
|
|
|
|
|
+
|
|
|
|
|
+ auto users = std::make_unique<Collection>("users");
|
|
|
|
|
+
|
|
|
|
|
+ // Set TTL config
|
|
|
|
|
+ TTLConfig ttl;
|
|
|
|
|
+ ttl.enabled = true;
|
|
|
|
|
+ ttl.ttl_seconds = 3600;
|
|
|
|
|
+ ttl.field_name = "_createdAt";
|
|
|
|
|
+ users->SetTTLConfig(ttl);
|
|
|
|
|
+
|
|
|
|
|
+ // Set encryption config
|
|
|
|
|
+ EncryptionConfig encryption;
|
|
|
|
|
+ encryption.encrypted_fields = {"password", "ssn"};
|
|
|
|
|
+ encryption.key_id = "key-001";
|
|
|
|
|
+ users->SetEncryptionConfig(encryption);
|
|
|
|
|
+
|
|
|
|
|
+ (void)users->Create({{"name", "Test"}}, "user-1");
|
|
|
|
|
+ provider->AddCollection(std::move(users));
|
|
|
|
|
+
|
|
|
|
|
+ ASSERT_TRUE(manager.Start(provider));
|
|
|
|
|
+ auto result = manager.CreateSnapshotSync();
|
|
|
|
|
+ ASSERT_TRUE(result.IsOk());
|
|
|
|
|
+ manager.Stop();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Load and verify metadata
|
|
|
|
|
+ {
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+ auto result = manager.LoadSnapshot();
|
|
|
|
|
+ ASSERT_TRUE(result.IsOk());
|
|
|
|
|
+
|
|
|
|
|
+ auto it = result.value->find("users");
|
|
|
|
|
+ ASSERT_NE(it, result.value->end());
|
|
|
|
|
+
|
|
|
|
|
+ const auto& meta = it->second.first;
|
|
|
|
|
+ EXPECT_EQ(meta.name, "users");
|
|
|
|
|
+
|
|
|
|
|
+ // Check TTL config
|
|
|
|
|
+ EXPECT_TRUE(meta.ttl_config.enabled);
|
|
|
|
|
+ EXPECT_EQ(meta.ttl_config.ttl_seconds, 3600);
|
|
|
|
|
+ EXPECT_EQ(meta.ttl_config.field_name, "_createdAt");
|
|
|
|
|
+
|
|
|
|
|
+ // Check encryption config
|
|
|
|
|
+ EXPECT_EQ(meta.encryption.key_id, "key-001");
|
|
|
|
|
+ EXPECT_TRUE(meta.encryption.encrypted_fields.contains("password"));
|
|
|
|
|
+ EXPECT_TRUE(meta.encryption.encrypted_fields.contains("ssn"));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+// Async and Background Thread Tests
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+
|
|
|
|
|
+TEST_F(SnapshotTest, StartWithNullProvider_Fails) {
|
|
|
|
|
+ auto config = CreateConfig();
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+
|
|
|
|
|
+ EXPECT_FALSE(manager.Start(nullptr));
|
|
|
|
|
+ EXPECT_EQ(manager.GetStatus(), SnapshotManagerStatus::kStopped);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+TEST_F(SnapshotTest, DoubleStart_Fails) {
|
|
|
|
|
+ auto config = CreateConfig();
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+
|
|
|
|
|
+ auto provider = std::make_shared<MockCollectionProvider>();
|
|
|
|
|
+
|
|
|
|
|
+ EXPECT_TRUE(manager.Start(provider));
|
|
|
|
|
+ EXPECT_FALSE(manager.Start(provider));
|
|
|
|
|
+
|
|
|
|
|
+ manager.Stop();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+TEST_F(SnapshotTest, RequestSnapshot_TriggersAsync) {
|
|
|
|
|
+ auto config = CreateConfig();
|
|
|
|
|
+ config.interval = std::chrono::seconds(3600); // Very long interval
|
|
|
|
|
+
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+ auto provider = std::make_shared<MockCollectionProvider>();
|
|
|
|
|
+
|
|
|
|
|
+ auto users = std::make_unique<Collection>("users");
|
|
|
|
|
+ (void)users->Create({{"name", "Test"}}, "user-1");
|
|
|
|
|
+ provider->AddCollection(std::move(users));
|
|
|
|
|
+
|
|
|
|
|
+ ASSERT_TRUE(manager.Start(provider));
|
|
|
|
|
+ EXPECT_FALSE(manager.SnapshotExists());
|
|
|
|
|
+
|
|
|
|
|
+ // Request an async snapshot
|
|
|
|
|
+ manager.RequestSnapshot();
|
|
|
|
|
+
|
|
|
|
|
+ // Wait for the snapshot to complete (with timeout)
|
|
|
|
|
+ int attempts = 0;
|
|
|
|
|
+ while (!manager.SnapshotExists() && attempts < 50) {
|
|
|
|
|
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
|
+ ++attempts;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ EXPECT_TRUE(manager.SnapshotExists());
|
|
|
|
|
+ EXPECT_EQ(manager.GetPendingChanges(), 0);
|
|
|
|
|
+
|
|
|
|
|
+ manager.Stop();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+TEST_F(SnapshotTest, ChangeThreshold_TriggersSnapshot) {
|
|
|
|
|
+ auto config = CreateConfig();
|
|
|
|
|
+ config.interval = std::chrono::seconds(3600); // Very long interval
|
|
|
|
|
+ config.change_threshold = 5; // Trigger after 5 changes
|
|
|
|
|
+
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+ auto provider = std::make_shared<MockCollectionProvider>();
|
|
|
|
|
+
|
|
|
|
|
+ auto users = std::make_unique<Collection>("users");
|
|
|
|
|
+ provider->AddCollection(std::move(users));
|
|
|
|
|
+
|
|
|
|
|
+ ASSERT_TRUE(manager.Start(provider));
|
|
|
|
|
+ EXPECT_FALSE(manager.SnapshotExists());
|
|
|
|
|
+
|
|
|
|
|
+ // Notify changes below threshold
|
|
|
|
|
+ manager.NotifyChanges(3);
|
|
|
|
|
+ EXPECT_EQ(manager.GetPendingChanges(), 3);
|
|
|
|
|
+
|
|
|
|
|
+ // Add more to exceed threshold
|
|
|
|
|
+ manager.NotifyChanges(3); // Now at 6, exceeds 5
|
|
|
|
|
+
|
|
|
|
|
+ // Wait for the snapshot to complete
|
|
|
|
|
+ int attempts = 0;
|
|
|
|
|
+ while (!manager.SnapshotExists() && attempts < 50) {
|
|
|
|
|
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
|
+ ++attempts;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ EXPECT_TRUE(manager.SnapshotExists());
|
|
|
|
|
+ EXPECT_EQ(manager.GetPendingChanges(), 0);
|
|
|
|
|
+
|
|
|
|
|
+ manager.Stop();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+TEST_F(SnapshotTest, GracefulShutdown_CreatesSnapshot) {
|
|
|
|
|
+ auto config = CreateConfig();
|
|
|
|
|
+ config.interval = std::chrono::seconds(3600); // Very long interval
|
|
|
|
|
+
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+ auto provider = std::make_shared<MockCollectionProvider>();
|
|
|
|
|
+
|
|
|
|
|
+ auto users = std::make_unique<Collection>("users");
|
|
|
|
|
+ (void)users->Create({{"name", "Final"}}, "user-1");
|
|
|
|
|
+ provider->AddCollection(std::move(users));
|
|
|
|
|
+
|
|
|
|
|
+ ASSERT_TRUE(manager.Start(provider));
|
|
|
|
|
+ EXPECT_FALSE(manager.SnapshotExists());
|
|
|
|
|
+
|
|
|
|
|
+ // Stop should create a final snapshot
|
|
|
|
|
+ manager.Stop();
|
|
|
|
|
+
|
|
|
|
|
+ EXPECT_TRUE(manager.SnapshotExists());
|
|
|
|
|
+ EXPECT_EQ(manager.GetStatus(), SnapshotManagerStatus::kStopped);
|
|
|
|
|
+
|
|
|
|
|
+ // Verify the snapshot contains the data
|
|
|
|
|
+ auto result = manager.LoadSnapshot();
|
|
|
|
|
+ ASSERT_TRUE(result.IsOk());
|
|
|
|
|
+ EXPECT_EQ(result.value->size(), 1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+// Statistics Tests
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+
|
|
|
|
|
+TEST_F(SnapshotTest, LastSnapshotStats_Updated) {
|
|
|
|
|
+ auto config = CreateConfig();
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+
|
|
|
|
|
+ auto provider = std::make_shared<MockCollectionProvider>();
|
|
|
|
|
+
|
|
|
|
|
+ auto users = std::make_unique<Collection>("users");
|
|
|
|
|
+ (void)users->Create({{"name", "Test"}}, "user-1");
|
|
|
|
|
+ provider->AddCollection(std::move(users));
|
|
|
|
|
+
|
|
|
|
|
+ ASSERT_TRUE(manager.Start(provider));
|
|
|
|
|
+
|
|
|
|
|
+ EXPECT_FALSE(manager.GetLastSnapshotStats().has_value());
|
|
|
|
|
+
|
|
|
|
|
+ auto result = manager.CreateSnapshotSync();
|
|
|
|
|
+ ASSERT_TRUE(result.IsOk());
|
|
|
|
|
+
|
|
|
|
|
+ auto stats = manager.GetLastSnapshotStats();
|
|
|
|
|
+ ASSERT_TRUE(stats.has_value());
|
|
|
|
|
+ EXPECT_EQ(stats->collections, 1);
|
|
|
|
|
+ EXPECT_EQ(stats->documents, 1);
|
|
|
|
|
+ EXPECT_GT(stats->bytes_written, 0);
|
|
|
|
|
+
|
|
|
|
|
+ manager.Stop();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+// Edge Cases
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+
|
|
|
|
|
+TEST_F(SnapshotTest, EmptyCollection) {
|
|
|
|
|
+ auto config = CreateConfig();
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+
|
|
|
|
|
+ auto provider = std::make_shared<MockCollectionProvider>();
|
|
|
|
|
+ provider->AddCollection(std::make_unique<Collection>("empty"));
|
|
|
|
|
+
|
|
|
|
|
+ ASSERT_TRUE(manager.Start(provider));
|
|
|
|
|
+
|
|
|
|
|
+ auto result = manager.CreateSnapshotSync();
|
|
|
|
|
+ ASSERT_TRUE(result.IsOk());
|
|
|
|
|
+ EXPECT_EQ(result.value->collections, 1);
|
|
|
|
|
+ EXPECT_EQ(result.value->documents, 0);
|
|
|
|
|
+
|
|
|
|
|
+ manager.Stop();
|
|
|
|
|
+
|
|
|
|
|
+ // Reload and verify
|
|
|
|
|
+ auto load_result = manager.LoadSnapshot();
|
|
|
|
|
+ ASSERT_TRUE(load_result.IsOk());
|
|
|
|
|
+ EXPECT_EQ(load_result.value->size(), 1);
|
|
|
|
|
+ EXPECT_TRUE(load_result.value->at("empty").second.empty());
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+TEST_F(SnapshotTest, LargeDocuments) {
|
|
|
|
|
+ auto config = CreateConfig();
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+
|
|
|
|
|
+ auto provider = std::make_shared<MockCollectionProvider>();
|
|
|
|
|
+
|
|
|
|
|
+ auto data = std::make_unique<Collection>("data");
|
|
|
|
|
+
|
|
|
|
|
+ // Create a document with a large string
|
|
|
|
|
+ std::string large_content(100000, 'x');
|
|
|
|
|
+ (void)data->Create({{"content", large_content}}, "large-doc");
|
|
|
|
|
+ provider->AddCollection(std::move(data));
|
|
|
|
|
+
|
|
|
|
|
+ ASSERT_TRUE(manager.Start(provider));
|
|
|
|
|
+
|
|
|
|
|
+ auto result = manager.CreateSnapshotSync();
|
|
|
|
|
+ ASSERT_TRUE(result.IsOk());
|
|
|
|
|
+
|
|
|
|
|
+ manager.Stop();
|
|
|
|
|
+
|
|
|
|
|
+ // Reload and verify
|
|
|
|
|
+ auto load_result = manager.LoadSnapshot();
|
|
|
|
|
+ ASSERT_TRUE(load_result.IsOk());
|
|
|
|
|
+ const auto& docs = load_result.value->at("data").second;
|
|
|
|
|
+ EXPECT_EQ(docs.size(), 1);
|
|
|
|
|
+ EXPECT_EQ(docs[0].GetData()["content"].get<std::string>().size(), 100000);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+TEST_F(SnapshotTest, SystemCollection) {
|
|
|
|
|
+ auto config = CreateConfig();
|
|
|
|
|
+ SnapshotManager manager(config);
|
|
|
|
|
+
|
|
|
|
|
+ auto provider = std::make_shared<MockCollectionProvider>();
|
|
|
|
|
+
|
|
|
|
|
+ // System collections start with underscore
|
|
|
|
|
+ auto system_col = std::make_unique<Collection>("_sessions");
|
|
|
|
|
+ (void)system_col->Create({{"token", "abc123"}}, "session-1");
|
|
|
|
|
+ provider->AddCollection(std::move(system_col));
|
|
|
|
|
+
|
|
|
|
|
+ ASSERT_TRUE(manager.Start(provider));
|
|
|
|
|
+
|
|
|
|
|
+ auto result = manager.CreateSnapshotSync();
|
|
|
|
|
+ ASSERT_TRUE(result.IsOk());
|
|
|
|
|
+
|
|
|
|
|
+ manager.Stop();
|
|
|
|
|
+
|
|
|
|
|
+ // Reload and verify system collection is preserved
|
|
|
|
|
+ auto load_result = manager.LoadSnapshot();
|
|
|
|
|
+ ASSERT_TRUE(load_result.IsOk());
|
|
|
|
|
+ ASSERT_EQ(load_result.value->size(), 1);
|
|
|
|
|
+
|
|
|
|
|
+ const auto& meta = load_result.value->at("_sessions").first;
|
|
|
|
|
+ EXPECT_TRUE(meta.is_system);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+} // namespace
|
|
|
|
|
+} // namespace smartbotic::database
|