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
351 lines
11 KiB
Rust
351 lines
11 KiB
Rust
//! Integration tests for queue stats repository
|
|
//!
|
|
//! Tests queue statistics persistence and retrieval operations.
|
|
|
|
use attune_common::repositories::queue_stats::{QueueStatsRepository, UpsertQueueStatsInput};
|
|
use chrono::Utc;
|
|
|
|
mod helpers;
|
|
use helpers::{ActionFixture, PackFixture};
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_upsert_queue_stats() {
|
|
let pool = helpers::create_test_pool().await.unwrap();
|
|
|
|
// Create test pack and action using fixtures
|
|
let pack = PackFixture::new_unique("test").create(&pool).await.unwrap();
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "test_action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Upsert queue stats (insert)
|
|
let input = UpsertQueueStatsInput {
|
|
action_id: action.id,
|
|
queue_length: 5,
|
|
active_count: 2,
|
|
max_concurrent: 3,
|
|
oldest_enqueued_at: Some(Utc::now()),
|
|
total_enqueued: 100,
|
|
total_completed: 95,
|
|
};
|
|
|
|
let stats = QueueStatsRepository::upsert(&pool, input.clone())
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(stats.action_id, action.id);
|
|
assert_eq!(stats.queue_length, 5);
|
|
assert_eq!(stats.active_count, 2);
|
|
assert_eq!(stats.max_concurrent, 3);
|
|
assert_eq!(stats.total_enqueued, 100);
|
|
assert_eq!(stats.total_completed, 95);
|
|
assert!(stats.oldest_enqueued_at.is_some());
|
|
|
|
// Upsert again (update)
|
|
let update_input = UpsertQueueStatsInput {
|
|
action_id: action.id,
|
|
queue_length: 3,
|
|
active_count: 3,
|
|
max_concurrent: 3,
|
|
oldest_enqueued_at: None,
|
|
total_enqueued: 110,
|
|
total_completed: 107,
|
|
};
|
|
|
|
let updated_stats = QueueStatsRepository::upsert(&pool, update_input)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(updated_stats.action_id, action.id);
|
|
assert_eq!(updated_stats.queue_length, 3);
|
|
assert_eq!(updated_stats.active_count, 3);
|
|
assert_eq!(updated_stats.total_enqueued, 110);
|
|
assert_eq!(updated_stats.total_completed, 107);
|
|
assert!(updated_stats.oldest_enqueued_at.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_find_queue_stats_by_action() {
|
|
let pool = helpers::create_test_pool().await.unwrap();
|
|
|
|
// Create test pack and action
|
|
let pack = PackFixture::new_unique("test").create(&pool).await.unwrap();
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "test_action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
// No stats initially
|
|
let result = QueueStatsRepository::find_by_action(&pool, action.id)
|
|
.await
|
|
.unwrap();
|
|
assert!(result.is_none());
|
|
|
|
// Create stats
|
|
let input = UpsertQueueStatsInput {
|
|
action_id: action.id,
|
|
queue_length: 10,
|
|
active_count: 5,
|
|
max_concurrent: 5,
|
|
oldest_enqueued_at: Some(Utc::now()),
|
|
total_enqueued: 200,
|
|
total_completed: 190,
|
|
};
|
|
|
|
QueueStatsRepository::upsert(&pool, input).await.unwrap();
|
|
|
|
// Find stats
|
|
let stats = QueueStatsRepository::find_by_action(&pool, action.id)
|
|
.await
|
|
.unwrap()
|
|
.expect("Stats should exist");
|
|
|
|
assert_eq!(stats.action_id, action.id);
|
|
assert_eq!(stats.queue_length, 10);
|
|
assert_eq!(stats.active_count, 5);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_list_active_queue_stats() {
|
|
let pool = helpers::create_test_pool().await.unwrap();
|
|
|
|
// Create test pack
|
|
let pack = PackFixture::new_unique("test").create(&pool).await.unwrap();
|
|
|
|
// Create multiple actions with different queue states
|
|
for i in 0..3 {
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, &format!("action_{}", i))
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let input = if i == 0 {
|
|
// Active queue
|
|
UpsertQueueStatsInput {
|
|
action_id: action.id,
|
|
queue_length: 5,
|
|
active_count: 2,
|
|
max_concurrent: 3,
|
|
oldest_enqueued_at: Some(Utc::now()),
|
|
total_enqueued: 50,
|
|
total_completed: 45,
|
|
}
|
|
} else if i == 1 {
|
|
// Active executions but no queue
|
|
UpsertQueueStatsInput {
|
|
action_id: action.id,
|
|
queue_length: 0,
|
|
active_count: 3,
|
|
max_concurrent: 3,
|
|
oldest_enqueued_at: None,
|
|
total_enqueued: 30,
|
|
total_completed: 27,
|
|
}
|
|
} else {
|
|
// Idle (should not appear in active list)
|
|
UpsertQueueStatsInput {
|
|
action_id: action.id,
|
|
queue_length: 0,
|
|
active_count: 0,
|
|
max_concurrent: 3,
|
|
oldest_enqueued_at: None,
|
|
total_enqueued: 20,
|
|
total_completed: 20,
|
|
}
|
|
};
|
|
|
|
QueueStatsRepository::upsert(&pool, input).await.unwrap();
|
|
}
|
|
|
|
// List active queues
|
|
let active_stats = QueueStatsRepository::list_active(&pool).await.unwrap();
|
|
|
|
// Should only return entries with queue_length > 0 or active_count > 0
|
|
// At least 2 from our test data (may be more from other tests)
|
|
let our_active = active_stats
|
|
.iter()
|
|
.filter(|s| s.queue_length > 0 || s.active_count > 0)
|
|
.count();
|
|
assert!(our_active >= 2);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_delete_queue_stats() {
|
|
let pool = helpers::create_test_pool().await.unwrap();
|
|
|
|
// Create test pack and action
|
|
let pack = PackFixture::new_unique("test").create(&pool).await.unwrap();
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "test_action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create stats
|
|
let input = UpsertQueueStatsInput {
|
|
action_id: action.id,
|
|
queue_length: 5,
|
|
active_count: 2,
|
|
max_concurrent: 3,
|
|
oldest_enqueued_at: Some(Utc::now()),
|
|
total_enqueued: 100,
|
|
total_completed: 95,
|
|
};
|
|
|
|
QueueStatsRepository::upsert(&pool, input).await.unwrap();
|
|
|
|
// Verify exists
|
|
let stats = QueueStatsRepository::find_by_action(&pool, action.id)
|
|
.await
|
|
.unwrap();
|
|
assert!(stats.is_some());
|
|
|
|
// Delete
|
|
let deleted = QueueStatsRepository::delete(&pool, action.id)
|
|
.await
|
|
.unwrap();
|
|
assert!(deleted);
|
|
|
|
// Verify deleted
|
|
let stats = QueueStatsRepository::find_by_action(&pool, action.id)
|
|
.await
|
|
.unwrap();
|
|
assert!(stats.is_none());
|
|
|
|
// Delete again (should return false)
|
|
let deleted = QueueStatsRepository::delete(&pool, action.id)
|
|
.await
|
|
.unwrap();
|
|
assert!(!deleted);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_batch_upsert_queue_stats() {
|
|
let pool = helpers::create_test_pool().await.unwrap();
|
|
|
|
// Create test pack
|
|
let pack = PackFixture::new_unique("test").create(&pool).await.unwrap();
|
|
|
|
// Create multiple actions
|
|
let mut inputs = Vec::new();
|
|
for i in 0..5 {
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, &format!("action_{}", i))
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
inputs.push(UpsertQueueStatsInput {
|
|
action_id: action.id,
|
|
queue_length: i,
|
|
active_count: i,
|
|
max_concurrent: 5,
|
|
oldest_enqueued_at: if i > 0 { Some(Utc::now()) } else { None },
|
|
total_enqueued: (i * 10) as i64,
|
|
total_completed: (i * 9) as i64,
|
|
});
|
|
}
|
|
|
|
// Batch upsert
|
|
let results = QueueStatsRepository::batch_upsert(&pool, inputs)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(results.len(), 5);
|
|
|
|
// Verify each result
|
|
for (i, stats) in results.iter().enumerate() {
|
|
assert_eq!(stats.queue_length, i as i32);
|
|
assert_eq!(stats.active_count, i as i32);
|
|
assert_eq!(stats.total_enqueued, (i * 10) as i64);
|
|
assert_eq!(stats.total_completed, (i * 9) as i64);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_clear_stale_queue_stats() {
|
|
let pool = helpers::create_test_pool().await.unwrap();
|
|
|
|
// Create test pack
|
|
let pack = PackFixture::new_unique("test").create(&pool).await.unwrap();
|
|
|
|
// Create action with idle stats
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "test_action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create idle stats (queue_length = 0, active_count = 0)
|
|
let input = UpsertQueueStatsInput {
|
|
action_id: action.id,
|
|
queue_length: 0,
|
|
active_count: 0,
|
|
max_concurrent: 3,
|
|
oldest_enqueued_at: None,
|
|
total_enqueued: 100,
|
|
total_completed: 100,
|
|
};
|
|
|
|
QueueStatsRepository::upsert(&pool, input).await.unwrap();
|
|
|
|
// Try to clear stale stats (with very large timeout - should not delete recent stats)
|
|
let _cleared = QueueStatsRepository::clear_stale(&pool, 3600)
|
|
.await
|
|
.unwrap();
|
|
// May or may not be 0 depending on other test data, but our stat should still exist
|
|
|
|
// Verify our stat still exists (was just created)
|
|
let stats = QueueStatsRepository::find_by_action(&pool, action.id)
|
|
.await
|
|
.unwrap();
|
|
assert!(stats.is_some());
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_queue_stats_cascade_delete() {
|
|
let pool = helpers::create_test_pool().await.unwrap();
|
|
|
|
// Create test pack and action
|
|
let pack = PackFixture::new_unique("test").create(&pool).await.unwrap();
|
|
let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "test_action")
|
|
.create(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Create stats
|
|
let input = UpsertQueueStatsInput {
|
|
action_id: action.id,
|
|
queue_length: 5,
|
|
active_count: 2,
|
|
max_concurrent: 3,
|
|
oldest_enqueued_at: Some(Utc::now()),
|
|
total_enqueued: 100,
|
|
total_completed: 95,
|
|
};
|
|
|
|
QueueStatsRepository::upsert(&pool, input).await.unwrap();
|
|
|
|
// Verify stats exist
|
|
let stats = QueueStatsRepository::find_by_action(&pool, action.id)
|
|
.await
|
|
.unwrap();
|
|
assert!(stats.is_some());
|
|
|
|
// Delete the action (should cascade to queue_stats)
|
|
use attune_common::repositories::action::ActionRepository;
|
|
use attune_common::repositories::Delete;
|
|
ActionRepository::delete(&pool, action.id).await.unwrap();
|
|
|
|
// Verify stats are also deleted (cascade)
|
|
let stats = QueueStatsRepository::find_by_action(&pool, action.id)
|
|
.await
|
|
.unwrap();
|
|
assert!(stats.is_none());
|
|
}
|