#![cfg(feature = "sqlite")]
use a2a_protocol_server::push::{PushConfigStore, SqlitePushConfigStore};
use a2a_protocol_server::store::{SqliteTaskStore, TaskStore};
use a2a_protocol_types::error::A2aResult;
use a2a_protocol_types::params::ListTasksParams;
use a2a_protocol_types::push::TaskPushNotificationConfig;
use a2a_protocol_types::task::{Task, TaskId, TaskState, TaskStatus};
fn make_task(id: &str, context_id: &str) -> Task {
Task {
id: TaskId(id.to_string()),
context_id: a2a_protocol_types::task::ContextId(context_id.to_string()),
status: TaskStatus::new(TaskState::Submitted),
artifacts: None,
history: None,
metadata: None,
}
}
async fn new_task_store() -> SqliteTaskStore {
SqliteTaskStore::new("sqlite::memory:")
.await
.expect("open in-memory sqlite")
}
#[tokio::test]
async fn task_save_and_get() -> A2aResult<()> {
let store = new_task_store().await;
let task = make_task("t1", "ctx1");
store.save(task.clone()).await?;
let got = store.get(&TaskId("t1".into())).await?;
assert!(got.is_some());
assert_eq!(got.unwrap().id.0, "t1");
Ok(())
}
#[tokio::test]
async fn task_get_missing() -> A2aResult<()> {
let store = new_task_store().await;
let got = store.get(&TaskId("nope".into())).await?;
assert!(got.is_none());
Ok(())
}
#[tokio::test]
async fn task_save_upsert() -> A2aResult<()> {
let store = new_task_store().await;
let mut task = make_task("t1", "ctx1");
store.save(task.clone()).await?;
task.status = TaskStatus::new(TaskState::Working);
store.save(task).await?;
let got = store.get(&TaskId("t1".into())).await?.unwrap();
assert_eq!(got.status.state, TaskState::Working);
Ok(())
}
#[tokio::test]
async fn task_insert_if_absent() -> A2aResult<()> {
let store = new_task_store().await;
let task = make_task("t1", "ctx1");
assert!(store.insert_if_absent(task.clone()).await?);
assert!(!store.insert_if_absent(task).await?);
Ok(())
}
#[tokio::test]
async fn task_delete() -> A2aResult<()> {
let store = new_task_store().await;
store.save(make_task("t1", "ctx1")).await?;
store.delete(&TaskId("t1".into())).await?;
assert!(store.get(&TaskId("t1".into())).await?.is_none());
Ok(())
}
#[tokio::test]
async fn task_list_basic() -> A2aResult<()> {
let store = new_task_store().await;
store.save(make_task("a", "ctx1")).await?;
store.save(make_task("b", "ctx1")).await?;
store.save(make_task("c", "ctx2")).await?;
let all = store.list(&ListTasksParams::default()).await?;
assert_eq!(all.tasks.len(), 3);
let filtered = store
.list(&ListTasksParams {
context_id: Some("ctx1".into()),
..Default::default()
})
.await?;
assert_eq!(filtered.tasks.len(), 2);
Ok(())
}
#[tokio::test]
async fn task_list_pagination() -> A2aResult<()> {
let store = new_task_store().await;
for i in 0..5 {
store.save(make_task(&format!("t{i:02}"), "ctx")).await?;
}
let page1 = store
.list(&ListTasksParams {
page_size: Some(2),
..Default::default()
})
.await?;
assert_eq!(page1.tasks.len(), 2);
assert!(page1.next_page_token.is_some());
let page2 = store
.list(&ListTasksParams {
page_size: Some(2),
page_token: page1.next_page_token,
..Default::default()
})
.await?;
assert_eq!(page2.tasks.len(), 2);
Ok(())
}
async fn new_push_store() -> SqlitePushConfigStore {
SqlitePushConfigStore::new("sqlite::memory:")
.await
.expect("open in-memory sqlite")
}
fn make_push_config(task_id: &str) -> TaskPushNotificationConfig {
TaskPushNotificationConfig {
task_id: task_id.to_string(),
id: None,
tenant: None,
url: "https://example.com/push".to_string(),
token: Some("tok".to_string()),
authentication: None,
}
}
#[tokio::test]
async fn push_set_and_get() -> A2aResult<()> {
let store = new_push_store().await;
let config = store.set(make_push_config("t1")).await?;
let id = config.id.as_deref().unwrap();
let got = store.get("t1", id).await?;
assert!(got.is_some());
assert_eq!(got.unwrap().task_id, "t1");
Ok(())
}
#[tokio::test]
async fn push_get_missing() -> A2aResult<()> {
let store = new_push_store().await;
assert!(store.get("t1", "nope").await?.is_none());
Ok(())
}
#[tokio::test]
async fn push_list() -> A2aResult<()> {
let store = new_push_store().await;
store.set(make_push_config("t1")).await?;
store.set(make_push_config("t1")).await?;
store.set(make_push_config("t2")).await?;
let t1_configs = store.list("t1").await?;
assert_eq!(t1_configs.len(), 2);
let t2_configs = store.list("t2").await?;
assert_eq!(t2_configs.len(), 1);
Ok(())
}
#[tokio::test]
async fn push_delete() -> A2aResult<()> {
let store = new_push_store().await;
let config = store.set(make_push_config("t1")).await?;
let id = config.id.as_deref().unwrap();
store.delete("t1", id).await?;
assert!(store.get("t1", id).await?.is_none());
Ok(())
}
#[tokio::test]
async fn push_upsert() -> A2aResult<()> {
let store = new_push_store().await;
let mut config = make_push_config("t1");
config.id = Some("fixed-id".into());
store.set(config.clone()).await?;
config.url = "https://example.com/v2".to_string();
store.set(config).await?;
let configs = store.list("t1").await?;
assert_eq!(configs.len(), 1);
assert_eq!(configs[0].url, "https://example.com/v2");
Ok(())
}