//! Integration tests for database migrations //! //! These tests verify that migrations run successfully, the schema is correct, //! and basic database operations work as expected. mod helpers; use helpers::*; use sqlx::Row; #[tokio::test] async fn test_migrations_applied() { let pool = create_test_pool().await.unwrap(); // Verify migrations were applied by checking that core tables exist // We check for multiple tables to ensure the schema is properly set up let tables = vec!["pack", "action", "trigger", "rule", "execution"]; for table_name in tables { let row = sqlx::query(&format!( r#" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = '{}' ) as exists "#, table_name )) .fetch_one(&pool) .await .unwrap(); let exists: bool = row.get("exists"); assert!( exists, "Table '{}' does not exist - migrations may not have run", table_name ); } } #[tokio::test] async fn test_pack_table_exists() { let pool = create_test_pool().await.unwrap(); let row = sqlx::query( r#" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'pack' ) as exists "#, ) .fetch_one(&pool) .await .unwrap(); let exists: bool = row.get("exists"); assert!(exists, "pack table does not exist"); } #[tokio::test] async fn test_action_table_exists() { let pool = create_test_pool().await.unwrap(); let row = sqlx::query( r#" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'action' ) as exists "#, ) .fetch_one(&pool) .await .unwrap(); let exists: bool = row.get("exists"); assert!(exists, "action table does not exist"); } #[tokio::test] async fn test_trigger_table_exists() { let pool = create_test_pool().await.unwrap(); let row = sqlx::query( r#" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'trigger' ) as exists "#, ) .fetch_one(&pool) .await .unwrap(); let exists: bool = row.get("exists"); assert!(exists, "trigger table does not exist"); } #[tokio::test] async fn test_sensor_table_exists() { let pool = create_test_pool().await.unwrap(); let row = sqlx::query( r#" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'sensor' ) as exists "#, ) .fetch_one(&pool) .await .unwrap(); let exists: bool = row.get("exists"); assert!(exists, "sensor table does not exist"); } #[tokio::test] async fn test_rule_table_exists() { let pool = create_test_pool().await.unwrap(); let row = sqlx::query( r#" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'rule' ) as exists "#, ) .fetch_one(&pool) .await .unwrap(); let exists: bool = row.get("exists"); assert!(exists, "rule table does not exist"); } #[tokio::test] async fn test_execution_table_exists() { let pool = create_test_pool().await.unwrap(); let row = sqlx::query( r#" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'execution' ) as exists "#, ) .fetch_one(&pool) .await .unwrap(); let exists: bool = row.get("exists"); assert!(exists, "execution table does not exist"); } #[tokio::test] async fn test_event_table_exists() { let pool = create_test_pool().await.unwrap(); let row = sqlx::query( r#" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'event' ) as exists "#, ) .fetch_one(&pool) .await .unwrap(); let exists: bool = row.get("exists"); assert!(exists, "event table does not exist"); } #[tokio::test] async fn test_enforcement_table_exists() { let pool = create_test_pool().await.unwrap(); let row = sqlx::query( r#" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'enforcement' ) as exists "#, ) .fetch_one(&pool) .await .unwrap(); let exists: bool = row.get("exists"); assert!(exists, "enforcement table does not exist"); } #[tokio::test] async fn test_inquiry_table_exists() { let pool = create_test_pool().await.unwrap(); let row = sqlx::query( r#" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'inquiry' ) as exists "#, ) .fetch_one(&pool) .await .unwrap(); let exists: bool = row.get("exists"); assert!(exists, "inquiry table does not exist"); } #[tokio::test] async fn test_identity_table_exists() { let pool = create_test_pool().await.unwrap(); let row = sqlx::query( r#" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'identity' ) as exists "#, ) .fetch_one(&pool) .await .unwrap(); let exists: bool = row.get("exists"); assert!(exists, "identity table does not exist"); } #[tokio::test] async fn test_key_table_exists() { let pool = create_test_pool().await.unwrap(); let row = sqlx::query( r#" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'key' ) as exists "#, ) .fetch_one(&pool) .await .unwrap(); let exists: bool = row.get("exists"); assert!(exists, "key table does not exist"); } #[tokio::test] async fn test_notification_table_exists() { let pool = create_test_pool().await.unwrap(); let row = sqlx::query( r#" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'notification' ) as exists "#, ) .fetch_one(&pool) .await .unwrap(); let exists: bool = row.get("exists"); assert!(exists, "notification table does not exist"); } #[tokio::test] async fn test_runtime_table_exists() { let pool = create_test_pool().await.unwrap(); let row = sqlx::query( r#" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'runtime' ) as exists "#, ) .fetch_one(&pool) .await .unwrap(); let exists: bool = row.get("exists"); assert!(exists, "runtime table does not exist"); } #[tokio::test] async fn test_worker_table_exists() { let pool = create_test_pool().await.unwrap(); let row = sqlx::query( r#" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'worker' ) as exists "#, ) .fetch_one(&pool) .await .unwrap(); let exists: bool = row.get("exists"); assert!(exists, "worker table does not exist"); } #[tokio::test] async fn test_pack_columns() { let pool = create_test_pool().await.unwrap(); // Verify all expected columns exist in pack table let columns: Vec = sqlx::query( r#" SELECT column_name FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'pack' ORDER BY column_name "#, ) .fetch_all(&pool) .await .unwrap() .iter() .map(|row| row.get("column_name")) .collect(); let expected_columns = vec![ "conf_schema", "config", "created", "dependencies", "description", "id", "is_standard", "label", "meta", "ref", "runtime_deps", "tags", "updated", "version", ]; for col in &expected_columns { assert!( columns.contains(&col.to_string()), "Column '{}' not found in pack table", col ); } } #[tokio::test] async fn test_action_columns() { let pool = create_test_pool().await.unwrap(); // Verify all expected columns exist in action table let columns: Vec = sqlx::query( r#" SELECT column_name FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'action' ORDER BY column_name "#, ) .fetch_all(&pool) .await .unwrap() .iter() .map(|row| row.get("column_name")) .collect(); let expected_columns = vec![ "created", "description", "entrypoint", "id", "label", "out_schema", "pack", "pack_ref", "param_schema", "ref", "runtime", "updated", ]; for col in &expected_columns { assert!( columns.contains(&col.to_string()), "Column '{}' not found in action table", col ); } } #[tokio::test] async fn test_timestamps_auto_populated() { let pool = create_test_pool().await.unwrap(); clean_database(&pool).await.unwrap(); // Create a pack and verify timestamps are set let pack = PackFixture::new("timestamp_pack") .create(&pool) .await .unwrap(); // Timestamps should be set to current time let now = chrono::Utc::now(); assert!(pack.created <= now); assert!(pack.updated <= now); assert!(pack.created <= pack.updated); } #[tokio::test] async fn test_json_column_storage() { let pool = create_test_pool().await.unwrap(); clean_database(&pool).await.unwrap(); // Create pack with JSON data let pack = PackFixture::new("json_pack") .with_description("Pack with JSON data") .create(&pool) .await .unwrap(); // Verify JSON data is stored and retrieved correctly assert!(pack.conf_schema.is_object()); assert!(pack.config.is_object()); assert!(pack.meta.is_object()); } #[tokio::test] async fn test_array_column_storage() { let pool = create_test_pool().await.unwrap(); clean_database(&pool).await.unwrap(); // Create pack with arrays let pack = PackFixture::new("array_pack") .with_tags(vec![ "test".to_string(), "example".to_string(), "demo".to_string(), ]) .create(&pool) .await .unwrap(); // Verify arrays are stored correctly assert_eq!(pack.tags.len(), 3); assert!(pack.tags.contains(&"test".to_string())); assert!(pack.tags.contains(&"example".to_string())); assert!(pack.tags.contains(&"demo".to_string())); } #[tokio::test] async fn test_unique_constraints() { let pool = create_test_pool().await.unwrap(); clean_database(&pool).await.unwrap(); // Create a pack PackFixture::new("unique_pack").create(&pool).await.unwrap(); // Try to create another pack with the same ref - should fail let result = PackFixture::new("unique_pack").create(&pool).await; assert!(result.is_err(), "Should not allow duplicate pack refs"); } #[tokio::test] async fn test_foreign_key_constraints() { let pool = create_test_pool().await.unwrap(); clean_database(&pool).await.unwrap(); // Try to create an action with non-existent pack_id - should fail let result = sqlx::query( r#" INSERT INTO attune.action (ref, pack, pack_ref, label, description, entrypoint) VALUES ($1, $2, $3, $4, $5, $6) "#, ) .bind("test_pack.test_action") .bind(99999i64) // Non-existent pack ID .bind("test_pack") .bind("Test Action") .bind("Test action description") .bind("main.py") .execute(&pool) .await; assert!( result.is_err(), "Should not allow action with non-existent pack" ); } #[tokio::test] async fn test_enum_types_exist() { let pool = create_test_pool().await.unwrap(); // Check that custom enum types are created let enums: Vec = sqlx::query( r#" SELECT typname FROM pg_type WHERE typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema()) AND typtype = 'e' ORDER BY typname "#, ) .fetch_all(&pool) .await .unwrap() .iter() .map(|row| row.get("typname")) .collect(); let expected_enums = vec![ "artifact_retention_enum", "artifact_type_enum", "enforcement_condition_enum", "enforcement_status_enum", "execution_status_enum", "inquiry_status_enum", "notification_status_enum", "owner_type_enum", "policy_method_enum", "worker_status_enum", "worker_type_enum", ]; for enum_type in &expected_enums { assert!( enums.contains(&enum_type.to_string()), "Enum type '{}' not found", enum_type ); } }