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
1301 lines
37 KiB
Rust
1301 lines
37 KiB
Rust
//! Integration tests for Inquiry repository
|
|
//!
|
|
//! These tests verify CRUD operations, queries, and constraints
|
|
//! for the Inquiry repository.
|
|
|
|
mod helpers;
|
|
|
|
use attune_common::{
|
|
models::enums::InquiryStatus,
|
|
repositories::{
|
|
inquiry::{CreateInquiryInput, InquiryRepository, UpdateInquiryInput},
|
|
Create, Delete, FindById, List, Update,
|
|
},
|
|
Error,
|
|
};
|
|
use chrono::{Duration, Utc};
|
|
use helpers::*;
|
|
use serde_json::json;
|
|
|
|
// ============================================================================
|
|
// CREATE Tests
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_create_inquiry_minimal() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
// Create pack, action, and execution
|
|
let pack = PackFixture::new_unique("inquiry_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create execution for inquiry
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create inquiry with minimal fields
|
|
let input = CreateInquiryInput {
|
|
execution: execution.id,
|
|
prompt: "Approve deployment?".to_string(),
|
|
response_schema: None,
|
|
assigned_to: None,
|
|
status: InquiryStatus::Pending,
|
|
response: None,
|
|
timeout_at: None,
|
|
};
|
|
|
|
let inquiry = InquiryRepository::create(&pool, input).await.unwrap();
|
|
|
|
assert!(inquiry.id > 0);
|
|
assert_eq!(inquiry.execution, execution.id);
|
|
assert_eq!(inquiry.prompt, "Approve deployment?");
|
|
assert_eq!(inquiry.response_schema, None);
|
|
assert_eq!(inquiry.assigned_to, None);
|
|
assert_eq!(inquiry.status, InquiryStatus::Pending);
|
|
assert_eq!(inquiry.response, None);
|
|
assert_eq!(inquiry.timeout_at, None);
|
|
assert_eq!(inquiry.responded_at, None);
|
|
assert!(inquiry.created.timestamp() > 0);
|
|
assert!(inquiry.updated.timestamp() > 0);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_create_inquiry_with_response_schema() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("schema_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let response_schema = json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"approved": {"type": "boolean"},
|
|
"reason": {"type": "string"}
|
|
},
|
|
"required": ["approved"]
|
|
});
|
|
|
|
let input = CreateInquiryInput {
|
|
execution: execution.id,
|
|
prompt: "Approve this action?".to_string(),
|
|
response_schema: Some(response_schema.clone()),
|
|
assigned_to: None,
|
|
status: InquiryStatus::Pending,
|
|
response: None,
|
|
timeout_at: None,
|
|
};
|
|
|
|
let inquiry = InquiryRepository::create(&pool, input).await.unwrap();
|
|
|
|
assert_eq!(inquiry.response_schema, Some(response_schema));
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_create_inquiry_with_timeout() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("timeout_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let timeout_at = Utc::now() + Duration::hours(1);
|
|
|
|
let input = CreateInquiryInput {
|
|
execution: execution.id,
|
|
prompt: "Time-sensitive approval".to_string(),
|
|
response_schema: None,
|
|
assigned_to: None,
|
|
status: InquiryStatus::Pending,
|
|
response: None,
|
|
timeout_at: Some(timeout_at),
|
|
};
|
|
|
|
let inquiry = InquiryRepository::create(&pool, input).await.unwrap();
|
|
|
|
assert!(inquiry.timeout_at.is_some());
|
|
let saved_timeout = inquiry.timeout_at.unwrap();
|
|
// Allow for small timestamp differences (within 1 second)
|
|
assert!((saved_timeout.timestamp() - timeout_at.timestamp()).abs() < 1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_create_inquiry_with_assigned_user() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("assigned_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create an identity to assign to
|
|
use attune_common::repositories::identity::{CreateIdentityInput, IdentityRepository};
|
|
let identity = IdentityRepository::create(
|
|
&pool,
|
|
CreateIdentityInput {
|
|
login: format!("approver_{}", unique_test_id()),
|
|
display_name: Some("Approver User".to_string()),
|
|
attributes: json!({"email": format!("approver_{}@example.com", unique_test_id())}),
|
|
password_hash: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let input = CreateInquiryInput {
|
|
execution: execution.id,
|
|
prompt: "Review and approve".to_string(),
|
|
response_schema: None,
|
|
assigned_to: Some(identity.id),
|
|
status: InquiryStatus::Pending,
|
|
response: None,
|
|
timeout_at: None,
|
|
};
|
|
|
|
let inquiry = InquiryRepository::create(&pool, input).await.unwrap();
|
|
|
|
assert_eq!(inquiry.assigned_to, Some(identity.id));
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_create_inquiry_with_invalid_execution_fails() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
// Try to create inquiry with non-existent execution ID
|
|
let input = CreateInquiryInput {
|
|
execution: 99999,
|
|
prompt: "Test prompt".to_string(),
|
|
response_schema: None,
|
|
assigned_to: None,
|
|
status: InquiryStatus::Pending,
|
|
response: None,
|
|
timeout_at: None,
|
|
};
|
|
|
|
let result = InquiryRepository::create(&pool, input).await;
|
|
|
|
assert!(result.is_err());
|
|
// Foreign key constraint violation
|
|
}
|
|
|
|
// ============================================================================
|
|
// READ Tests
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_find_inquiry_by_id() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("find_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let created_inquiry = InquiryFixture::new_unique(execution.id, "Find me")
|
|
.with_response_schema(json!({"type": "boolean"}))
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let found = InquiryRepository::find_by_id(&pool, created_inquiry.id)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert!(found.is_some());
|
|
let inquiry = found.unwrap();
|
|
assert_eq!(inquiry.id, created_inquiry.id);
|
|
assert_eq!(inquiry.execution, created_inquiry.execution);
|
|
assert_eq!(inquiry.prompt, created_inquiry.prompt);
|
|
assert_eq!(inquiry.status, created_inquiry.status);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_find_inquiry_by_id_not_found() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let result = InquiryRepository::find_by_id(&pool, 99999).await.unwrap();
|
|
|
|
assert!(result.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_get_inquiry_by_id() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("get_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let created_inquiry = InquiryFixture::new_unique(execution.id, "Get me")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let inquiry = InquiryRepository::get_by_id(&pool, created_inquiry.id)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(inquiry.id, created_inquiry.id);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_get_inquiry_by_id_not_found() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let result = InquiryRepository::get_by_id(&pool, 99999).await;
|
|
|
|
assert!(result.is_err());
|
|
assert!(matches!(result.unwrap_err(), Error::NotFound { .. }));
|
|
}
|
|
|
|
// ============================================================================
|
|
// LIST Tests
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_list_inquiries_empty() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let inquiries = InquiryRepository::list(&pool).await.unwrap();
|
|
// May have inquiries from other tests, just verify we can list without error
|
|
drop(inquiries);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_list_inquiries() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("list_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let before_count = InquiryRepository::list(&pool).await.unwrap().len();
|
|
|
|
// Create multiple inquiries
|
|
let mut created_ids = vec![];
|
|
for i in 0..3 {
|
|
let inquiry = InquiryFixture::new_unique(execution.id, &format!("Inquiry {}", i))
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
created_ids.push(inquiry.id);
|
|
}
|
|
|
|
let inquiries = InquiryRepository::list(&pool).await.unwrap();
|
|
|
|
assert!(inquiries.len() >= before_count + 3);
|
|
// Verify our inquiries are in the list
|
|
let our_inquiries: Vec<_> = inquiries
|
|
.iter()
|
|
.filter(|i| created_ids.contains(&i.id))
|
|
.collect();
|
|
assert_eq!(our_inquiries.len(), 3);
|
|
}
|
|
|
|
// ============================================================================
|
|
// UPDATE Tests
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_update_inquiry_status() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("update_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let inquiry = InquiryFixture::new_unique(execution.id, "Update status")
|
|
.with_status(InquiryStatus::Pending)
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let input = UpdateInquiryInput {
|
|
status: Some(InquiryStatus::Responded),
|
|
response: None,
|
|
responded_at: None,
|
|
assigned_to: None,
|
|
};
|
|
|
|
let updated = InquiryRepository::update(&pool, inquiry.id, input)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(updated.id, inquiry.id);
|
|
assert_eq!(updated.status, InquiryStatus::Responded);
|
|
assert!(updated.updated > inquiry.updated);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_update_inquiry_status_transitions() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("transitions_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let inquiry = InquiryFixture::new_unique(execution.id, "Transitions")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Test status transitions: Pending -> Responded
|
|
let updated = InquiryRepository::update(
|
|
&pool,
|
|
inquiry.id,
|
|
UpdateInquiryInput {
|
|
status: Some(InquiryStatus::Responded),
|
|
response: None,
|
|
responded_at: None,
|
|
assigned_to: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(updated.status, InquiryStatus::Responded);
|
|
|
|
// Test status transition: Responded -> Cancelled (although unusual)
|
|
let updated = InquiryRepository::update(
|
|
&pool,
|
|
inquiry.id,
|
|
UpdateInquiryInput {
|
|
status: Some(InquiryStatus::Cancelled),
|
|
response: None,
|
|
responded_at: None,
|
|
assigned_to: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(updated.status, InquiryStatus::Cancelled);
|
|
|
|
// Test Timeout status
|
|
let updated = InquiryRepository::update(
|
|
&pool,
|
|
inquiry.id,
|
|
UpdateInquiryInput {
|
|
status: Some(InquiryStatus::Timeout),
|
|
response: None,
|
|
responded_at: None,
|
|
assigned_to: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(updated.status, InquiryStatus::Timeout);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_update_inquiry_response() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("response_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let inquiry = InquiryFixture::new_unique(execution.id, "Get response")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let response = json!({
|
|
"approved": true,
|
|
"reason": "Looks good to me"
|
|
});
|
|
|
|
let input = UpdateInquiryInput {
|
|
status: None,
|
|
response: Some(response.clone()),
|
|
responded_at: None,
|
|
assigned_to: None,
|
|
};
|
|
|
|
let updated = InquiryRepository::update(&pool, inquiry.id, input)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(updated.response, Some(response));
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_update_inquiry_with_response_and_status() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("both_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let inquiry = InquiryFixture::new_unique(execution.id, "Complete")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let response = json!({"decision": "approved"});
|
|
let responded_at = Utc::now();
|
|
|
|
let input = UpdateInquiryInput {
|
|
status: Some(InquiryStatus::Responded),
|
|
response: Some(response.clone()),
|
|
responded_at: Some(responded_at),
|
|
assigned_to: None,
|
|
};
|
|
|
|
let updated = InquiryRepository::update(&pool, inquiry.id, input)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(updated.status, InquiryStatus::Responded);
|
|
assert_eq!(updated.response, Some(response));
|
|
assert!(updated.responded_at.is_some());
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_update_inquiry_assignment() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("assign_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let inquiry = InquiryFixture::new_unique(execution.id, "Reassign")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create an identity to assign to
|
|
use attune_common::repositories::identity::{CreateIdentityInput, IdentityRepository};
|
|
let identity = IdentityRepository::create(
|
|
&pool,
|
|
CreateIdentityInput {
|
|
login: format!("new_approver_{}", unique_test_id()),
|
|
display_name: Some("New Approver".to_string()),
|
|
password_hash: None,
|
|
attributes: json!({"email": format!("new_approver_{}@example.com", unique_test_id())}),
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let input = UpdateInquiryInput {
|
|
status: None,
|
|
response: None,
|
|
responded_at: None,
|
|
assigned_to: Some(identity.id),
|
|
};
|
|
|
|
let updated = InquiryRepository::update(&pool, inquiry.id, input)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(updated.assigned_to, Some(identity.id));
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_update_inquiry_no_changes() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("nochange_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let inquiry = InquiryFixture::new_unique(execution.id, "No change")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let input = UpdateInquiryInput {
|
|
status: None,
|
|
response: None,
|
|
responded_at: None,
|
|
assigned_to: None,
|
|
};
|
|
|
|
let result = InquiryRepository::update(&pool, inquiry.id, input)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Should return existing inquiry without updating
|
|
assert_eq!(result.id, inquiry.id);
|
|
assert_eq!(result.status, inquiry.status);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_update_inquiry_not_found() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let input = UpdateInquiryInput {
|
|
status: Some(InquiryStatus::Responded),
|
|
response: None,
|
|
responded_at: None,
|
|
assigned_to: None,
|
|
};
|
|
|
|
let result = InquiryRepository::update(&pool, 99999, input).await;
|
|
|
|
// When updating non-existent entity with changes, SQLx returns RowNotFound error
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
// ============================================================================
|
|
// DELETE Tests
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_delete_inquiry() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("delete_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let inquiry = InquiryFixture::new_unique(execution.id, "Delete me")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let deleted = InquiryRepository::delete(&pool, inquiry.id).await.unwrap();
|
|
|
|
assert!(deleted);
|
|
|
|
// Verify it's gone
|
|
let found = InquiryRepository::find_by_id(&pool, inquiry.id)
|
|
.await
|
|
.unwrap();
|
|
assert!(found.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_delete_inquiry_not_found() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let deleted = InquiryRepository::delete(&pool, 99999).await.unwrap();
|
|
|
|
assert!(!deleted);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_delete_execution_cascades_to_inquiries() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("cascade_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create inquiries for this execution
|
|
let inquiry1 = InquiryFixture::new_unique(execution.id, "First")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let inquiry2 = InquiryFixture::new_unique(execution.id, "Second")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Delete the execution - should cascade to inquiries
|
|
use attune_common::repositories::Delete;
|
|
ExecutionRepository::delete(&pool, execution.id)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Verify inquiries are deleted
|
|
let found1 = InquiryRepository::find_by_id(&pool, inquiry1.id)
|
|
.await
|
|
.unwrap();
|
|
assert!(found1.is_none());
|
|
|
|
let found2 = InquiryRepository::find_by_id(&pool, inquiry2.id)
|
|
.await
|
|
.unwrap();
|
|
assert!(found2.is_none());
|
|
}
|
|
|
|
// ============================================================================
|
|
// SPECIALIZED QUERY Tests
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_find_inquiries_by_status() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("status_query_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create inquiries with different statuses
|
|
let inq1 = InquiryFixture::new_unique(execution.id, "Pending 1")
|
|
.with_status(InquiryStatus::Pending)
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let inq2 = InquiryFixture::new_unique(execution.id, "Responded")
|
|
.with_status(InquiryStatus::Responded)
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let inq3 = InquiryFixture::new_unique(execution.id, "Pending 2")
|
|
.with_status(InquiryStatus::Pending)
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let pending_inquiries = InquiryRepository::find_by_status(&pool, InquiryStatus::Pending)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Filter to only our test inquiries
|
|
let our_pending: Vec<_> = pending_inquiries
|
|
.iter()
|
|
.filter(|i| i.id == inq1.id || i.id == inq3.id)
|
|
.collect();
|
|
assert_eq!(our_pending.len(), 2);
|
|
for inquiry in &our_pending {
|
|
assert_eq!(inquiry.status, InquiryStatus::Pending);
|
|
}
|
|
|
|
let responded_inquiries = InquiryRepository::find_by_status(&pool, InquiryStatus::Responded)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Verify our responded inquiry is in the list
|
|
let our_responded: Vec<_> = responded_inquiries
|
|
.iter()
|
|
.filter(|i| i.id == inq2.id)
|
|
.collect();
|
|
assert_eq!(our_responded.len(), 1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_find_inquiries_by_execution() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("exec_query_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution1 = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let execution2 = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create inquiries for execution1
|
|
for i in 0..3 {
|
|
InquiryFixture::new_unique(execution1.id, &format!("Exec1 inquiry {}", i))
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
// Create inquiries for execution2
|
|
for i in 0..2 {
|
|
InquiryFixture::new_unique(execution2.id, &format!("Exec2 inquiry {}", i))
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
let inquiries = InquiryRepository::find_by_execution(&pool, execution1.id)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(inquiries.len(), 3);
|
|
for inquiry in &inquiries {
|
|
assert_eq!(inquiry.execution, execution1.id);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// TIMESTAMP Tests
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_inquiry_timestamps_auto_managed() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("timestamp_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let inquiry = InquiryFixture::new_unique(execution.id, "Timestamps")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let created_time = inquiry.created;
|
|
let updated_time = inquiry.updated;
|
|
|
|
assert!(created_time.timestamp() > 0);
|
|
assert_eq!(created_time, updated_time);
|
|
|
|
// Update and verify timestamp changed
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
|
|
|
let input = UpdateInquiryInput {
|
|
status: Some(InquiryStatus::Responded),
|
|
response: None,
|
|
responded_at: None,
|
|
assigned_to: None,
|
|
};
|
|
|
|
let updated = InquiryRepository::update(&pool, inquiry.id, input)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(updated.created, created_time); // created unchanged
|
|
assert!(updated.updated > updated_time); // updated changed
|
|
}
|
|
|
|
// ============================================================================
|
|
// JSON SCHEMA Tests
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_inquiry_complex_response_schema() {
|
|
let pool = create_test_pool().await.unwrap();
|
|
|
|
let pack = PackFixture::new_unique("schema_complex_pack")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
use attune_common::repositories::execution::{CreateExecutionInput, ExecutionRepository};
|
|
let execution = ExecutionRepository::create(
|
|
&pool,
|
|
CreateExecutionInput {
|
|
action: Some(action.id),
|
|
action_ref: action.r#ref.clone(),
|
|
config: None,
|
|
env_vars: None,
|
|
parent: None,
|
|
enforcement: None,
|
|
executor: None,
|
|
status: attune_common::models::enums::ExecutionStatus::Requested,
|
|
result: None,
|
|
workflow_task: None,
|
|
},
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let complex_schema = json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"severity": {
|
|
"type": "string",
|
|
"enum": ["low", "medium", "high", "critical"]
|
|
},
|
|
"impact_analysis": {
|
|
"type": "object",
|
|
"properties": {
|
|
"affected_systems": {
|
|
"type": "array",
|
|
"items": {"type": "string"}
|
|
},
|
|
"estimated_downtime": {"type": "number"}
|
|
}
|
|
},
|
|
"approval": {"type": "boolean"}
|
|
},
|
|
"required": ["severity", "approval"]
|
|
});
|
|
|
|
let inquiry = InquiryFixture::new_unique(execution.id, "Complex schema")
|
|
.with_response_schema(complex_schema.clone())
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(inquiry.response_schema, Some(complex_schema));
|
|
}
|