Some checks failed
CI / Rustfmt (push) Successful in 23s
CI / Cargo Audit & Deny (push) Successful in 30s
CI / Web Blocking Checks (push) Successful in 48s
CI / Security Blocking Checks (push) Successful in 8s
CI / Clippy (push) Failing after 1m55s
CI / Web Advisory Checks (push) Successful in 35s
CI / Security Advisory Checks (push) Successful in 37s
CI / Tests (push) Successful in 8m5s
593 lines
15 KiB
Rust
593 lines
15 KiB
Rust
//! 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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_pack_columns() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
// Verify all expected columns exist in pack table
|
|
let columns: Vec<String> = 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]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_action_columns() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
// Verify all expected columns exist in action table
|
|
let columns: Vec<String> = 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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
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]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_enum_types_exist() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
// Check that custom enum types are created
|
|
let enums: Vec<String> = 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
|
|
);
|
|
}
|
|
}
|