snapshot_test.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. #include <gtest/gtest.h>
  2. #include <filesystem>
  3. #include <thread>
  4. #include "smartbotic/database/snapshot.hpp"
  5. namespace smartbotic::database::storage {
  6. namespace {
  7. /// Mock collection provider for testing
  8. class MockCollectionProvider : public ICollectionProvider {
  9. public:
  10. void AddCollection(std::unique_ptr<Collection> collection) {
  11. std::string name(collection->GetName());
  12. collections_.emplace(name, std::move(collection));
  13. }
  14. [[nodiscard]] auto GetCollectionNames() const -> std::vector<std::string> override {
  15. std::vector<std::string> names;
  16. names.reserve(collections_.size());
  17. for (const auto& [name, _] : collections_) {
  18. names.push_back(name);
  19. }
  20. return names;
  21. }
  22. [[nodiscard]] auto GetCollection(std::string_view name) const -> const Collection* override {
  23. auto it = collections_.find(std::string(name));
  24. if (it == collections_.end()) {
  25. return nullptr;
  26. }
  27. return it->second.get();
  28. }
  29. [[nodiscard]] auto GetAllMetadata() const -> std::unordered_map<std::string, CollectionMeta> override {
  30. std::unordered_map<std::string, CollectionMeta> result;
  31. for (const auto& [name, collection] : collections_) {
  32. result.emplace(name, collection->GetMetadata());
  33. }
  34. return result;
  35. }
  36. private:
  37. std::unordered_map<std::string, std::unique_ptr<Collection>> collections_;
  38. };
  39. class SnapshotTest : public ::testing::Test {
  40. protected:
  41. void SetUp() override {
  42. // Use a unique test directory for each test
  43. test_dir_ = std::filesystem::temp_directory_path() / ("snapshot_test_" + std::to_string(std::time(nullptr)));
  44. std::filesystem::create_directories(test_dir_);
  45. }
  46. void TearDown() override {
  47. // Clean up test directory
  48. std::error_code ec;
  49. std::filesystem::remove_all(test_dir_, ec);
  50. }
  51. [[nodiscard]] auto CreateConfig() -> SnapshotConfig {
  52. SnapshotConfig config;
  53. config.snapshot_dir = test_dir_.string();
  54. config.interval = std::chrono::seconds(3600); // Long interval for tests
  55. config.change_threshold = 0; // Disable threshold for tests
  56. config.enabled = true;
  57. return config;
  58. }
  59. std::filesystem::path test_dir_;
  60. };
  61. // ============================================================================
  62. // Basic Snapshot Tests
  63. // ============================================================================
  64. TEST_F(SnapshotTest, CreateSnapshotSync_EmptyDatabase) {
  65. auto config = CreateConfig();
  66. SnapshotManager manager(config);
  67. auto provider = std::make_shared<MockCollectionProvider>();
  68. ASSERT_TRUE(manager.Start(provider));
  69. EXPECT_EQ(manager.GetStatus(), SnapshotManagerStatus::kRunning);
  70. auto result = manager.CreateSnapshotSync();
  71. ASSERT_TRUE(result.IsOk());
  72. EXPECT_EQ(result.value->collections, 0);
  73. EXPECT_EQ(result.value->documents, 0);
  74. // Snapshot file should exist
  75. EXPECT_TRUE(manager.SnapshotExists());
  76. manager.Stop();
  77. EXPECT_EQ(manager.GetStatus(), SnapshotManagerStatus::kStopped);
  78. }
  79. TEST_F(SnapshotTest, CreateSnapshotSync_WithCollections) {
  80. auto config = CreateConfig();
  81. SnapshotManager manager(config);
  82. auto provider = std::make_shared<MockCollectionProvider>();
  83. // Create a collection with some documents
  84. auto users = std::make_unique<Collection>("users");
  85. (void)users->Create({{"name", "Alice"}, {"email", "alice@example.com"}}, "user-1");
  86. (void)users->Create({{"name", "Bob"}, {"email", "bob@example.com"}}, "user-2");
  87. provider->AddCollection(std::move(users));
  88. // Create another collection
  89. auto posts = std::make_unique<Collection>("posts");
  90. (void)posts->Create({{"title", "Hello World"}, {"content", "First post"}}, "post-1");
  91. provider->AddCollection(std::move(posts));
  92. ASSERT_TRUE(manager.Start(provider));
  93. auto result = manager.CreateSnapshotSync();
  94. ASSERT_TRUE(result.IsOk());
  95. EXPECT_EQ(result.value->collections, 2);
  96. EXPECT_EQ(result.value->documents, 3);
  97. EXPECT_GT(result.value->bytes_written, 0);
  98. manager.Stop();
  99. }
  100. TEST_F(SnapshotTest, LoadSnapshot_EmptyFile) {
  101. auto config = CreateConfig();
  102. SnapshotManager manager(config);
  103. // No snapshot file exists
  104. EXPECT_FALSE(manager.SnapshotExists());
  105. auto result = manager.LoadSnapshot();
  106. ASSERT_TRUE(result.IsOk());
  107. EXPECT_TRUE(result.value->empty());
  108. }
  109. TEST_F(SnapshotTest, LoadSnapshot_RestoresData) {
  110. auto config = CreateConfig();
  111. // Create and save a snapshot
  112. {
  113. SnapshotManager manager(config);
  114. auto provider = std::make_shared<MockCollectionProvider>();
  115. auto users = std::make_unique<Collection>("users");
  116. (void)users->Create({{"name", "Alice"}, {"age", 30}}, "user-1");
  117. (void)users->Create({{"name", "Bob"}, {"age", 25}}, "user-2");
  118. provider->AddCollection(std::move(users));
  119. ASSERT_TRUE(manager.Start(provider));
  120. auto result = manager.CreateSnapshotSync();
  121. ASSERT_TRUE(result.IsOk());
  122. manager.Stop();
  123. }
  124. // Load the snapshot with a new manager
  125. {
  126. SnapshotManager manager(config);
  127. auto result = manager.LoadSnapshot();
  128. ASSERT_TRUE(result.IsOk());
  129. EXPECT_EQ(result.value->size(), 1);
  130. auto it = result.value->find("users");
  131. ASSERT_NE(it, result.value->end());
  132. const auto& [meta, documents] = it->second;
  133. EXPECT_EQ(meta.name, "users");
  134. EXPECT_EQ(documents.size(), 2);
  135. // Verify document data
  136. bool found_alice = false;
  137. bool found_bob = false;
  138. for (const auto& doc : documents) {
  139. if (doc.GetId() == "user-1") {
  140. EXPECT_EQ(doc.GetData()["name"], "Alice");
  141. EXPECT_EQ(doc.GetData()["age"], 30);
  142. found_alice = true;
  143. } else if (doc.GetId() == "user-2") {
  144. EXPECT_EQ(doc.GetData()["name"], "Bob");
  145. EXPECT_EQ(doc.GetData()["age"], 25);
  146. found_bob = true;
  147. }
  148. }
  149. EXPECT_TRUE(found_alice);
  150. EXPECT_TRUE(found_bob);
  151. }
  152. }
  153. TEST_F(SnapshotTest, PreservesCollectionMetadata) {
  154. auto config = CreateConfig();
  155. // Create a collection with custom metadata
  156. {
  157. SnapshotManager manager(config);
  158. auto provider = std::make_shared<MockCollectionProvider>();
  159. auto users = std::make_unique<Collection>("users");
  160. // Set TTL config
  161. TTLConfig ttl;
  162. ttl.enabled = true;
  163. ttl.ttl_seconds = 3600;
  164. ttl.field_name = "_createdAt";
  165. users->SetTTLConfig(ttl);
  166. // Set encryption config
  167. EncryptionConfig encryption;
  168. encryption.encrypted_fields = {"password", "ssn"};
  169. encryption.key_id = "key-001";
  170. users->SetEncryptionConfig(encryption);
  171. (void)users->Create({{"name", "Test"}}, "user-1");
  172. provider->AddCollection(std::move(users));
  173. ASSERT_TRUE(manager.Start(provider));
  174. auto result = manager.CreateSnapshotSync();
  175. ASSERT_TRUE(result.IsOk());
  176. manager.Stop();
  177. }
  178. // Load and verify metadata
  179. {
  180. SnapshotManager manager(config);
  181. auto result = manager.LoadSnapshot();
  182. ASSERT_TRUE(result.IsOk());
  183. auto it = result.value->find("users");
  184. ASSERT_NE(it, result.value->end());
  185. const auto& meta = it->second.first;
  186. EXPECT_EQ(meta.name, "users");
  187. // Check TTL config
  188. EXPECT_TRUE(meta.ttl_config.enabled);
  189. EXPECT_EQ(meta.ttl_config.ttl_seconds, 3600);
  190. EXPECT_EQ(meta.ttl_config.field_name, "_createdAt");
  191. // Check encryption config
  192. EXPECT_EQ(meta.encryption.key_id, "key-001");
  193. EXPECT_TRUE(meta.encryption.encrypted_fields.contains("password"));
  194. EXPECT_TRUE(meta.encryption.encrypted_fields.contains("ssn"));
  195. }
  196. }
  197. // ============================================================================
  198. // Async and Background Thread Tests
  199. // ============================================================================
  200. TEST_F(SnapshotTest, StartWithNullProvider_Fails) {
  201. auto config = CreateConfig();
  202. SnapshotManager manager(config);
  203. EXPECT_FALSE(manager.Start(nullptr));
  204. EXPECT_EQ(manager.GetStatus(), SnapshotManagerStatus::kStopped);
  205. }
  206. TEST_F(SnapshotTest, DoubleStart_Fails) {
  207. auto config = CreateConfig();
  208. SnapshotManager manager(config);
  209. auto provider = std::make_shared<MockCollectionProvider>();
  210. EXPECT_TRUE(manager.Start(provider));
  211. EXPECT_FALSE(manager.Start(provider));
  212. manager.Stop();
  213. }
  214. TEST_F(SnapshotTest, RequestSnapshot_TriggersAsync) {
  215. auto config = CreateConfig();
  216. config.interval = std::chrono::seconds(3600); // Very long interval
  217. SnapshotManager manager(config);
  218. auto provider = std::make_shared<MockCollectionProvider>();
  219. auto users = std::make_unique<Collection>("users");
  220. (void)users->Create({{"name", "Test"}}, "user-1");
  221. provider->AddCollection(std::move(users));
  222. ASSERT_TRUE(manager.Start(provider));
  223. EXPECT_FALSE(manager.SnapshotExists());
  224. // Request an async snapshot
  225. manager.RequestSnapshot();
  226. // Wait for the snapshot to complete (with timeout)
  227. int attempts = 0;
  228. while (!manager.SnapshotExists() && attempts < 50) {
  229. std::this_thread::sleep_for(std::chrono::milliseconds(100));
  230. ++attempts;
  231. }
  232. EXPECT_TRUE(manager.SnapshotExists());
  233. EXPECT_EQ(manager.GetPendingChanges(), 0);
  234. manager.Stop();
  235. }
  236. TEST_F(SnapshotTest, ChangeThreshold_TriggersSnapshot) {
  237. auto config = CreateConfig();
  238. config.interval = std::chrono::seconds(3600); // Very long interval
  239. config.change_threshold = 5; // Trigger after 5 changes
  240. SnapshotManager manager(config);
  241. auto provider = std::make_shared<MockCollectionProvider>();
  242. auto users = std::make_unique<Collection>("users");
  243. provider->AddCollection(std::move(users));
  244. ASSERT_TRUE(manager.Start(provider));
  245. EXPECT_FALSE(manager.SnapshotExists());
  246. // Notify changes below threshold
  247. manager.NotifyChanges(3);
  248. EXPECT_EQ(manager.GetPendingChanges(), 3);
  249. // Add more to exceed threshold
  250. manager.NotifyChanges(3); // Now at 6, exceeds 5
  251. // Wait for the snapshot to complete
  252. int attempts = 0;
  253. while (!manager.SnapshotExists() && attempts < 50) {
  254. std::this_thread::sleep_for(std::chrono::milliseconds(100));
  255. ++attempts;
  256. }
  257. EXPECT_TRUE(manager.SnapshotExists());
  258. EXPECT_EQ(manager.GetPendingChanges(), 0);
  259. manager.Stop();
  260. }
  261. TEST_F(SnapshotTest, GracefulShutdown_CreatesSnapshot) {
  262. auto config = CreateConfig();
  263. config.interval = std::chrono::seconds(3600); // Very long interval
  264. SnapshotManager manager(config);
  265. auto provider = std::make_shared<MockCollectionProvider>();
  266. auto users = std::make_unique<Collection>("users");
  267. (void)users->Create({{"name", "Final"}}, "user-1");
  268. provider->AddCollection(std::move(users));
  269. ASSERT_TRUE(manager.Start(provider));
  270. EXPECT_FALSE(manager.SnapshotExists());
  271. // Stop should create a final snapshot
  272. manager.Stop();
  273. EXPECT_TRUE(manager.SnapshotExists());
  274. EXPECT_EQ(manager.GetStatus(), SnapshotManagerStatus::kStopped);
  275. // Verify the snapshot contains the data
  276. auto result = manager.LoadSnapshot();
  277. ASSERT_TRUE(result.IsOk());
  278. EXPECT_EQ(result.value->size(), 1);
  279. }
  280. // ============================================================================
  281. // Statistics Tests
  282. // ============================================================================
  283. TEST_F(SnapshotTest, LastSnapshotStats_Updated) {
  284. auto config = CreateConfig();
  285. SnapshotManager manager(config);
  286. auto provider = std::make_shared<MockCollectionProvider>();
  287. auto users = std::make_unique<Collection>("users");
  288. (void)users->Create({{"name", "Test"}}, "user-1");
  289. provider->AddCollection(std::move(users));
  290. ASSERT_TRUE(manager.Start(provider));
  291. EXPECT_FALSE(manager.GetLastSnapshotStats().has_value());
  292. auto result = manager.CreateSnapshotSync();
  293. ASSERT_TRUE(result.IsOk());
  294. auto stats = manager.GetLastSnapshotStats();
  295. ASSERT_TRUE(stats.has_value());
  296. EXPECT_EQ(stats->collections, 1);
  297. EXPECT_EQ(stats->documents, 1);
  298. EXPECT_GT(stats->bytes_written, 0);
  299. manager.Stop();
  300. }
  301. // ============================================================================
  302. // Edge Cases
  303. // ============================================================================
  304. TEST_F(SnapshotTest, EmptyCollection) {
  305. auto config = CreateConfig();
  306. SnapshotManager manager(config);
  307. auto provider = std::make_shared<MockCollectionProvider>();
  308. provider->AddCollection(std::make_unique<Collection>("empty"));
  309. ASSERT_TRUE(manager.Start(provider));
  310. auto result = manager.CreateSnapshotSync();
  311. ASSERT_TRUE(result.IsOk());
  312. EXPECT_EQ(result.value->collections, 1);
  313. EXPECT_EQ(result.value->documents, 0);
  314. manager.Stop();
  315. // Reload and verify
  316. auto load_result = manager.LoadSnapshot();
  317. ASSERT_TRUE(load_result.IsOk());
  318. EXPECT_EQ(load_result.value->size(), 1);
  319. EXPECT_TRUE(load_result.value->at("empty").second.empty());
  320. }
  321. TEST_F(SnapshotTest, LargeDocuments) {
  322. auto config = CreateConfig();
  323. SnapshotManager manager(config);
  324. auto provider = std::make_shared<MockCollectionProvider>();
  325. auto data = std::make_unique<Collection>("data");
  326. // Create a document with a large string
  327. std::string large_content(100000, 'x');
  328. (void)data->Create({{"content", large_content}}, "large-doc");
  329. provider->AddCollection(std::move(data));
  330. ASSERT_TRUE(manager.Start(provider));
  331. auto result = manager.CreateSnapshotSync();
  332. ASSERT_TRUE(result.IsOk());
  333. manager.Stop();
  334. // Reload and verify
  335. auto load_result = manager.LoadSnapshot();
  336. ASSERT_TRUE(load_result.IsOk());
  337. const auto& docs = load_result.value->at("data").second;
  338. EXPECT_EQ(docs.size(), 1);
  339. EXPECT_EQ(docs[0].GetData()["content"].get<std::string>().size(), 100000);
  340. }
  341. TEST_F(SnapshotTest, SystemCollection) {
  342. auto config = CreateConfig();
  343. SnapshotManager manager(config);
  344. auto provider = std::make_shared<MockCollectionProvider>();
  345. // System collections start with underscore
  346. auto system_col = std::make_unique<Collection>("_sessions");
  347. (void)system_col->Create({{"token", "abc123"}}, "session-1");
  348. provider->AddCollection(std::move(system_col));
  349. ASSERT_TRUE(manager.Start(provider));
  350. auto result = manager.CreateSnapshotSync();
  351. ASSERT_TRUE(result.IsOk());
  352. manager.Stop();
  353. // Reload and verify system collection is preserved
  354. auto load_result = manager.LoadSnapshot();
  355. ASSERT_TRUE(load_result.IsOk());
  356. ASSERT_EQ(load_result.value->size(), 1);
  357. const auto& meta = load_result.value->at("_sessions").first;
  358. EXPECT_TRUE(meta.is_system);
  359. }
  360. } // namespace
  361. } // namespace smartbotic::database::storage