1use crate::database::VirtualDatabase;
7use crate::entities::EntityRegistry;
8use crate::{Error, Result};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use std::collections::HashMap;
12use std::path::{Path, PathBuf};
13use tokio::fs;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct SnapshotMetadata {
18 pub name: String,
20 pub created_at: chrono::DateTime<chrono::Utc>,
22 pub description: Option<String>,
24 pub entity_counts: HashMap<String, usize>,
26 pub database_size: Option<u64>,
28 pub storage_backend: String,
30 #[serde(default)]
32 pub time_travel_state: Option<TimeTravelSnapshotState>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct TimeTravelSnapshotState {
38 pub enabled: bool,
40 pub current_time: Option<chrono::DateTime<chrono::Utc>>,
42 pub scale_factor: f64,
44 #[serde(default)]
46 pub cron_jobs: Vec<serde_json::Value>,
47 #[serde(default)]
49 pub mutation_rules: Vec<serde_json::Value>,
50}
51
52pub struct SnapshotManager {
54 snapshots_dir: PathBuf,
56}
57
58impl SnapshotManager {
59 pub fn new<P: AsRef<Path>>(snapshots_dir: P) -> Self {
64 Self {
65 snapshots_dir: snapshots_dir.as_ref().to_path_buf(),
66 }
67 }
68
69 fn snapshot_path(&self, name: &str) -> PathBuf {
71 self.snapshots_dir.join(name)
72 }
73
74 fn metadata_path(&self, name: &str) -> PathBuf {
76 self.snapshot_path(name).join("metadata.json")
77 }
78
79 pub async fn create_snapshot(
89 &self,
90 name: &str,
91 description: Option<String>,
92 database: &dyn crate::database::VirtualDatabase,
93 registry: &EntityRegistry,
94 ) -> Result<SnapshotMetadata> {
95 self.create_snapshot_with_time_travel(name, description, database, registry, false, None)
96 .await
97 }
98
99 pub async fn create_snapshot_with_time_travel(
109 &self,
110 name: &str,
111 description: Option<String>,
112 database: &dyn crate::database::VirtualDatabase,
113 registry: &EntityRegistry,
114 include_time_travel: bool,
115 time_travel_state: Option<TimeTravelSnapshotState>,
116 ) -> Result<SnapshotMetadata> {
117 let snapshot_dir = self.snapshot_path(name);
119 fs::create_dir_all(&snapshot_dir)
120 .await
121 .map_err(|e| Error::generic(format!("Failed to create snapshot directory: {}", e)))?;
122
123 let mut entity_counts = HashMap::new();
125 for entity_name in registry.list() {
126 if let Some(entity) = registry.get(&entity_name) {
127 let table_name = entity.table_name();
128 let count_query = format!("SELECT COUNT(*) as count FROM {}", table_name);
129 let results = database.query(&count_query, &[]).await?;
130 let count = results
131 .first()
132 .and_then(|r| r.get("count"))
133 .and_then(|v| v.as_u64())
134 .map(|v| v as usize)
135 .unwrap_or(0);
136 entity_counts.insert(entity_name.clone(), count);
137 }
138 }
139
140 let storage_backend = database.connection_info();
142 let database_size = self.create_snapshot_data(name, database, registry).await?;
143
144 let metadata = SnapshotMetadata {
146 name: name.to_string(),
147 created_at: chrono::Utc::now(),
148 description,
149 time_travel_state: if include_time_travel {
150 time_travel_state
151 } else {
152 None
153 },
154 entity_counts,
155 database_size,
156 storage_backend,
157 };
158
159 let metadata_json = serde_json::to_string_pretty(&metadata)
161 .map_err(|e| Error::generic(format!("Failed to serialize metadata: {}", e)))?;
162 fs::write(self.metadata_path(name), metadata_json)
163 .await
164 .map_err(|e| Error::generic(format!("Failed to write metadata: {}", e)))?;
165
166 Ok(metadata)
167 }
168
169 async fn create_snapshot_data(
171 &self,
172 name: &str,
173 database: &dyn crate::database::VirtualDatabase,
174 registry: &EntityRegistry,
175 ) -> Result<Option<u64>> {
176 let snapshot_dir = self.snapshot_path(name);
177 let storage_backend = database.connection_info().to_lowercase();
178
179 if storage_backend.contains("sqlite") {
180 self.export_sqlite_to_json(&snapshot_dir, database, registry).await?;
183 Ok(None) } else if storage_backend.contains("json") {
185 Ok(None)
188 } else {
189 self.export_memory_to_json(&snapshot_dir, database, registry).await?;
191 Ok(None)
192 }
193 }
194
195 async fn export_sqlite_to_json(
197 &self,
198 snapshot_dir: &Path,
199 database: &dyn crate::database::VirtualDatabase,
200 registry: &EntityRegistry,
201 ) -> Result<()> {
202 let data_dir = snapshot_dir.join("data");
203 fs::create_dir_all(&data_dir)
204 .await
205 .map_err(|e| Error::generic(format!("Failed to create data directory: {}", e)))?;
206
207 for entity_name in registry.list() {
209 if let Some(entity) = registry.get(&entity_name) {
210 let table_name = entity.table_name();
211 let query = format!("SELECT * FROM {}", table_name);
212 let records = database.query(&query, &[]).await?;
213
214 let json_file = data_dir.join(format!("{}.json", entity_name.to_lowercase()));
215 let json_content = serde_json::to_string_pretty(&records)
216 .map_err(|e| Error::generic(format!("Failed to serialize data: {}", e)))?;
217 fs::write(&json_file, json_content)
218 .await
219 .map_err(|e| Error::generic(format!("Failed to write data file: {}", e)))?;
220 }
221 }
222
223 Ok(())
224 }
225
226 async fn export_memory_to_json(
228 &self,
229 snapshot_dir: &Path,
230 database: &dyn crate::database::VirtualDatabase,
231 registry: &EntityRegistry,
232 ) -> Result<()> {
233 self.export_sqlite_to_json(snapshot_dir, database, registry).await
234 }
235
236 pub async fn restore_snapshot(
243 &self,
244 name: &str,
245 database: &dyn crate::database::VirtualDatabase,
246 registry: &EntityRegistry,
247 ) -> Result<()> {
248 self.restore_snapshot_with_time_travel(
249 name,
250 database,
251 registry,
252 false,
253 None::<fn(TimeTravelSnapshotState) -> Result<()>>,
254 )
255 .await
256 }
257
258 pub async fn restore_snapshot_with_time_travel<F>(
267 &self,
268 name: &str,
269 database: &dyn crate::database::VirtualDatabase,
270 registry: &EntityRegistry,
271 restore_time_travel: bool,
272 time_travel_restore_callback: Option<F>,
273 ) -> Result<()>
274 where
275 F: FnOnce(TimeTravelSnapshotState) -> Result<()>,
276 {
277 let metadata = self.get_snapshot_metadata(name).await?;
279
280 self.reset_database(database, registry).await?;
282
283 let snapshot_dir = self.snapshot_path(name);
285 let storage_backend_lower = metadata.storage_backend.to_lowercase();
286 if storage_backend_lower.contains("sqlite") || storage_backend_lower.contains("memory") {
287 self.import_json_to_database(&snapshot_dir, database, registry).await?;
288 } else if storage_backend_lower.contains("json") {
289 return Err(Error::generic(
292 "JSON backend snapshot restore not yet implemented".to_string(),
293 ));
294 }
295
296 if restore_time_travel {
298 if let Some(ref time_travel_state) = metadata.time_travel_state {
299 if let Some(callback) = time_travel_restore_callback {
300 callback(time_travel_state.clone())?;
301 }
302 }
303 }
304
305 Ok(())
306 }
307
308 async fn import_json_to_database(
310 &self,
311 snapshot_dir: &Path,
312 database: &dyn crate::database::VirtualDatabase,
313 registry: &EntityRegistry,
314 ) -> Result<()> {
315 let data_dir = snapshot_dir.join("data");
316
317 if !data_dir.exists() {
318 return Err(Error::generic("Snapshot data directory not found".to_string()));
319 }
320
321 for entity_name in registry.list() {
323 let json_file = data_dir.join(format!("{}.json", entity_name.to_lowercase()));
324 if !json_file.exists() {
325 continue;
327 }
328
329 {
330 let content = fs::read_to_string(&json_file)
331 .await
332 .map_err(|e| Error::generic(format!("Failed to read data file: {}", e)))?;
333
334 let records: Vec<HashMap<String, Value>> = serde_json::from_str(&content)
335 .map_err(|e| Error::generic(format!("Failed to parse data file: {}", e)))?;
336
337 if let Some(entity) = registry.get(&entity_name) {
338 let table_name = entity.table_name();
339
340 if !database.table_exists(table_name).await.unwrap_or(false) {
344 }
348
349 for record in records {
350 let fields: Vec<String> = record.keys().cloned().collect();
351 let placeholders: Vec<String> =
352 (0..fields.len()).map(|_| "?".to_string()).collect();
353 let values: Vec<Value> = fields
354 .iter()
355 .map(|f| record.get(f).cloned().unwrap_or(Value::Null))
356 .collect();
357
358 let query = format!(
359 "INSERT INTO {} ({}) VALUES ({})",
360 table_name,
361 fields.join(", "),
362 placeholders.join(", ")
363 );
364
365 database.execute(&query, &values).await?;
366 }
367 }
368 }
369 }
370
371 Ok(())
372 }
373
374 async fn reset_database(
376 &self,
377 database: &dyn crate::database::VirtualDatabase,
378 registry: &EntityRegistry,
379 ) -> Result<()> {
380 for entity_name in registry.list() {
382 if let Some(entity) = registry.get(&entity_name) {
383 let table_name = entity.table_name();
384 let query = format!("DELETE FROM {}", table_name);
385 let _ = database.execute(&query, &[]).await;
386 }
387 }
388
389 let counter_table = "_vbr_counters";
391 if database.table_exists(counter_table).await.unwrap_or(false) {
392 let query = format!("DELETE FROM {}", counter_table);
393 let _ = database.execute(&query, &[]).await;
394 }
395
396 Ok(())
397 }
398
399 pub async fn list_snapshots(&self) -> Result<Vec<SnapshotMetadata>> {
401 if !self.snapshots_dir.exists() {
402 return Ok(Vec::new());
403 }
404
405 let mut snapshots = Vec::new();
406
407 let mut entries = fs::read_dir(&self.snapshots_dir)
408 .await
409 .map_err(|e| Error::generic(format!("Failed to read snapshots directory: {}", e)))?;
410
411 while let Some(entry) = entries
412 .next_entry()
413 .await
414 .map_err(|e| Error::generic(format!("Failed to read directory entry: {}", e)))?
415 {
416 let path = entry.path();
417 if path.is_dir() {
418 if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
419 if let Ok(metadata) = self.get_snapshot_metadata(name).await {
420 snapshots.push(metadata);
421 }
422 }
423 }
424 }
425
426 snapshots.sort_by(|a, b| b.created_at.cmp(&a.created_at));
428
429 Ok(snapshots)
430 }
431
432 pub async fn get_snapshot_metadata(&self, name: &str) -> Result<SnapshotMetadata> {
434 let metadata_path = self.metadata_path(name);
435 let content = fs::read_to_string(&metadata_path)
436 .await
437 .map_err(|e| Error::generic(format!("Failed to read snapshot metadata: {}", e)))?;
438
439 let metadata: SnapshotMetadata = serde_json::from_str(&content)
440 .map_err(|e| Error::generic(format!("Failed to parse snapshot metadata: {}", e)))?;
441
442 Ok(metadata)
443 }
444
445 pub async fn delete_snapshot(&self, name: &str) -> Result<()> {
447 let snapshot_dir = self.snapshot_path(name);
448
449 if !snapshot_dir.exists() {
450 return Err(Error::generic(format!("Snapshot '{}' not found", name)));
451 }
452
453 fs::remove_dir_all(&snapshot_dir)
454 .await
455 .map_err(|e| Error::generic(format!("Failed to delete snapshot: {}", e)))?;
456
457 Ok(())
458 }
459}
460
461pub async fn reset_database(
463 database: &dyn crate::database::VirtualDatabase,
464 registry: &EntityRegistry,
465) -> Result<()> {
466 for entity_name in registry.list() {
469 if let Some(entity) = registry.get(&entity_name) {
470 let table_name = entity.table_name();
471 let query = format!("DELETE FROM {}", table_name);
472 let _ = database.execute(&query, &[]).await;
473 }
474 }
475
476 let counter_table = "_vbr_counters";
478 if database.table_exists(counter_table).await.unwrap_or(false) {
479 let query = format!("DELETE FROM {}", counter_table);
480 let _ = database.execute(&query, &[]).await;
481 }
482
483 Ok(())
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489 use crate::database::InMemoryDatabase;
490 use crate::entities::{Entity, EntityRegistry};
491 use crate::migration::MigrationManager;
492 use crate::schema::VbrSchemaDefinition;
493 use mockforge_data::{FieldDefinition, SchemaDefinition};
494 use std::sync::Arc;
495
496 async fn setup_test_env(
497 ) -> (Arc<dyn VirtualDatabase + Send + Sync>, EntityRegistry, tempfile::TempDir) {
498 let mut db = InMemoryDatabase::new().await.unwrap();
499 db.initialize().await.unwrap();
500 let mut registry = EntityRegistry::new();
501
502 let base_schema = SchemaDefinition::new("User".to_string())
504 .with_field(FieldDefinition::new("id".to_string(), "string".to_string()))
505 .with_field(FieldDefinition::new("name".to_string(), "string".to_string()));
506
507 let vbr_schema = VbrSchemaDefinition::new(base_schema);
508 let entity = Entity::new("User".to_string(), vbr_schema);
509
510 let manager = MigrationManager::new();
511 let create_sql = manager.generate_create_table(&entity).unwrap();
512 db.create_table(&create_sql).await.unwrap();
513
514 registry.register(entity).unwrap();
515
516 let temp_dir = tempfile::tempdir().unwrap();
517 (Arc::new(db), registry, temp_dir)
518 }
519
520 #[test]
522 fn test_time_travel_snapshot_state_serialize() {
523 let state = TimeTravelSnapshotState {
524 enabled: true,
525 current_time: Some(chrono::Utc::now()),
526 scale_factor: 1.0,
527 cron_jobs: vec![],
528 mutation_rules: vec![],
529 };
530
531 let json = serde_json::to_string(&state).unwrap();
532 assert!(json.contains("enabled"));
533 assert!(json.contains("scale_factor"));
534 }
535
536 #[test]
537 fn test_time_travel_snapshot_state_deserialize() {
538 let json = r#"{
539 "enabled": true,
540 "current_time": "2024-01-01T00:00:00Z",
541 "scale_factor": 2.0,
542 "cron_jobs": [],
543 "mutation_rules": []
544 }"#;
545
546 let state: TimeTravelSnapshotState = serde_json::from_str(json).unwrap();
547 assert!(state.enabled);
548 assert_eq!(state.scale_factor, 2.0);
549 }
550
551 #[test]
552 fn test_time_travel_snapshot_state_clone() {
553 let state = TimeTravelSnapshotState {
554 enabled: false,
555 current_time: None,
556 scale_factor: 1.5,
557 cron_jobs: vec![],
558 mutation_rules: vec![],
559 };
560
561 let cloned = state.clone();
562 assert_eq!(state.enabled, cloned.enabled);
563 assert_eq!(state.scale_factor, cloned.scale_factor);
564 }
565
566 #[test]
568 fn test_snapshot_metadata_serialize() {
569 let metadata = SnapshotMetadata {
570 name: "test-snapshot".to_string(),
571 created_at: chrono::Utc::now(),
572 description: Some("Test description".to_string()),
573 entity_counts: HashMap::new(),
574 database_size: Some(1024),
575 storage_backend: "In-Memory".to_string(),
576 time_travel_state: None,
577 };
578
579 let json = serde_json::to_string(&metadata).unwrap();
580 assert!(json.contains("test-snapshot"));
581 assert!(json.contains("In-Memory"));
582 }
583
584 #[test]
585 fn test_snapshot_metadata_deserialize() {
586 let json = r#"{
587 "name": "test",
588 "created_at": "2024-01-01T00:00:00Z",
589 "description": null,
590 "entity_counts": {},
591 "database_size": null,
592 "storage_backend": "SQLite",
593 "time_travel_state": null
594 }"#;
595
596 let metadata: SnapshotMetadata = serde_json::from_str(json).unwrap();
597 assert_eq!(metadata.name, "test");
598 assert_eq!(metadata.storage_backend, "SQLite");
599 }
600
601 #[test]
602 fn test_snapshot_metadata_clone() {
603 let metadata = SnapshotMetadata {
604 name: "snap1".to_string(),
605 created_at: chrono::Utc::now(),
606 description: None,
607 entity_counts: HashMap::new(),
608 database_size: None,
609 storage_backend: "Memory".to_string(),
610 time_travel_state: None,
611 };
612
613 let cloned = metadata.clone();
614 assert_eq!(metadata.name, cloned.name);
615 assert_eq!(metadata.storage_backend, cloned.storage_backend);
616 }
617
618 #[test]
620 fn test_snapshot_manager_new() {
621 let temp_dir = tempfile::tempdir().unwrap();
622 let manager = SnapshotManager::new(temp_dir.path());
623 assert_eq!(manager.snapshots_dir, temp_dir.path());
624 }
625
626 #[test]
627 fn test_snapshot_path() {
628 let temp_dir = tempfile::tempdir().unwrap();
629 let manager = SnapshotManager::new(temp_dir.path());
630 let path = manager.snapshot_path("test-snapshot");
631 assert!(path.ends_with("test-snapshot"));
632 }
633
634 #[test]
635 fn test_metadata_path() {
636 let temp_dir = tempfile::tempdir().unwrap();
637 let manager = SnapshotManager::new(temp_dir.path());
638 let path = manager.metadata_path("test-snapshot");
639 assert!(path.ends_with("metadata.json"));
640 }
641
642 #[tokio::test]
643 async fn test_create_snapshot() {
644 let (database, registry, temp_dir) = setup_test_env().await;
645 let manager = SnapshotManager::new(temp_dir.path());
646
647 database
649 .execute(
650 "INSERT INTO users (id, name) VALUES (?, ?)",
651 &[
652 serde_json::Value::String("1".to_string()),
653 serde_json::Value::String("Test User".to_string()),
654 ],
655 )
656 .await
657 .unwrap();
658
659 let result = manager
660 .create_snapshot(
661 "test-snapshot",
662 Some("Test description".to_string()),
663 database.as_ref(),
664 ®istry,
665 )
666 .await;
667
668 assert!(result.is_ok());
669 let metadata = result.unwrap();
670 assert_eq!(metadata.name, "test-snapshot");
671 assert_eq!(metadata.description, Some("Test description".to_string()));
672 assert!(metadata.entity_counts.contains_key("User"));
673 }
674
675 #[tokio::test]
676 async fn test_create_snapshot_with_time_travel() {
677 let (database, registry, temp_dir) = setup_test_env().await;
678 let manager = SnapshotManager::new(temp_dir.path());
679
680 let time_travel_state = TimeTravelSnapshotState {
681 enabled: true,
682 current_time: Some(chrono::Utc::now()),
683 scale_factor: 2.0,
684 cron_jobs: vec![],
685 mutation_rules: vec![],
686 };
687
688 let result = manager
689 .create_snapshot_with_time_travel(
690 "tt-snapshot",
691 None,
692 database.as_ref(),
693 ®istry,
694 true,
695 Some(time_travel_state),
696 )
697 .await;
698
699 assert!(result.is_ok());
700 let metadata = result.unwrap();
701 assert!(metadata.time_travel_state.is_some());
702 assert_eq!(metadata.time_travel_state.unwrap().scale_factor, 2.0);
703 }
704
705 #[tokio::test]
706 async fn test_list_snapshots_empty() {
707 let temp_dir = tempfile::tempdir().unwrap();
708 let manager = SnapshotManager::new(temp_dir.path());
709
710 let result = manager.list_snapshots().await;
711 assert!(result.is_ok());
712 assert_eq!(result.unwrap().len(), 0);
713 }
714
715 #[tokio::test]
716 async fn test_list_snapshots() {
717 let (database, registry, temp_dir) = setup_test_env().await;
718 let manager = SnapshotManager::new(temp_dir.path());
719
720 manager
722 .create_snapshot("snap1", None, database.as_ref(), ®istry)
723 .await
724 .unwrap();
725 manager
726 .create_snapshot("snap2", None, database.as_ref(), ®istry)
727 .await
728 .unwrap();
729
730 let result = manager.list_snapshots().await;
731 assert!(result.is_ok());
732 let snapshots = result.unwrap();
733 assert_eq!(snapshots.len(), 2);
734 }
735
736 #[tokio::test]
737 async fn test_get_snapshot_metadata() {
738 let (database, registry, temp_dir) = setup_test_env().await;
739 let manager = SnapshotManager::new(temp_dir.path());
740
741 manager
742 .create_snapshot("test", None, database.as_ref(), ®istry)
743 .await
744 .unwrap();
745
746 let result = manager.get_snapshot_metadata("test").await;
747 assert!(result.is_ok());
748 let metadata = result.unwrap();
749 assert_eq!(metadata.name, "test");
750 }
751
752 #[tokio::test]
753 async fn test_get_snapshot_metadata_not_found() {
754 let temp_dir = tempfile::tempdir().unwrap();
755 let manager = SnapshotManager::new(temp_dir.path());
756
757 let result = manager.get_snapshot_metadata("nonexistent").await;
758 assert!(result.is_err());
759 }
760
761 #[tokio::test]
762 async fn test_delete_snapshot() {
763 let (database, registry, temp_dir) = setup_test_env().await;
764 let manager = SnapshotManager::new(temp_dir.path());
765
766 manager
767 .create_snapshot("to-delete", None, database.as_ref(), ®istry)
768 .await
769 .unwrap();
770
771 let result = manager.delete_snapshot("to-delete").await;
772 assert!(result.is_ok());
773
774 let snapshots = manager.list_snapshots().await.unwrap();
776 assert_eq!(snapshots.len(), 0);
777 }
778
779 #[tokio::test]
780 async fn test_delete_snapshot_not_found() {
781 let temp_dir = tempfile::tempdir().unwrap();
782 let manager = SnapshotManager::new(temp_dir.path());
783
784 let result = manager.delete_snapshot("nonexistent").await;
785 assert!(result.is_err());
786 }
787
788 #[tokio::test]
789 async fn test_restore_snapshot() {
790 let (database, registry, temp_dir) = setup_test_env().await;
791 let manager = SnapshotManager::new(temp_dir.path());
792
793 database
795 .execute(
796 "INSERT INTO users (id, name) VALUES (?, ?)",
797 &[
798 serde_json::Value::String("1".to_string()),
799 serde_json::Value::String("Original".to_string()),
800 ],
801 )
802 .await
803 .unwrap();
804
805 manager
806 .create_snapshot("backup", None, database.as_ref(), ®istry)
807 .await
808 .unwrap();
809
810 database
812 .execute(
813 "UPDATE users SET name = ? WHERE id = ?",
814 &[
815 serde_json::Value::String("Modified".to_string()),
816 serde_json::Value::String("1".to_string()),
817 ],
818 )
819 .await
820 .unwrap();
821
822 let result = manager.restore_snapshot("backup", database.as_ref(), ®istry).await;
824 assert!(result.is_ok());
825
826 let rows = database
828 .query(
829 "SELECT * FROM users WHERE id = ?",
830 &[serde_json::Value::String("1".to_string())],
831 )
832 .await
833 .unwrap();
834 assert_eq!(rows.len(), 1);
835 assert_eq!(rows[0].get("name").unwrap().as_str().unwrap(), "Original");
836 }
837
838 #[tokio::test]
839 async fn test_restore_snapshot_not_found() {
840 let (database, registry, temp_dir) = setup_test_env().await;
841 let manager = SnapshotManager::new(temp_dir.path());
842
843 let result = manager.restore_snapshot("nonexistent", database.as_ref(), ®istry).await;
844 assert!(result.is_err());
845 }
846
847 #[tokio::test]
848 async fn test_reset_database() {
849 let (database, registry, _temp_dir) = setup_test_env().await;
850
851 database
853 .execute(
854 "INSERT INTO users (id, name) VALUES (?, ?)",
855 &[
856 serde_json::Value::String("1".to_string()),
857 serde_json::Value::String("Test".to_string()),
858 ],
859 )
860 .await
861 .unwrap();
862
863 let rows = database.query("SELECT * FROM users", &[]).await.unwrap();
865 assert_eq!(rows.len(), 1);
866
867 let result = reset_database(database.as_ref(), ®istry).await;
869 assert!(result.is_ok());
870
871 let rows = database.query("SELECT * FROM users", &[]).await.unwrap();
873 assert_eq!(rows.len(), 0);
874 }
875
876 #[tokio::test]
877 async fn test_snapshot_ordering() {
878 let (database, registry, temp_dir) = setup_test_env().await;
879 let manager = SnapshotManager::new(temp_dir.path());
880
881 manager
883 .create_snapshot("first", None, database.as_ref(), ®istry)
884 .await
885 .unwrap();
886 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
887 manager
888 .create_snapshot("second", None, database.as_ref(), ®istry)
889 .await
890 .unwrap();
891
892 let snapshots = manager.list_snapshots().await.unwrap();
894 assert_eq!(snapshots.len(), 2);
895 assert_eq!(snapshots[0].name, "second");
896 assert_eq!(snapshots[1].name, "first");
897 }
898
899 #[tokio::test]
900 async fn test_snapshot_entity_counts() {
901 let (database, mut registry, temp_dir) = setup_test_env().await;
902
903 let base_schema = SchemaDefinition::new("Product".to_string())
905 .with_field(FieldDefinition::new("id".to_string(), "string".to_string()));
906 let vbr_schema = VbrSchemaDefinition::new(base_schema);
907 let entity = Entity::new("Product".to_string(), vbr_schema);
908
909 let manager_m = MigrationManager::new();
910 let create_sql = manager_m.generate_create_table(&entity).unwrap();
911 database.create_table(&create_sql).await.unwrap();
912 registry.register(entity).unwrap();
913
914 database
916 .execute(
917 "INSERT INTO users (id, name) VALUES (?, ?)",
918 &[
919 serde_json::Value::String("1".to_string()),
920 serde_json::Value::String("User1".to_string()),
921 ],
922 )
923 .await
924 .unwrap();
925
926 database
927 .execute(
928 "INSERT INTO products (id) VALUES (?)",
929 &[serde_json::Value::String("1".to_string())],
930 )
931 .await
932 .unwrap();
933 database
934 .execute(
935 "INSERT INTO products (id) VALUES (?)",
936 &[serde_json::Value::String("2".to_string())],
937 )
938 .await
939 .unwrap();
940
941 let manager = SnapshotManager::new(temp_dir.path());
942 let metadata = manager
943 .create_snapshot("multi-entity", None, database.as_ref(), ®istry)
944 .await
945 .unwrap();
946
947 assert_eq!(metadata.entity_counts.get("User").unwrap(), &1);
948 assert_eq!(metadata.entity_counts.get("Product").unwrap(), &2);
949 }
950
951 #[tokio::test]
952 async fn test_snapshot_with_empty_tables() {
953 let (database, registry, temp_dir) = setup_test_env().await;
954 let manager = SnapshotManager::new(temp_dir.path());
955
956 let result = manager.create_snapshot("empty", None, database.as_ref(), ®istry).await;
957 assert!(result.is_ok());
958
959 let metadata = result.unwrap();
960 assert_eq!(metadata.entity_counts.get("User").unwrap(), &0);
961 }
962}