Skip to main content

reinhardt_testkit/fixtures/
migrations.rs

1//! Migration Registry Test Fixtures
2//! Migration test fixtures and helpers
3//!
4//! This module provides comprehensive fixtures for testing database migrations
5//! and schema operations in Reinhardt applications.
6//!
7//! ## Fixture Categories
8//!
9//! ### Unit Testing Fixtures
10//!
11//! - `` `migration_registry` `` - Isolated `LocalRegistry` for unit testing
12//! - `` `test_migration_source` `` - In-memory migration source
13//! - `` `in_memory_repository` `` - In-memory migration repository
14//!
15//! ### Integration Testing Fixtures (requires `testcontainers` feature)
16//!
17//! - `` `migration_executor` `` - DatabaseMigrationExecutor with PostgreSQL container
18//! - `` `postgres_table_creator` `` - PostgreSQL schema management helper
19//! - `` `admin_table_creator` `` - Admin panel integration helper (available in `reinhardt-test`)
20//!
21//! ## Usage Examples
22//!
23//! ### Unit Testing with LocalRegistry
24//!
25//! ```rust,no_run
26//! # use reinhardt_testkit::fixtures::*;
27//! # use reinhardt_db::migrations::Migration;
28//! # use rstest::*;
29//! // #[rstest]
30//! // fn test_migration_registration(migration_registry: LocalRegistry) {
31//! //     let migration = Migration::new("0001_initial", "polls");
32//! //
33//! //     migration_registry.register(migration).unwrap();
34//! //     assert_eq!(migration_registry.all_migrations().len(), 1);
35//! // }
36//! ```
37//!
38//! ### Integration Testing with PostgresTableCreator
39//!
40//! ```rust,no_run
41//! # use reinhardt_testkit::fixtures::*;
42//! # use reinhardt_db::migrations::{Operation, ColumnDefinition, FieldType};
43//! # use rstest::*;
44//! // #[rstest]
45//! // #[tokio::test]
46//! // async fn test_with_schema(#[future] postgres_table_creator: PostgresTableCreator) {
47//! //     let mut creator = postgres_table_creator.await;
48//! //
49//! //     // Define schema using Operation enum
50//! //     let schema = vec![
51//! //         Operation::CreateTable {
52//! //             name: "users".to_string(),
53//! //             columns: vec![
54//! //                 ColumnDefinition::new("id", FieldType::Serial).primary_key(),
55//! //                 ColumnDefinition::new("name", FieldType::Text),
56//! //             ],
57//! //             constraints: vec![],
58//! //             without_rowid: None,
59//! //             interleave_in_parent: None,
60//! //             partition: None,
61//! //         },
62//! //     ];
63//! //
64//! //     // Apply schema
65//! //     creator.apply(schema).await.unwrap();
66//! //
67//! //     // Use the database
68//! //     let pool = creator.pool();
69//! //     // ... test code ...
70//! // }
71//! ```
72//!
73//! ## Migration Patterns
74//!
75//! The new `PostgresTableCreator` and `AdminTableCreator` fixtures promote type-safe
76//! schema management using `Operation` enum instead of raw SQL strings. This provides:
77//!
78//! - **Type safety**: Schema defined using Rust types
79//! - **Testability**: Easy to create isolated test databases
80//! - **Maintainability**: Schema changes are explicit and reviewable
81//! - **Consistency**: Same patterns across all tests
82
83use async_trait::async_trait;
84use reinhardt_db::migrations::registry::LocalRegistry;
85use reinhardt_db::migrations::{Migration, MigrationRepository, MigrationSource, Result};
86use rstest::*;
87use std::collections::HashMap;
88
89// TestContainers-related imports (conditional on feature)
90#[cfg(feature = "testcontainers")]
91use crate::fixtures::testcontainers::postgres_container;
92#[cfg(feature = "testcontainers")]
93use reinhardt_db::migrations::executor::DatabaseMigrationExecutor;
94#[cfg(feature = "testcontainers")]
95use reinhardt_db::migrations::{DatabaseConnection, MigrationError, Operation};
96#[cfg(feature = "testcontainers")]
97use std::sync::Arc;
98#[cfg(feature = "testcontainers")]
99use testcontainers::{ContainerAsync, GenericImage};
100
101/// Creates a new isolated migration registry for testing
102///
103/// Each test gets its own empty LocalRegistry instance, ensuring complete
104/// isolation between test cases. This avoids the "duplicate distributed_slice"
105/// errors that occur with linkme's global registry in test environments.
106///
107/// # Examples
108///
109/// ```rust,no_run
110/// # use reinhardt_testkit::fixtures::*;
111/// # use reinhardt_db::migrations::Migration;
112/// # use rstest::*;
113/// // #[rstest]
114/// // fn test_migration_operations(migration_registry: LocalRegistry) {
115/// //     // Registry starts empty
116/// //     assert!(migration_registry.all_migrations().is_empty());
117/// //
118/// //     // Register a migration
119/// //     migration_registry.register(Migration::new("0001_initial", "polls")).unwrap();
120/// //
121/// //     // Verify registration
122/// //     assert_eq!(migration_registry.all_migrations().len(), 1);
123/// // }
124/// ```
125#[fixture]
126pub fn migration_registry() -> LocalRegistry {
127	LocalRegistry::new()
128}
129
130/// In-memory migration source for testing
131///
132/// Provides a simple implementation of `MigrationSource` that stores migrations
133/// in memory. Useful for testing migration-related functionality without
134/// filesystem or database dependencies.
135///
136/// # Examples
137///
138/// ```rust,no_run
139/// # use reinhardt_testkit::fixtures::TestMigrationSource;
140/// # use reinhardt_db::migrations::{Migration, MigrationSource};
141/// # #[tokio::main]
142/// # async fn main() {
143/// // #[tokio::test]
144/// // async fn test_source() {
145/// let mut source = TestMigrationSource::new();
146/// source.add_migration(Migration::new("0001_initial", "polls"));
147///
148/// let migrations = source.all_migrations().await.unwrap();
149/// assert_eq!(migrations.len(), 1);
150/// // }
151/// # }
152/// ```
153pub struct TestMigrationSource {
154	migrations: Vec<Migration>,
155}
156
157impl TestMigrationSource {
158	/// Create a new empty TestMigrationSource
159	pub fn new() -> Self {
160		Self {
161			migrations: Vec::new(),
162		}
163	}
164
165	/// Create a TestMigrationSource with initial migrations
166	pub fn with_migrations(migrations: Vec<Migration>) -> Self {
167		Self { migrations }
168	}
169
170	/// Add a migration to the source
171	pub fn add_migration(&mut self, migration: Migration) {
172		self.migrations.push(migration);
173	}
174
175	/// Clear all migrations from the source
176	pub fn clear(&mut self) {
177		self.migrations.clear();
178	}
179
180	/// Get the number of migrations
181	pub fn len(&self) -> usize {
182		self.migrations.len()
183	}
184
185	/// Check if the source is empty
186	pub fn is_empty(&self) -> bool {
187		self.migrations.is_empty()
188	}
189}
190
191impl Default for TestMigrationSource {
192	fn default() -> Self {
193		Self::new()
194	}
195}
196
197#[async_trait]
198impl MigrationSource for TestMigrationSource {
199	async fn all_migrations(&self) -> Result<Vec<Migration>> {
200		Ok(self.migrations.clone())
201	}
202}
203
204/// In-memory migration repository for testing
205///
206/// Provides a simple implementation of `MigrationRepository` that stores migrations
207/// in memory using a HashMap. Useful for testing migration persistence without
208/// actual file I/O.
209///
210/// # Examples
211///
212/// ```rust,no_run
213/// # use reinhardt_testkit::fixtures::InMemoryRepository;
214/// # use reinhardt_db::migrations::{Migration, MigrationRepository};
215/// # #[tokio::main]
216/// # async fn main() {
217/// // #[tokio::test]
218/// // async fn test_repository() {
219/// let mut repo = InMemoryRepository::new();
220///
221/// let migration = Migration::new("0001_initial", "polls");
222///
223/// repo.save(&migration).await.unwrap();
224/// let retrieved = repo.get("polls", "0001_initial").await.unwrap();
225/// assert_eq!(retrieved.name, "0001_initial");
226/// // }
227/// # }
228/// ```
229pub struct InMemoryRepository {
230	migrations: HashMap<(String, String), Migration>,
231}
232
233impl InMemoryRepository {
234	/// Create a new empty InMemoryRepository
235	pub fn new() -> Self {
236		Self {
237			migrations: HashMap::new(),
238		}
239	}
240
241	/// Create an InMemoryRepository with initial migrations
242	pub fn with_migrations(migrations: Vec<Migration>) -> Self {
243		let mut repo = Self::new();
244		for migration in migrations {
245			let key = (migration.app_label.to_string(), migration.name.to_string());
246			repo.migrations.insert(key, migration);
247		}
248		repo
249	}
250
251	/// Clear all migrations from the repository
252	pub fn clear(&mut self) {
253		self.migrations.clear();
254	}
255
256	/// Get the number of migrations in the repository
257	pub fn len(&self) -> usize {
258		self.migrations.len()
259	}
260
261	/// Check if the repository is empty
262	pub fn is_empty(&self) -> bool {
263		self.migrations.is_empty()
264	}
265}
266
267impl Default for InMemoryRepository {
268	fn default() -> Self {
269		Self::new()
270	}
271}
272
273#[async_trait]
274impl MigrationRepository for InMemoryRepository {
275	async fn save(&mut self, migration: &Migration) -> Result<()> {
276		let key = (migration.app_label.to_string(), migration.name.to_string());
277		self.migrations.insert(key, migration.clone());
278		Ok(())
279	}
280
281	async fn get(&self, app_label: &str, name: &str) -> Result<Migration> {
282		let key = (app_label.to_string(), name.to_string());
283		self.migrations.get(&key).cloned().ok_or_else(|| {
284			reinhardt_db::migrations::MigrationError::NotFound(format!("{}.{}", app_label, name))
285		})
286	}
287
288	async fn list(&self, app_label: &str) -> Result<Vec<Migration>> {
289		Ok(self
290			.migrations
291			.values()
292			.filter(|m| m.app_label == app_label)
293			.cloned()
294			.collect())
295	}
296
297	async fn delete(&mut self, app_label: &str, name: &str) -> Result<()> {
298		let key = (app_label.to_string(), name.to_string());
299		self.migrations.remove(&key).ok_or_else(|| {
300			reinhardt_db::migrations::MigrationError::NotFound(format!("{}.{}", app_label, name))
301		})?;
302		Ok(())
303	}
304}
305
306/// Creates a new TestMigrationSource for testing
307///
308/// Provides an empty migration source that can be populated with test migrations.
309///
310/// # Examples
311///
312/// ```rust,no_run
313/// # use reinhardt_testkit::fixtures::*;
314/// # use reinhardt_db::migrations::{Migration, MigrationSource};
315/// # use rstest::*;
316/// # #[tokio::main]
317/// # async fn main() {
318/// // #[rstest]
319/// // #[tokio::test]
320/// // async fn test_with_source(mut test_migration_source: TestMigrationSource) {
321/// let mut test_migration_source = TestMigrationSource::new();
322/// test_migration_source.add_migration(Migration::new("0001_initial", "polls"));
323///
324/// let migrations = test_migration_source.all_migrations().await.unwrap();
325/// assert_eq!(migrations.len(), 1);
326/// // }
327/// # }
328/// ```
329#[fixture]
330pub fn test_migration_source() -> TestMigrationSource {
331	TestMigrationSource::new()
332}
333
334/// Creates a new InMemoryRepository for testing
335///
336/// Provides an empty migration repository that stores migrations in memory.
337///
338/// # Examples
339///
340/// ```rust,no_run
341/// # use reinhardt_testkit::fixtures::*;
342/// # use reinhardt_db::migrations::{Migration, MigrationRepository};
343/// # use rstest::*;
344/// # #[tokio::main]
345/// # async fn main() {
346/// // #[rstest]
347/// // #[tokio::test]
348/// // async fn test_with_repository(mut in_memory_repository: InMemoryRepository) {
349/// let mut in_memory_repository = InMemoryRepository::new();
350/// let migration = Migration::new("0001_initial", "polls");
351///
352/// in_memory_repository.save(&migration).await.unwrap();
353/// let retrieved = in_memory_repository.get("polls", "0001_initial").await.unwrap();
354/// assert_eq!(retrieved.name, "0001_initial");
355/// }
356/// ```
357#[fixture]
358pub fn in_memory_repository() -> InMemoryRepository {
359	InMemoryRepository::new()
360}
361
362// ============================================================================
363// TestContainers-based Migration Executor Fixtures
364// ============================================================================
365
366/// Type alias for migration_executor fixture return value
367///
368/// Contains all elements from postgres_container plus the migration executor:
369/// - `DatabaseMigrationExecutor`: Migration executor instance
370/// - `ContainerAsync<GenericImage>`: PostgreSQL container
371/// - `Arc<PgPool>`: Database connection pool
372/// - `u16`: PostgreSQL port
373/// - `String`: Database URL
374#[cfg(feature = "testcontainers")]
375pub type MigrationExecutorFixture = (
376	DatabaseMigrationExecutor,
377	ContainerAsync<GenericImage>,
378	Arc<sqlx::PgPool>,
379	u16,
380	String,
381);
382
383/// Helper for applying database schema migrations in tests with PostgreSQL
384///
385/// Provides a convenient interface for creating database tables and applying
386/// schema operations during test setup. Holds a DatabaseMigrationExecutor
387/// and connection pool internally.
388///
389/// # Examples
390///
391/// ```rust,no_run
392/// # use reinhardt_testkit::fixtures::*;
393/// # use reinhardt_db::migrations::{Operation, ColumnDefinition, FieldType};
394/// # use rstest::*;
395/// #[rstest]
396/// #[tokio::test]
397/// async fn test_with_schema(#[future] postgres_table_creator: PostgresTableCreator) {
398///     let mut creator = postgres_table_creator.await;
399///
400///     let schema = vec![
401///         Operation::CreateTable {
402///             name: "users".to_string(),
403///             columns: vec![
404///                 ColumnDefinition::new("id", FieldType::Integer),
405///                 ColumnDefinition::new("name", FieldType::Text),
406///             ],
407///             constraints: vec![],
408///             without_rowid: None,
409///             interleave_in_parent: None,
410///             partition: None,
411///         },
412///     ];
413///
414///     creator.apply(schema).await.unwrap();
415///
416///     let pool = creator.pool();
417///     // Run tests using pool...
418/// }
419/// ```
420#[cfg(feature = "testcontainers")]
421pub struct PostgresTableCreator {
422	executor: DatabaseMigrationExecutor,
423	pool: Arc<sqlx::PgPool>,
424	container: ContainerAsync<GenericImage>,
425	port: u16,
426	url: String,
427}
428
429#[cfg(feature = "testcontainers")]
430impl PostgresTableCreator {
431	/// Create a new TableCreator
432	pub fn new(
433		executor: DatabaseMigrationExecutor,
434		container: ContainerAsync<GenericImage>,
435		pool: Arc<sqlx::PgPool>,
436		port: u16,
437		url: String,
438	) -> Self {
439		Self {
440			executor,
441			pool,
442			container,
443			port,
444			url,
445		}
446	}
447
448	/// Apply schema operations by creating and executing a migration
449	///
450	/// # Examples
451	///
452	/// ```rust,no_run
453	/// # use reinhardt_testkit::fixtures::*;
454	/// # use reinhardt_db::migrations::{Operation, ColumnDefinition, FieldType};
455	/// # async fn example(mut creator: PostgresTableCreator) {
456	/// let schema = vec![
457	///     Operation::CreateTable {
458	///         name: "products".to_string(),
459	///         columns: vec![
460	///             ColumnDefinition::new("id", FieldType::Integer),
461	///             ColumnDefinition::new("name", FieldType::Text),
462	///         ],
463	///         constraints: vec![],
464	///         without_rowid: None,
465	///         interleave_in_parent: None,
466	///         partition: None,
467	///     },
468	/// ];
469	///
470	/// creator.apply(schema).await.unwrap();
471	/// # }
472	/// ```
473	pub async fn apply(&mut self, schema: Vec<Operation>) -> Result<()> {
474		let mut migration = Migration::new("0001_test_schema", "testapp");
475
476		for operation in schema {
477			migration = migration.add_operation(operation);
478		}
479
480		self.executor
481			.apply_migrations(&[migration])
482			.await
483			.expect("Failed to apply test schema migrations");
484
485		Ok(())
486	}
487
488	/// Get a reference to the database connection pool
489	pub fn pool(&self) -> &Arc<sqlx::PgPool> {
490		&self.pool
491	}
492
493	/// Get the database URL
494	pub fn url(&self) -> &str {
495		&self.url
496	}
497
498	/// Get the database port
499	pub fn port(&self) -> u16 {
500		self.port
501	}
502
503	/// Get a reference to the container
504	///
505	/// This is useful for advanced test scenarios that need direct container access.
506	pub fn container(&self) -> &ContainerAsync<GenericImage> {
507		&self.container
508	}
509
510	/// Insert data into a table using reinhardt-query
511	///
512	/// This method provides a convenient way to insert test data using type-safe
513	/// reinhardt-query builders instead of raw SQL strings.
514	///
515	/// # Examples
516	///
517	/// ```rust,no_run
518	/// # use reinhardt_testkit::fixtures::*;
519	/// # use reinhardt_query::prelude::Value;
520	/// # async fn example(creator: &PostgresTableCreator) {
521	/// creator.insert_data(
522	///     "users",
523	///     vec!["id", "name", "email"],
524	///     vec![
525	///         vec![
526	///             Value::Int(Some(1)),
527	///             Value::String(Some(Box::new("Alice".to_string()))),
528	///             Value::String(Some(Box::new("alice@example.com".to_string()))),
529	///         ],
530	///     ],
531	/// ).await.unwrap();
532	/// # }
533	/// ```
534	pub async fn insert_data(
535		&self,
536		table: &str,
537		columns: Vec<&str>,
538		values: Vec<Vec<reinhardt_query::prelude::Value>>,
539	) -> Result<()> {
540		use reinhardt_query::prelude::{Alias, PostgresQueryBuilder, Query, QueryStatementBuilder};
541
542		for row_values in values {
543			let mut query = Query::insert();
544			query
545				.into_table(Alias::new(table))
546				.columns(columns.iter().map(|&c| Alias::new(c)));
547
548			query.values_panic(row_values);
549
550			// Build SQL string
551			let sql = query.to_string(PostgresQueryBuilder::new());
552
553			sqlx::query(&sql)
554				.execute(self.pool.as_ref())
555				.await
556				.map_err(MigrationError::SqlError)?;
557		}
558		Ok(())
559	}
560
561	/// Execute custom SQL (fallback for complex cases)
562	///
563	/// This method allows executing arbitrary SQL statements when reinhardt-query
564	/// is insufficient for complex schema operations or PostgreSQL-specific features.
565	///
566	/// # Examples
567	///
568	/// ```rust,no_run
569	/// # use reinhardt_testkit::fixtures::*;
570	/// # async fn example(creator: &PostgresTableCreator) {
571	/// // PostgreSQL-specific feature
572	/// creator.execute_sql(
573	///     "CREATE TABLE test (id SERIAL PRIMARY KEY) WITH (autovacuum_enabled = false)"
574	/// ).await.unwrap();
575	/// # }
576	/// ```
577	pub async fn execute_sql(&self, sql: &str) -> Result<sqlx::postgres::PgQueryResult> {
578		sqlx::query(sql)
579			.execute(self.pool.as_ref())
580			.await
581			.map_err(MigrationError::SqlError)
582	}
583
584	/// Begin a transaction for advanced test scenarios
585	///
586	/// This is useful for testing two-phase commit (2PC) or other transaction-based
587	/// operations.
588	///
589	/// # Examples
590	///
591	/// ```rust,no_run
592	/// # use reinhardt_testkit::fixtures::*;
593	/// # async fn example(creator: &PostgresTableCreator) {
594	/// let mut tx = creator.begin_transaction().await.unwrap();
595	/// // Perform transactional operations...
596	/// tx.commit().await.unwrap();
597	/// # }
598	/// ```
599	pub async fn begin_transaction(&self) -> Result<sqlx::Transaction<'_, sqlx::Postgres>> {
600		self.pool.begin().await.map_err(MigrationError::SqlError)
601	}
602}
603
604// ============================================================================
605// Migration Executor and Table Creator Fixtures
606// ============================================================================
607
608/// Creates a DatabaseMigrationExecutor connected to a test PostgreSQL container
609///
610/// This fixture combines postgres_container with migration executor creation,
611/// providing a ready-to-use migration executor for tests.
612///
613/// # Type Signature
614///
615/// Returns `MigrationExecutorFixture`:
616/// - `DatabaseMigrationExecutor`: Migration executor instance
617/// - `ContainerAsync<GenericImage>`: PostgreSQL container
618/// - `Arc<PgPool>`: Database connection pool
619/// - `u16`: PostgreSQL port
620/// - `String`: Database URL
621///
622/// # Examples
623///
624/// ```rust,no_run
625/// # use reinhardt_testkit::fixtures::*;
626/// # use reinhardt_db::migrations::Migration;
627/// # use rstest::*;
628/// #[rstest]
629/// #[tokio::test]
630/// async fn test_with_executor(
631///     #[future] migration_executor: MigrationExecutorFixture
632/// ) {
633///     let (mut executor, _container, _pool, _port, _url) = migration_executor.await;
634///
635///     let migration = Migration::new("0001_test", "testapp");
636///     executor.apply_migrations(&[migration]).await.unwrap();
637/// }
638/// ```
639#[cfg(feature = "testcontainers")]
640#[fixture]
641pub async fn migration_executor(
642	#[future] postgres_container: (ContainerAsync<GenericImage>, Arc<sqlx::PgPool>, u16, String),
643) -> MigrationExecutorFixture {
644	let (container, pool, port, url) = postgres_container.await;
645
646	let connection = DatabaseConnection::connect_postgres(&url)
647		.await
648		.expect("Failed to connect to test database");
649
650	let executor = DatabaseMigrationExecutor::new(connection);
651
652	(executor, container, pool, port, url)
653}
654
655/// Creates a PostgresTableCreator for applying test schemas
656///
657/// This fixture combines migration_executor with a convenient helper structure
658/// for applying database schema operations in PostgreSQL tests.
659///
660/// # Type Signature
661///
662/// Returns `` `PostgresTableCreator` ``:
663/// - Helper with methods to apply schema operations
664/// - Provides access to connection pool, URL, and port
665/// - Includes methods for data insertion and custom SQL execution
666///
667/// # Examples
668///
669/// ```rust,no_run
670/// # use reinhardt_testkit::fixtures::*;
671/// # use reinhardt_db::migrations::{Operation, ColumnDefinition, FieldType};
672/// # use rstest::*;
673/// #[rstest]
674/// #[tokio::test]
675/// async fn test_with_creator(#[future] postgres_table_creator: PostgresTableCreator) {
676///     let mut creator = postgres_table_creator.await;
677///
678///     let schema = vec![
679///         Operation::CreateTable {
680///             name: "test_table".to_string(),
681///             columns: vec![
682///                 ColumnDefinition::new("id", FieldType::Integer),
683///             ],
684///             constraints: vec![],
685///             without_rowid: None,
686///             interleave_in_parent: None,
687///             partition: None,
688///         },
689///     ];
690///
691///     creator.apply(schema).await.unwrap();
692///     let pool = creator.pool();
693///     // Use pool for testing...
694/// }
695/// ```
696#[cfg(feature = "testcontainers")]
697#[fixture]
698pub async fn postgres_table_creator(
699	#[future] migration_executor: MigrationExecutorFixture,
700) -> PostgresTableCreator {
701	let (executor, container, pool, port, url) = migration_executor.await;
702	PostgresTableCreator::new(executor, container, pool, port, url)
703}
704
705#[cfg(test)]
706mod tests {
707	use super::*;
708	use reinhardt_db::migrations::Migration;
709	use reinhardt_db::migrations::registry::MigrationRegistry;
710
711	#[rstest]
712	fn test_migration_registry_fixture(migration_registry: LocalRegistry) {
713		assert!(migration_registry.all_migrations().is_empty());
714	}
715
716	#[rstest]
717	fn test_registry_isolation_between_tests(migration_registry: LocalRegistry) {
718		// This test runs independently - registry should be empty
719		assert_eq!(migration_registry.all_migrations().len(), 0);
720
721		migration_registry
722			.register(Migration::new("0001_initial", "test_app"))
723			.unwrap();
724
725		assert_eq!(migration_registry.all_migrations().len(), 1);
726	}
727
728	#[rstest]
729	fn test_another_isolated_test(migration_registry: LocalRegistry) {
730		// Even though previous test registered a migration,
731		// this new fixture instance should be empty
732		assert_eq!(migration_registry.all_migrations().len(), 0);
733	}
734
735	#[cfg(feature = "testcontainers")]
736	mod testcontainer_fixtures {
737		use super::*;
738		use reinhardt_db::migrations::{ColumnDefinition, DatabaseType, FieldType, Operation};
739
740		#[rstest]
741		#[tokio::test]
742		async fn test_migration_executor_fixture(
743			#[future] migration_executor: MigrationExecutorFixture,
744		) {
745			let (executor, _container, _pool, _port, _url) = migration_executor.await;
746
747			// Verify executor is connected to PostgreSQL
748			assert_eq!(executor.database_type(), DatabaseType::Postgres);
749		}
750
751		#[rstest]
752		#[tokio::test]
753		async fn test_postgres_table_creator_fixture(
754			#[future] postgres_table_creator: PostgresTableCreator,
755		) {
756			let mut creator = postgres_table_creator.await;
757
758			// Define schema directly in test
759			let schema = vec![Operation::CreateTable {
760				name: "fixture_test_table".to_string(),
761				columns: vec![
762					ColumnDefinition::new("id", FieldType::Integer),
763					ColumnDefinition::new("value", FieldType::Text),
764				],
765				constraints: vec![],
766				without_rowid: None,
767				interleave_in_parent: None,
768				partition: None,
769			}];
770
771			// Apply schema
772			creator.apply(schema).await.unwrap();
773
774			// Verify table was created
775			let pool = creator.pool();
776			let result = sqlx::query("SELECT * FROM fixture_test_table")
777				.fetch_all(pool.as_ref())
778				.await
779				.unwrap();
780
781			assert!(result.is_empty());
782		}
783	}
784}