re-uploading work
This commit is contained in:
477
crates/common/tests/action_repository_tests.rs
Normal file
477
crates/common/tests/action_repository_tests.rs
Normal file
@@ -0,0 +1,477 @@
|
||||
//! Integration tests for Action repository
|
||||
//!
|
||||
//! These tests verify CRUD operations, queries, and constraints
|
||||
//! for the Action repository.
|
||||
|
||||
mod helpers;
|
||||
|
||||
use attune_common::repositories::{
|
||||
action::{ActionRepository, CreateActionInput, UpdateActionInput},
|
||||
Create, Delete, FindById, FindByRef, List, Update,
|
||||
};
|
||||
use helpers::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_action() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let pack = PackFixture::new_unique("test_pack")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "test_action")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(action.pack, pack.id);
|
||||
assert_eq!(action.pack_ref, pack.r#ref);
|
||||
assert!(action.r#ref.contains("test_pack_"));
|
||||
assert!(action.r#ref.contains(".test_action_"));
|
||||
assert!(action.created.timestamp() > 0);
|
||||
assert!(action.updated.timestamp() > 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_action_with_optional_fields() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let pack = PackFixture::new_unique("test_pack")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "full_action")
|
||||
.with_label("Full Test Action")
|
||||
.with_description("Action with all optional fields")
|
||||
.with_entrypoint("custom.py")
|
||||
.with_param_schema(json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"}
|
||||
}
|
||||
}))
|
||||
.with_out_schema(json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {"type": "string"}
|
||||
}
|
||||
}))
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(action.label, "Full Test Action");
|
||||
assert_eq!(action.description, "Action with all optional fields");
|
||||
assert_eq!(action.entrypoint, "custom.py");
|
||||
assert!(action.param_schema.is_some());
|
||||
assert!(action.out_schema.is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_find_action_by_id() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let pack = PackFixture::new_unique("test_pack")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let created = ActionFixture::new_unique(pack.id, &pack.r#ref, "test_action")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let found = ActionRepository::find_by_id(&pool, created.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(found.is_some());
|
||||
let action = found.unwrap();
|
||||
assert_eq!(action.id, created.id);
|
||||
assert_eq!(action.r#ref, created.r#ref);
|
||||
assert_eq!(action.pack, pack.id);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_find_action_by_id_not_found() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let found = ActionRepository::find_by_id(&pool, 99999).await.unwrap();
|
||||
|
||||
assert!(found.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_find_action_by_ref() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let pack = PackFixture::new_unique("test_pack")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let created = ActionFixture::new_unique(pack.id, &pack.r#ref, "test_action")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let found = ActionRepository::find_by_ref(&pool, &created.r#ref)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(found.is_some());
|
||||
let action = found.unwrap();
|
||||
assert_eq!(action.id, created.id);
|
||||
assert_eq!(action.r#ref, created.r#ref);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_find_action_by_ref_not_found() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let found = ActionRepository::find_by_ref(&pool, "nonexistent.action")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(found.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list_actions() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let pack = PackFixture::new_unique("test_pack")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Create multiple actions
|
||||
ActionFixture::new_unique(pack.id, &pack.r#ref, "action1")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
ActionFixture::new_unique(pack.id, &pack.r#ref, "action2")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
ActionFixture::new_unique(pack.id, &pack.r#ref, "action3")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let actions = ActionRepository::list(&pool).await.unwrap();
|
||||
|
||||
// Should contain at least our created actions
|
||||
assert!(actions.len() >= 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list_actions_empty() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let actions = ActionRepository::list(&pool).await.unwrap();
|
||||
// May have actions from other tests, just verify we can list without error
|
||||
drop(actions);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_action() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let pack = PackFixture::new_unique("test_pack")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "test_action")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let original_updated = action.updated;
|
||||
|
||||
// Wait a bit to ensure timestamp difference
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
||||
|
||||
let update = UpdateActionInput {
|
||||
label: Some("Updated Label".to_string()),
|
||||
description: Some("Updated description".to_string()),
|
||||
entrypoint: None,
|
||||
runtime: None,
|
||||
param_schema: None,
|
||||
out_schema: None,
|
||||
};
|
||||
|
||||
let updated = ActionRepository::update(&pool, action.id, update)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(updated.id, action.id);
|
||||
assert_eq!(updated.label, "Updated Label");
|
||||
assert_eq!(updated.description, "Updated description");
|
||||
assert_eq!(updated.entrypoint, action.entrypoint); // Unchanged
|
||||
assert!(updated.updated > original_updated);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_action_not_found() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let update = UpdateActionInput {
|
||||
label: Some("New Label".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = ActionRepository::update(&pool, 99999, update).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_action_partial() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let pack = PackFixture::new_unique("test_pack")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "test_action")
|
||||
.with_label("Original")
|
||||
.with_description("Original description")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Update only the label
|
||||
let update = UpdateActionInput {
|
||||
label: Some("Updated Label Only".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let updated = ActionRepository::update(&pool, action.id, update)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(updated.label, "Updated Label Only");
|
||||
assert_eq!(updated.description, action.description); // Unchanged
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete_action() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let pack = PackFixture::new_unique("test_pack")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "test_action")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let deleted = ActionRepository::delete(&pool, action.id).await.unwrap();
|
||||
|
||||
assert!(deleted);
|
||||
|
||||
// Verify it's gone
|
||||
let found = ActionRepository::find_by_id(&pool, action.id)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(found.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete_action_not_found() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let deleted = ActionRepository::delete(&pool, 99999).await.unwrap();
|
||||
|
||||
assert!(!deleted);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_actions_cascade_delete_with_pack() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let pack = PackFixture::new_unique("test_pack")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "test_action")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Delete the pack
|
||||
sqlx::query("DELETE FROM pack WHERE id = $1")
|
||||
.bind(pack.id)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Action should be cascade deleted
|
||||
let found = ActionRepository::find_by_id(&pool, action.id)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(found.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_action_foreign_key_constraint() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
// Try to create action with non-existent pack
|
||||
let input = CreateActionInput {
|
||||
r#ref: "test.action".to_string(),
|
||||
pack: 99999,
|
||||
pack_ref: "nonexistent.pack".to_string(),
|
||||
label: "Test Action".to_string(),
|
||||
description: "Test".to_string(),
|
||||
entrypoint: "main.py".to_string(),
|
||||
runtime: None,
|
||||
param_schema: None,
|
||||
out_schema: None,
|
||||
is_adhoc: false,
|
||||
};
|
||||
|
||||
let result = ActionRepository::create(&pool, input).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_multiple_actions_same_pack() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let pack = PackFixture::new_unique("test_pack")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Create multiple actions in the same pack
|
||||
let action1 = ActionFixture::new_unique(pack.id, &pack.r#ref, "action1")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let action2 = ActionFixture::new_unique(pack.id, &pack.r#ref, "action2")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(action1.pack, pack.id);
|
||||
assert_eq!(action2.pack, pack.id);
|
||||
assert_ne!(action1.id, action2.id);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_action_unique_ref_constraint() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let pack = PackFixture::new_unique("test_pack")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Create first action - use non-unique name since we're testing duplicate detection
|
||||
let action_name = helpers::unique_action_name("duplicate");
|
||||
ActionFixture::new(pack.id, &pack.r#ref, &action_name)
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Try to create another action with same ref (should fail)
|
||||
let result = ActionFixture::new(pack.id, &pack.r#ref, &action_name)
|
||||
.create(&pool)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_action_with_json_schemas() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let pack = PackFixture::new_unique("test_pack")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let param_schema = json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"input": {"type": "string"},
|
||||
"count": {"type": "integer"}
|
||||
},
|
||||
"required": ["input"]
|
||||
});
|
||||
|
||||
let out_schema = json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"output": {"type": "string"},
|
||||
"status": {"type": "string"}
|
||||
}
|
||||
});
|
||||
|
||||
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "schema_action")
|
||||
.with_param_schema(param_schema.clone())
|
||||
.with_out_schema(out_schema.clone())
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(action.param_schema, Some(param_schema));
|
||||
assert_eq!(action.out_schema, Some(out_schema));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_action_timestamps_auto_populated() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let pack = PackFixture::new_unique("test_pack")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "test_action")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
assert!(action.created <= now);
|
||||
assert!(action.updated <= now);
|
||||
assert!(action.created <= action.updated);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_action_updated_changes_on_update() {
|
||||
let pool = create_test_pool().await.unwrap();
|
||||
|
||||
let pack = PackFixture::new_unique("test_pack")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "test_action")
|
||||
.create(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let original_created = action.created;
|
||||
let original_updated = action.updated;
|
||||
|
||||
// Wait a bit
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
||||
|
||||
let update = UpdateActionInput {
|
||||
label: Some("Updated".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let updated = ActionRepository::update(&pool, action.id, update)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(updated.created, original_created); // Created unchanged
|
||||
assert!(updated.updated > original_updated); // Updated changed
|
||||
}
|
||||
Reference in New Issue
Block a user