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
527 lines
15 KiB
Rust
527 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, 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("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);
|
|
}
|
|
}
|