Files
attune/crates/common/tests/pack_repository_tests.rs
David Culbreth f96861d417
Some checks failed
CI / Clippy (push) Failing after 3m6s
CI / Rustfmt (push) Failing after 3m9s
CI / Cargo Audit & Deny (push) Successful in 5m2s
CI / Tests (push) Successful in 8m15s
CI / Security Blocking Checks (push) Successful in 10s
CI / Web Advisory Checks (push) Successful in 1m4s
CI / Web Blocking Checks (push) Failing after 4m52s
Publish Images And Chart / Resolve Publish Metadata (push) Successful in 2s
CI / Security Advisory Checks (push) Successful in 1m31s
Publish Images And Chart / Publish init-user (push) Failing after 30s
Publish Images And Chart / Publish init-packs (push) Failing after 1m41s
Publish Images And Chart / Publish migrations (push) Failing after 10s
Publish Images And Chart / Publish web (push) Failing after 11s
Publish Images And Chart / Publish sensor (push) Failing after 32s
Publish Images And Chart / Publish worker (push) Failing after 11s
Publish Images And Chart / Publish executor (push) Failing after 11s
Publish Images And Chart / Publish notifier (push) Failing after 9s
Publish Images And Chart / Publish api (push) Failing after 31s
Publish Images And Chart / Publish Helm Chart (push) Has been skipped
properly handling patch updates
2026-03-17 12:17:58 -05:00

529 lines
15 KiB
Rust

