#![cfg(test)]
use crate::circuit_breaker::{CircuitBreakerConfig, CircuitBreakerStateInternal};
use crate::*;
use celers_core::{Broker, SerializedTask};
use chrono::Utc;
#[test]
fn test_db_task_state_display() {
assert_eq!(DbTaskState::Pending.to_string(), "pending");
assert_eq!(DbTaskState::Processing.to_string(), "processing");
assert_eq!(DbTaskState::Completed.to_string(), "completed");
assert_eq!(DbTaskState::Failed.to_string(), "failed");
assert_eq!(DbTaskState::Cancelled.to_string(), "cancelled");
}
#[test]
fn test_db_task_state_from_str() {
assert_eq!(
"pending".parse::<DbTaskState>().unwrap(),
DbTaskState::Pending
);
assert_eq!(
"processing".parse::<DbTaskState>().unwrap(),
DbTaskState::Processing
);
assert_eq!(
"completed".parse::<DbTaskState>().unwrap(),
DbTaskState::Completed
);
assert_eq!(
"failed".parse::<DbTaskState>().unwrap(),
DbTaskState::Failed
);
assert_eq!(
"cancelled".parse::<DbTaskState>().unwrap(),
DbTaskState::Cancelled
);
assert_eq!(
"PENDING".parse::<DbTaskState>().unwrap(),
DbTaskState::Pending
);
assert_eq!(
"Completed".parse::<DbTaskState>().unwrap(),
DbTaskState::Completed
);
}
#[test]
fn test_db_task_state_invalid() {
assert!("invalid".parse::<DbTaskState>().is_err());
assert!("".parse::<DbTaskState>().is_err());
}
#[test]
fn test_queue_statistics_default() {
let stats = QueueStatistics::default();
assert_eq!(stats.pending, 0);
assert_eq!(stats.processing, 0);
assert_eq!(stats.completed, 0);
assert_eq!(stats.failed, 0);
assert_eq!(stats.cancelled, 0);
assert_eq!(stats.dlq, 0);
assert_eq!(stats.total, 0);
}
#[test]
fn test_db_task_state_serialization() {
let state = DbTaskState::Pending;
let json = serde_json::to_string(&state).unwrap();
assert_eq!(json, "\"pending\"");
let deserialized: DbTaskState = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, state);
}
#[test]
fn test_task_result_status_display() {
assert_eq!(TaskResultStatus::Pending.to_string(), "PENDING");
assert_eq!(TaskResultStatus::Started.to_string(), "STARTED");
assert_eq!(TaskResultStatus::Success.to_string(), "SUCCESS");
assert_eq!(TaskResultStatus::Failure.to_string(), "FAILURE");
assert_eq!(TaskResultStatus::Retry.to_string(), "RETRY");
assert_eq!(TaskResultStatus::Revoked.to_string(), "REVOKED");
}
#[test]
fn test_task_result_status_from_str() {
assert_eq!(
"PENDING".parse::<TaskResultStatus>().unwrap(),
TaskResultStatus::Pending
);
assert_eq!(
"STARTED".parse::<TaskResultStatus>().unwrap(),
TaskResultStatus::Started
);
assert_eq!(
"SUCCESS".parse::<TaskResultStatus>().unwrap(),
TaskResultStatus::Success
);
assert_eq!(
"FAILURE".parse::<TaskResultStatus>().unwrap(),
TaskResultStatus::Failure
);
assert_eq!(
"RETRY".parse::<TaskResultStatus>().unwrap(),
TaskResultStatus::Retry
);
assert_eq!(
"REVOKED".parse::<TaskResultStatus>().unwrap(),
TaskResultStatus::Revoked
);
assert_eq!(
"pending".parse::<TaskResultStatus>().unwrap(),
TaskResultStatus::Pending
);
assert_eq!(
"Success".parse::<TaskResultStatus>().unwrap(),
TaskResultStatus::Success
);
}
#[test]
fn test_task_result_status_invalid() {
assert!("invalid".parse::<TaskResultStatus>().is_err());
assert!("".parse::<TaskResultStatus>().is_err());
}
#[test]
fn test_task_result_status_serialization() {
let status = TaskResultStatus::Success;
let json = serde_json::to_string(&status).unwrap();
assert_eq!(json, "\"success\"");
let deserialized: TaskResultStatus = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, status);
}
#[tokio::test]
#[ignore] async fn test_mysql_broker_creation() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await;
assert!(broker.is_ok());
}
#[tokio::test]
#[ignore] async fn test_mysql_broker_lifecycle() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
broker.migrate().await.unwrap();
let task = SerializedTask::new("test_task".to_string(), vec![1, 2, 3, 4]);
let task_id = task.metadata.id;
let returned_id = broker.enqueue(task.clone()).await.unwrap();
assert_eq!(returned_id, task_id);
let size = broker.queue_size().await.unwrap();
assert!(size >= 1);
let msg = broker.dequeue().await.unwrap();
assert!(msg.is_some());
let msg = msg.unwrap();
assert_eq!(msg.task.metadata.name, "test_task");
broker
.ack(&msg.task.metadata.id, msg.receipt_handle.as_deref())
.await
.unwrap();
}
#[tokio::test]
#[ignore] async fn test_mysql_queue_pause_resume() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
broker.migrate().await.unwrap();
assert!(!broker.is_paused());
broker.pause();
assert!(broker.is_paused());
let task = SerializedTask::new("pause_test".to_string(), vec![1, 2, 3]);
broker.enqueue(task).await.unwrap();
let msg = broker.dequeue().await.unwrap();
assert!(msg.is_none());
broker.resume();
assert!(!broker.is_paused());
let msg = broker.dequeue().await.unwrap();
assert!(msg.is_some());
}
#[tokio::test]
#[ignore] async fn test_mysql_statistics() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
broker.migrate().await.unwrap();
let stats = broker.get_statistics().await.unwrap();
assert!(stats.total >= 0);
}
#[tokio::test]
#[ignore] async fn test_mysql_health_check() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
let health = broker.check_health().await.unwrap();
assert!(health.healthy);
assert!(!health.database_version.is_empty());
}
#[tokio::test]
#[ignore] async fn test_batch_operations() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
broker.migrate().await.unwrap();
let tasks: Vec<_> = (0..10)
.map(|i| SerializedTask::new(format!("task_{}", i), vec![i as u8]))
.collect();
let task_ids = broker.enqueue_batch(tasks).await.unwrap();
assert_eq!(task_ids.len(), 10);
let messages = broker.dequeue_batch(5).await.unwrap();
assert_eq!(messages.len(), 5);
let ack_tasks: Vec<_> = messages
.iter()
.map(|m| (m.task.metadata.id, m.receipt_handle.clone()))
.collect();
broker.ack_batch(&ack_tasks).await.unwrap();
let remaining = broker.queue_size().await.unwrap();
assert_eq!(remaining, 5);
}
#[tokio::test]
#[ignore] async fn test_task_chain() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
broker.migrate().await.unwrap();
let chain = TaskChain::new()
.then(SerializedTask::new("step1".to_string(), vec![1]))
.then(SerializedTask::new("step2".to_string(), vec![2]))
.then(SerializedTask::new("step3".to_string(), vec![3]))
.with_delay(2);
let task_ids = broker.enqueue_chain(chain).await.unwrap();
assert_eq!(task_ids.len(), 3);
let scheduled = broker.list_scheduled_tasks(10, 0).await.unwrap();
assert!(scheduled.len() >= 2); }
#[tokio::test]
#[ignore] async fn test_connection_diagnostics() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
let diag = broker.get_connection_diagnostics();
assert!(diag.max_connections > 0);
assert!(diag.pool_utilization_percent >= 0.0);
assert!(diag.pool_utilization_percent <= 100.0);
}
#[tokio::test]
#[ignore] async fn test_performance_metrics() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
broker.migrate().await.unwrap();
let metrics = broker.get_performance_metrics().await.unwrap();
assert!(metrics.queue_depth >= 0);
assert!(metrics.processing_tasks >= 0);
assert!(metrics.dlq_size >= 0);
assert!(metrics.connection_pool.max_connections > 0);
}
#[tokio::test]
#[ignore] async fn test_migration_tracking() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
broker.migrate().await.unwrap();
let migrations = broker.list_migrations().await.unwrap();
assert!(migrations.len() >= 3);
let versions: Vec<_> = migrations.iter().map(|m| m.version.as_str()).collect();
assert!(versions.contains(&"001"));
assert!(versions.contains(&"002"));
assert!(versions.contains(&"003"));
}
#[tokio::test]
#[ignore] async fn test_is_ready() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
let ready = broker.is_ready().await;
assert!(ready);
}
#[tokio::test]
#[ignore] async fn test_concurrent_dequeue() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
broker.migrate().await.unwrap();
let tasks: Vec<_> = (0..20)
.map(|i| SerializedTask::new(format!("concurrent_{}", i), vec![i as u8]))
.collect();
broker.enqueue_batch(tasks).await.unwrap();
let mut handles = vec![];
for worker_id in 0..5 {
let db_url = database_url.clone();
let handle = tokio::spawn(async move {
let worker_broker = MysqlBroker::new(&db_url).await.unwrap();
let mut dequeued = 0;
for _ in 0..10 {
if let Ok(Some(msg)) = worker_broker.dequeue().await {
dequeued += 1;
let _ = worker_broker
.ack(&msg.task.metadata.id, msg.receipt_handle.as_deref())
.await;
}
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
}
(worker_id, dequeued)
});
handles.push(handle);
}
let results = futures::future::join_all(handles).await;
let total_dequeued: usize = results
.iter()
.filter_map(|r| r.as_ref().ok())
.map(|(_, count)| *count)
.sum();
assert_eq!(total_dequeued, 20);
}
#[tokio::test]
#[ignore] async fn test_skip_locked_behavior() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker1 = MysqlBroker::new(&database_url).await.unwrap();
broker1.migrate().await.unwrap();
for i in 0..10 {
let task = SerializedTask::new(format!("task_{}", i), vec![i as u8]);
broker1.enqueue(task).await.unwrap();
}
let broker2 = MysqlBroker::new(&database_url).await.unwrap();
let (msg1, msg2) = tokio::join!(broker1.dequeue(), broker2.dequeue());
assert!(msg1.is_ok());
assert!(msg2.is_ok());
if let (Ok(Some(m1)), Ok(Some(m2))) = (msg1, msg2) {
assert_ne!(m1.task.metadata.id, m2.task.metadata.id);
}
}
#[test]
fn test_pool_config_default() {
let config = PoolConfig::default();
assert_eq!(config.max_connections, 20);
assert_eq!(config.min_connections, 2);
assert_eq!(config.acquire_timeout_secs, 5);
assert_eq!(config.max_lifetime_secs, Some(1800));
assert_eq!(config.idle_timeout_secs, Some(600));
}
#[test]
fn test_task_chain_builder() {
let task1 = SerializedTask::new("task1".to_string(), vec![1]);
let task2 = SerializedTask::new("task2".to_string(), vec![2]);
let chain = TaskChain::new().then(task1).then(task2).with_delay(5);
assert_eq!(chain.tasks().len(), 2);
assert_eq!(chain.delay_between_secs(), Some(5));
}
#[test]
fn test_worker_statistics_serialization() {
let stats = WorkerStatistics {
worker_id: "worker-123".to_string(),
active_tasks: 5,
completed_tasks: 100,
failed_tasks: 3,
last_seen: Utc::now(),
avg_task_duration_secs: 2.5,
};
let json = serde_json::to_string(&stats).unwrap();
let deserialized: WorkerStatistics = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.worker_id, "worker-123");
assert_eq!(deserialized.active_tasks, 5);
assert_eq!(deserialized.completed_tasks, 100);
assert_eq!(deserialized.failed_tasks, 3);
assert_eq!(deserialized.avg_task_duration_secs, 2.5);
}
#[test]
fn test_task_age_distribution_serialization() {
let dist = TaskAgeDistribution {
bucket_label: "< 1 min".to_string(),
task_count: 42,
oldest_task_age_secs: 55,
};
let json = serde_json::to_string(&dist).unwrap();
let deserialized: TaskAgeDistribution = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.bucket_label, "< 1 min");
assert_eq!(deserialized.task_count, 42);
assert_eq!(deserialized.oldest_task_age_secs, 55);
}
#[test]
fn test_retry_statistics_serialization() {
let stats = RetryStatistics {
task_name: "failing_task".to_string(),
total_retries: 150,
unique_tasks: 50,
avg_retries_per_task: 3.0,
max_retries_observed: 5,
};
let json = serde_json::to_string(&stats).unwrap();
let deserialized: RetryStatistics = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.task_name, "failing_task");
assert_eq!(deserialized.total_retries, 150);
assert_eq!(deserialized.unique_tasks, 50);
assert_eq!(deserialized.avg_retries_per_task, 3.0);
assert_eq!(deserialized.max_retries_observed, 5);
}
#[tokio::test]
#[ignore] async fn test_cancel_batch() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
broker.migrate().await.unwrap();
let mut task_ids = Vec::new();
for i in 0..10 {
let task = SerializedTask::new(format!("task_{}", i), vec![i as u8]);
let task_id = broker.enqueue(task).await.unwrap();
task_ids.push(task_id);
}
let to_cancel = &task_ids[0..5];
let cancelled = broker.cancel_batch(to_cancel).await.unwrap();
assert_eq!(cancelled, 5);
let stats = broker.get_statistics().await.unwrap();
assert_eq!(stats.cancelled, 5);
assert_eq!(stats.pending, 5);
}
#[tokio::test]
#[ignore] async fn test_worker_statistics() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
broker.migrate().await.unwrap();
let task = SerializedTask::new("test_task".to_string(), vec![1, 2, 3]);
broker.enqueue(task).await.unwrap();
let msg = broker
.dequeue_with_worker_id("test-worker-123")
.await
.unwrap()
.unwrap();
let stats = broker
.get_worker_statistics("test-worker-123")
.await
.unwrap();
assert_eq!(stats.worker_id, "test-worker-123");
assert_eq!(stats.active_tasks, 1);
broker
.ack(&msg.task_id(), msg.receipt_handle.as_deref())
.await
.unwrap();
let stats = broker
.get_worker_statistics("test-worker-123")
.await
.unwrap();
assert_eq!(stats.active_tasks, 0);
assert_eq!(stats.completed_tasks, 1);
}
#[tokio::test]
#[ignore] async fn test_count_by_state_quick() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
broker.migrate().await.unwrap();
for i in 0..5 {
let task = SerializedTask::new(format!("task_{}", i), vec![i as u8]);
broker.enqueue(task).await.unwrap();
}
let pending_count = broker
.count_by_state_quick(DbTaskState::Pending)
.await
.unwrap();
assert_eq!(pending_count, 5);
broker.dequeue().await.unwrap();
let processing_count = broker
.count_by_state_quick(DbTaskState::Processing)
.await
.unwrap();
assert_eq!(processing_count, 1);
}
#[tokio::test]
#[ignore] async fn test_task_age_distribution() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
broker.migrate().await.unwrap();
for i in 0..10 {
let task = SerializedTask::new(format!("task_{}", i), vec![i as u8]);
broker.enqueue(task).await.unwrap();
}
let distribution = broker.get_task_age_distribution().await.unwrap();
assert!(!distribution.is_empty());
let youngest = distribution.first().unwrap();
assert_eq!(youngest.bucket_label, "< 1 min");
assert_eq!(youngest.task_count, 10);
}
#[tokio::test]
#[ignore] async fn test_retry_statistics() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
broker.migrate().await.unwrap();
for i in 0..3 {
let task = SerializedTask::new("failing_task".to_string(), vec![i as u8]);
let _task_id = broker.enqueue(task).await.unwrap();
let msg = broker.dequeue().await.unwrap().unwrap();
broker
.reject(&msg.task_id(), msg.receipt_handle.as_deref(), true)
.await
.unwrap();
}
let stats = broker.get_retry_statistics().await.unwrap();
if !stats.is_empty() {
let task_stats = &stats[0];
assert_eq!(task_stats.task_name, "failing_task");
assert!(task_stats.total_retries > 0);
}
}
#[tokio::test]
#[ignore] async fn test_list_active_workers() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
broker.migrate().await.unwrap();
for i in 0..3 {
let task = SerializedTask::new(format!("task_{}", i), vec![i as u8]);
broker.enqueue(task).await.unwrap();
}
let _msg1 = broker
.dequeue_with_worker_id("worker-1")
.await
.unwrap()
.unwrap();
let _msg2 = broker
.dequeue_with_worker_id("worker-2")
.await
.unwrap()
.unwrap();
let workers = broker.list_active_workers().await.unwrap();
assert_eq!(workers.len(), 2);
assert!(workers.contains(&"worker-1".to_string()));
assert!(workers.contains(&"worker-2".to_string()));
}
#[tokio::test]
#[ignore] async fn test_get_all_worker_statistics() {
let database_url = std::env::var("MYSQL_URL")
.unwrap_or_else(|_| "mysql://root:password@localhost/celers_test".to_string());
let broker = MysqlBroker::new(&database_url).await.unwrap();
broker.migrate().await.unwrap();
for i in 0..2 {
let task = SerializedTask::new(format!("task_{}", i), vec![i as u8]);
broker.enqueue(task).await.unwrap();
}
let _msg1 = broker
.dequeue_with_worker_id("worker-alpha")
.await
.unwrap()
.unwrap();
let _msg2 = broker
.dequeue_with_worker_id("worker-beta")
.await
.unwrap()
.unwrap();
let all_stats = broker.get_all_worker_statistics().await.unwrap();
assert_eq!(all_stats.len(), 2);
for stats in &all_stats {
assert!(stats.worker_id == "worker-alpha" || stats.worker_id == "worker-beta");
assert_eq!(stats.active_tasks, 1);
}
}
#[test]
fn test_circuit_breaker_initial_state() {
let config = CircuitBreakerConfig::default();
let cb_internal = CircuitBreakerStateInternal::new(config);
assert_eq!(cb_internal.state, CircuitBreakerState::Closed);
assert_eq!(cb_internal.failure_count, 0);
assert_eq!(cb_internal.success_count, 0);
assert!(cb_internal.last_failure_time.is_none());
}
#[test]
fn test_circuit_breaker_config_default() {
let config = CircuitBreakerConfig::default();
assert_eq!(config.failure_threshold, 5);
assert_eq!(config.timeout_secs, 60);
assert_eq!(config.success_threshold, 2);
}
#[tokio::test]
async fn test_circuit_breaker_stats() {
let database_url = "mysql://test:test@localhost/test";
let result = MysqlBroker::new(database_url).await;
if result.is_err() {
return;
}
let broker = result.unwrap();
let stats = broker.get_circuit_breaker_stats();
assert_eq!(stats.state, CircuitBreakerState::Closed);
assert_eq!(stats.failure_count, 0);
assert_eq!(stats.success_count, 0);
}
#[tokio::test]
async fn test_circuit_breaker_reset() {
let database_url = "mysql://test:test@localhost/test";
let result = MysqlBroker::new(database_url).await;
if result.is_err() {
return;
}
let broker = result.unwrap();
for _ in 0..3 {
broker.record_failure();
}
let stats_before = broker.get_circuit_breaker_stats();
assert_eq!(stats_before.failure_count, 3);
broker.reset_circuit_breaker();
let stats_after = broker.get_circuit_breaker_stats();
assert_eq!(stats_after.state, CircuitBreakerState::Closed);
assert_eq!(stats_after.failure_count, 0);
assert_eq!(stats_after.success_count, 0);
}
#[test]
fn test_circuit_breaker_state_serialization() {
let state = CircuitBreakerState::Closed;
let json = serde_json::to_string(&state).unwrap();
let deserialized: CircuitBreakerState = serde_json::from_str(&json).unwrap();
assert_eq!(state, deserialized);
let state = CircuitBreakerState::Open;
let json = serde_json::to_string(&state).unwrap();
let deserialized: CircuitBreakerState = serde_json::from_str(&json).unwrap();
assert_eq!(state, deserialized);
let state = CircuitBreakerState::HalfOpen;
let json = serde_json::to_string(&state).unwrap();
let deserialized: CircuitBreakerState = serde_json::from_str(&json).unwrap();
assert_eq!(state, deserialized);
}
#[test]
fn test_circuit_breaker_stats_serialization() {
let stats = CircuitBreakerStats {
state: CircuitBreakerState::Open,
failure_count: 5,
success_count: 0,
last_failure_time: Some(Utc::now()),
last_state_change: Utc::now(),
};
let json = serde_json::to_string(&stats).unwrap();
let deserialized: CircuitBreakerStats = serde_json::from_str(&json).unwrap();
assert_eq!(stats.state, deserialized.state);
assert_eq!(stats.failure_count, deserialized.failure_count);
assert_eq!(stats.success_count, deserialized.success_count);
}