Skip to main content

reinhardt_db/migrations/
repository.rs

1//! Migration repository abstraction
2//!
3//! This module defines the `MigrationRepository` trait, which abstracts how migrations are stored.
4
5pub mod filesystem;
6
7use super::{Migration, MigrationError, Result};
8use async_trait::async_trait;
9
10/// Trait for persisting migrations to various storage backends
11///
12/// Implementations:
13/// - `RustFileRepository`: Writes .rs files with metadata comments
14/// - `InMemoryRepository`: In-memory storage for testing
15#[async_trait]
16pub trait MigrationRepository: Send + Sync {
17	/// Saves a migration to the repository
18	async fn save(&mut self, migration: &Migration) -> Result<()>;
19
20	/// Retrieves a migration by app and name
21	async fn get(&self, app_label: &str, name: &str) -> Result<Migration>;
22
23	/// Lists all migrations for an app
24	async fn list(&self, app_label: &str) -> Result<Vec<Migration>>;
25
26	/// Checks if a migration exists
27	async fn exists(&self, app_label: &str, name: &str) -> Result<bool> {
28		self.get(app_label, name).await.map(|_| true).or(Ok(false))
29	}
30
31	/// Deletes a migration (optional, not all repositories support this)
32	async fn delete(&mut self, _app_label: &str, _name: &str) -> Result<()> {
33		Err(MigrationError::InvalidMigration(
34			"Delete operation not supported by this repository".to_string(),
35		))
36	}
37}
38
39#[cfg(test)]
40mod tests {
41	use super::*;
42	use std::collections::HashMap;
43
44	/// Test helper to create a migration
45	fn create_test_migration(app_label: &str, name: &str) -> Migration {
46		Migration {
47			app_label: app_label.to_string(),
48			name: name.to_string(),
49			operations: vec![],
50			dependencies: vec![],
51			atomic: true,
52			initial: None,
53			replaces: vec![],
54			state_only: false,
55			database_only: false,
56			swappable_dependencies: vec![],
57			optional_dependencies: vec![],
58		}
59	}
60
61	/// Test MigrationRepository implementation for unit tests
62	struct TestRepository {
63		migrations: HashMap<(String, String), Migration>,
64	}
65
66	impl TestRepository {
67		fn new() -> Self {
68			Self {
69				migrations: HashMap::new(),
70			}
71		}
72	}
73
74	#[async_trait]
75	impl MigrationRepository for TestRepository {
76		async fn save(&mut self, migration: &Migration) -> Result<()> {
77			let key = (migration.app_label.to_string(), migration.name.to_string());
78			self.migrations.insert(key, migration.clone());
79			Ok(())
80		}
81
82		async fn get(&self, app_label: &str, name: &str) -> Result<Migration> {
83			let key = (app_label.to_string(), name.to_string());
84			self.migrations
85				.get(&key)
86				.cloned()
87				.ok_or_else(|| MigrationError::NotFound(format!("{}.{}", app_label, name)))
88		}
89
90		async fn list(&self, app_label: &str) -> Result<Vec<Migration>> {
91			Ok(self
92				.migrations
93				.values()
94				.filter(|m| m.app_label == app_label)
95				.cloned()
96				.collect())
97		}
98
99		async fn delete(&mut self, app_label: &str, name: &str) -> Result<()> {
100			let key = (app_label.to_string(), name.to_string());
101			self.migrations
102				.remove(&key)
103				.ok_or_else(|| MigrationError::NotFound(format!("{}.{}", app_label, name)))?;
104			Ok(())
105		}
106	}
107
108	#[tokio::test]
109	async fn test_save_and_get() {
110		let mut repo = TestRepository::new();
111		let migration = create_test_migration("polls", "0001_initial");
112
113		repo.save(&migration).await.unwrap();
114
115		let retrieved = repo.get("polls", "0001_initial").await.unwrap();
116		assert_eq!(retrieved.app_label, "polls");
117		assert_eq!(retrieved.name, "0001_initial");
118	}
119
120	#[tokio::test]
121	async fn test_list() {
122		let mut repo = TestRepository::new();
123		repo.save(&create_test_migration("polls", "0001_initial"))
124			.await
125			.unwrap();
126		repo.save(&create_test_migration("polls", "0002_add_field"))
127			.await
128			.unwrap();
129		repo.save(&create_test_migration("users", "0001_initial"))
130			.await
131			.unwrap();
132
133		let polls_migrations = repo.list("polls").await.unwrap();
134		assert_eq!(polls_migrations.len(), 2);
135		assert!(polls_migrations.iter().all(|m| m.app_label == "polls"));
136
137		let users_migrations = repo.list("users").await.unwrap();
138		assert_eq!(users_migrations.len(), 1);
139	}
140
141	#[tokio::test]
142	async fn test_exists() {
143		let mut repo = TestRepository::new();
144		repo.save(&create_test_migration("polls", "0001_initial"))
145			.await
146			.unwrap();
147
148		assert!(repo.exists("polls", "0001_initial").await.unwrap());
149		assert!(!repo.exists("polls", "0002_nonexistent").await.unwrap());
150	}
151
152	#[tokio::test]
153	async fn test_delete() {
154		let mut repo = TestRepository::new();
155		repo.save(&create_test_migration("polls", "0001_initial"))
156			.await
157			.unwrap();
158
159		assert!(repo.exists("polls", "0001_initial").await.unwrap());
160
161		repo.delete("polls", "0001_initial").await.unwrap();
162
163		assert!(!repo.exists("polls", "0001_initial").await.unwrap());
164	}
165
166	#[tokio::test]
167	async fn test_get_not_found() {
168		let repo = TestRepository::new();
169		let result = repo.get("polls", "0001_nonexistent").await;
170		assert!(result.is_err());
171		assert!(matches!(result.unwrap_err(), MigrationError::NotFound(_)));
172	}
173}