//! Integration tests for Pack repository
//!
//! These tests verify all CRUD operations, transactions, error handling,
//! and constraint validation for the Pack repository.
mod helpers;
use attune_common::repositories::pack::{self, PackRepository};
use attune_common::repositories::{
Create, Delete, FindById, FindByRef, List, Pagination, Patch, Update,
};
use attune_common::Error;
use helpers::*;
use serde_json::json;
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_pack() {
let pool = create_test_pool().await.unwrap();
let pack = PackFixture::new_unique("test_pack")
.with_label("Test Pack")
.with_version("1.0.0")
.with_description("A test pack")
.create(&pool)
.await
.unwrap();
assert!(pack.r#ref.starts_with("test_pack_"));
assert_eq!(pack.version, "1.0.0");
assert_eq!(pack.label, "Test Pack");
assert_eq!(pack.description, Some("A test pack".to_string()));
assert!(pack.created.timestamp() > 0);
assert!(pack.updated.timestamp() > 0);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_pack_duplicate_ref() {
let pool = create_test_pool().await.unwrap();
// Create first pack - use a specific unique ref for this test
let unique_ref = helpers::unique_pack_ref("duplicate_test");
PackFixture::new(&unique_ref).create(&pool).await.unwrap();
// Try to create pack with same ref (should fail due to unique constraint)
let result = PackFixture::new(&unique_ref).create(&pool).await;
assert!(result.is_err());
let error = result.unwrap_err();
assert!(matches!(error, Error::AlreadyExists { .. }));
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_pack_with_tags() {
let pool = create_test_pool().await.unwrap();
let pack = PackFixture::new_unique("tagged_pack")
.with_tags(vec!["test".to_string(), "automation".to_string()])
.create(&pool)
.await
.unwrap();
assert_eq!(pack.tags.len(), 2);
assert!(pack.tags.contains(&"test".to_string()));
assert!(pack.tags.contains(&"automation".to_string()));
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_pack_standard() {
let pool = create_test_pool().await.unwrap();
let pack = PackFixture::new_unique("standard_pack")
.with_standard(true)
.create(&pool)
.await
.unwrap();
assert!(pack.is_standard);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_pack_by_id() {
let pool = create_test_pool().await.unwrap();
let created = PackFixture::new_unique("find_pack")
.create(&pool)
.await
.unwrap();
let found = PackRepository::find_by_id(&pool, created.id)
.await
.unwrap()
.expect("Pack not found");
assert_eq!(found.id, created.id);
assert_eq!(found.r#ref, created.r#ref);
assert_eq!(found.label, created.label);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_pack_by_id_not_found() {
let pool = create_test_pool().await.unwrap();
let result = PackRepository::find_by_id(&pool, 999999).await.unwrap();
assert!(result.is_none());
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_pack_by_ref() {
let pool = create_test_pool().await.unwrap();
let created = PackFixture::new_unique("ref_pack")
.create(&pool)
.await
.unwrap();
let found = PackRepository::find_by_ref(&pool, &created.r#ref)
.await
.unwrap()
.expect("Pack not found");
assert_eq!(found.id, created.id);
assert_eq!(found.r#ref, created.r#ref);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_pack_by_ref_not_found() {
let pool = create_test_pool().await.unwrap();
let result = PackRepository::find_by_ref(&pool, "nonexistent.pack")
.await
.unwrap();
assert!(result.is_none());
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_list_packs() {
let pool = create_test_pool().await.unwrap();
// Create multiple packs
let pack1 = PackFixture::new_unique("pack1")
.create(&pool)
.await
.unwrap();
let pack2 = PackFixture::new_unique("pack2")
.create(&pool)
.await
.unwrap();
let pack3 = PackFixture::new_unique("pack3")
.create(&pool)
.await
.unwrap();
let packs = PackRepository::list(&pool).await.unwrap();
// Should contain at least our created packs
assert!(packs.len() >= 3);
// Verify our packs are in the list
let pack_refs: Vec<String> = packs.iter().map(|p| p.r#ref.clone()).collect();
assert!(pack_refs.contains(&pack1.r#ref));
assert!(pack_refs.contains(&pack2.r#ref));
assert!(pack_refs.contains(&pack3.r#ref));
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_list_packs_with_pagination() {
let pool = create_test_pool().await.unwrap();
// Create test packs
for i in 1..=5 {
PackFixture::new_unique(&format!("pack{}", i))
.create(&pool)
.await
.unwrap();
}
// Test that pagination works by getting pages
let page1 = PackRepository::list_paginated(&pool, Pagination::new(2, 0))
.await
.unwrap();
// First page should have 2 items (or less if there are fewer total)
assert!(page1.len() <= 2);
// Test with different offset
let page2 = PackRepository::list_paginated(&pool, Pagination::new(2, 2))
.await
.unwrap();
// Second page should have items (or be empty if not enough total)
assert!(page2.len() <= 2);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_pack() {
let pool = create_test_pool().await.unwrap();
let pack = PackFixture::new_unique("update_pack")
.with_label("Original Label")
.with_version("1.0.0")
.create(&pool)
.await
.unwrap();
let update_input = pack::UpdatePackInput {
label: Some("Updated Label".to_string()),
version: Some("2.0.0".to_string()),
description: Some(Patch::Set("Updated description".to_string())),
..Default::default()
};
let updated = PackRepository::update(&pool, pack.id, update_input)
.await
.unwrap();
assert_eq!(updated.id, pack.id);
assert_eq!(updated.label, "Updated Label");
assert_eq!(updated.version, "2.0.0");
assert_eq!(updated.description, Some("Updated description".to_string()));
assert_eq!(updated.r#ref, pack.r#ref); // ref should not change
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_pack_partial() {
let pool = create_test_pool().await.unwrap();
let pack = PackFixture::new_unique("partial_pack")
.with_label("Original Label")
.with_version("1.0.0")
.with_description("Original description")
.create(&pool)
.await
.unwrap();
// Update only the label
let update_input = pack::UpdatePackInput {
label: Some("New Label".to_string()),
..Default::default()
};
let updated = PackRepository::update(&pool, pack.id, update_input)
.await
.unwrap();
assert_eq!(updated.label, "New Label");
assert_eq!(updated.version, "1.0.0"); // version unchanged
assert_eq!(updated.description, pack.description); // description unchanged
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_pack_not_found() {
let pool = create_test_pool().await.unwrap();
let update_input = pack::UpdatePackInput {
label: Some("Updated".to_string()),
..Default::default()
};
let result = PackRepository::update(&pool, 999999, update_input).await;
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::NotFound { .. }));
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_pack_tags() {
let pool = create_test_pool().await.unwrap();
let pack = PackFixture::new_unique("tags_pack")
.with_tags(vec!["old".to_string()])
.create(&pool)
.await
.unwrap();
let update_input = pack::UpdatePackInput {
tags: Some(vec!["new".to_string(), "updated".to_string()]),
..Default::default()
};
let updated = PackRepository::update(&pool, pack.id, update_input)
.await
.unwrap();
assert_eq!(updated.tags.len(), 2);
assert!(updated.tags.contains(&"new".to_string()));
assert!(updated.tags.contains(&"updated".to_string()));
assert!(!updated.tags.contains(&"old".to_string()));
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_delete_pack() {
let pool = create_test_pool().await.unwrap();
let pack = PackFixture::new_unique("delete_pack")
.create(&pool)
.await
.unwrap();
// Verify pack exists
let found = PackRepository::find_by_id(&pool, pack.id).await.unwrap();
assert!(found.is_some());
// Delete the pack
PackRepository::delete(&pool, pack.id).await.unwrap();
// Verify pack is gone
let not_found = PackRepository::find_by_id(&pool, pack.id).await.unwrap();
assert!(not_found.is_none());
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_delete_pack_not_found() {
let pool = create_test_pool().await.unwrap();
let deleted = PackRepository::delete(&pool, 999999).await.unwrap();
assert!(!deleted, "Should return false when pack doesn't exist");
}
// TODO: Re-enable once ActionFixture is fixed
// #[tokio::test]
// async fn test_delete_pack_cascades_to_actions() {
// let pool = create_test_pool().await.unwrap();
//
// // Create pack with an action
// let pack = PackFixture::new_unique("cascade_pack")
// .create(&pool)
// .await
// .unwrap();
//
// let action = ActionFixture::new(pack.id, "cascade_action")
// .create(&pool)
// .await
// .unwrap();
//
// // Verify action exists
// let found_action = ActionRepository::find_by_id(&pool, action.id)
// .await
// .unwrap();
// assert!(found_action.is_some());
//
// // Delete pack
// PackRepository::delete(&pool, pack.id).await.unwrap();
//
// // Verify action is also deleted (cascade)
// let action_after = ActionRepository::find_by_id(&pool, action.id)
// .await
// .unwrap();
// assert!(action_after.is_none());
// }
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_count_packs() {
let pool = create_test_pool().await.unwrap();
// Get initial count
let count_before = PackRepository::count(&pool).await.unwrap();
// Create some packs
PackFixture::new_unique("pack1")
.create(&pool)
.await
.unwrap();
PackFixture::new_unique("pack2")
.create(&pool)
.await
.unwrap();
PackFixture::new_unique("pack3")
.create(&pool)
.await
.unwrap();
let count_after = PackRepository::count(&pool).await.unwrap();
// Should have at least 3 more packs (may have more from parallel tests)
assert!(count_after >= count_before + 3);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_pack_transaction_commit() {
let pool = create_test_pool().await.unwrap();
// Begin transaction
let mut tx = pool.begin().await.unwrap();
// Create pack in transaction with unique ref
let unique_ref = helpers::unique_pack_ref("tx_pack");
let input = pack::CreatePackInput {
r#ref: unique_ref.clone(),
label: "Transaction Pack".to_string(),
description: None,
version: "1.0.0".to_string(),
conf_schema: json!({}),
config: json!({}),
meta: json!({}),
tags: vec![],
runtime_deps: vec![],
dependencies: vec![],
is_standard: false,
installers: json!({}),
};
let pack = PackRepository::create(&mut *tx, input).await.unwrap();
// Commit transaction
tx.commit().await.unwrap();
// Verify pack exists after commit
let found = PackRepository::find_by_id(&pool, pack.id)
.await
.unwrap()
.expect("Pack should exist after commit");
assert_eq!(found.r#ref, unique_ref);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_pack_transaction_rollback() {
let pool = create_test_pool().await.unwrap();
// Begin transaction
let mut tx = pool.begin().await.unwrap();
// Create pack in transaction with unique ref
let input = pack::CreatePackInput {
r#ref: helpers::unique_pack_ref("rollback_pack"),
label: "Rollback Pack".to_string(),
description: None,
version: "1.0.0".to_string(),
conf_schema: json!({}),
config: json!({}),
meta: json!({}),
tags: vec![],
runtime_deps: vec![],
dependencies: vec![],
is_standard: false,
installers: json!({}),
};
let pack = PackRepository::create(&mut *tx, input).await.unwrap();
let pack_id = pack.id;
// Rollback transaction
tx.rollback().await.unwrap();
// Verify pack does NOT exist after rollback
let not_found = PackRepository::find_by_id(&pool, pack_id).await.unwrap();
assert!(not_found.is_none());
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_pack_invalid_ref_format() {
let pool = create_test_pool().await.unwrap();
let input = pack::CreatePackInput {
r#ref: "invalid pack!@#".to_string(), // Contains invalid characters
label: "Invalid Pack".to_string(),
description: None,
version: "1.0.0".to_string(),
conf_schema: json!({}),
config: json!({}),
meta: json!({}),
tags: vec![],
runtime_deps: vec![],
dependencies: vec![],
is_standard: false,
installers: json!({}),
};
let result = PackRepository::create(&pool, input).await;
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::Validation { .. }));
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_pack_valid_ref_formats() {
let pool = create_test_pool().await.unwrap();
// Valid ref formats - each gets unique suffix
let valid_base_refs = vec![
"simple",
"with_underscores",
"with-hyphens",
"mixed_all-together-123",
];
for base_ref in valid_base_refs {
let unique_ref = helpers::unique_pack_ref(base_ref);
let input = pack::CreatePackInput {
r#ref: unique_ref.clone(),
label: format!("Pack {}", base_ref),
description: None,
version: "1.0.0".to_string(),
conf_schema: json!({}),
config: json!({}),
meta: json!({}),
tags: vec![],
runtime_deps: vec![],
dependencies: vec![],
is_standard: false,
installers: json!({}),
};
let result = PackRepository::create(&pool, input).await;
assert!(result.is_ok(), "Ref '{}' should be valid", unique_ref);
}
}