Files
attune/crates/common/tests/inquiry_repository_tests.rs
2026-02-04 17:46:30 -06:00

1256 lines
36 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]
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,
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]
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,
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]
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,
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]
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,
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]
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]
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,
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]
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]
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,
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]
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]
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]
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,
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]
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,
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]
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,
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]
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,
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]
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,
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]
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,
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]
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,
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]
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]
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,
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]
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]
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,
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]
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,
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]
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,
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,
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]
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,
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]
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,
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));
}