fn task_events_test_db() -> roboticus_db::Database {
let db = roboticus_db::Database::new(":memory:").expect("in-memory db");
db.conn()
.execute_batch(
"CREATE TABLE IF NOT EXISTS task_events ( \
id TEXT PRIMARY KEY, \
task_id TEXT NOT NULL, \
parent_task_id TEXT, \
assigned_to TEXT, \
event_type TEXT NOT NULL CHECK ( \
event_type IN ('pending','assigned','running','progress','completed','failed','cancelled','retry') \
), \
summary TEXT, \
detail_json TEXT, \
percentage REAL, \
retry_count INTEGER NOT NULL DEFAULT 0, \
created_at TEXT NOT NULL DEFAULT (datetime('now')) \
); \
CREATE INDEX IF NOT EXISTS idx_task_events_task_id ON task_events(task_id); \
CREATE INDEX IF NOT EXISTS idx_task_events_assigned_to ON task_events(assigned_to); \
CREATE INDEX IF NOT EXISTS idx_task_events_created ON task_events(created_at DESC);",
)
.expect("create task_events table");
db
}
#[test]
fn task_events_api_response_shape() {
use roboticus_db::task_events::{TaskEventRow, TaskLifecycleState};
let event = TaskEventRow {
id: "evt-1".into(),
task_id: "task-1".into(),
parent_task_id: None,
assigned_to: Some("code-analyst".into()),
event_type: TaskLifecycleState::Running,
summary: Some("Analyzing code".into()),
detail_json: None,
percentage: Some(30.0),
retry_count: 0,
created_at: "2026-03-23T10:00:00".into(),
};
let json = serde_json::to_value(&event).unwrap();
assert_eq!(json["event_type"], "running");
assert_eq!(json["task_id"], "task-1");
assert_eq!(json["percentage"], 30.0);
}
#[test]
fn task_lifecycle_state_is_terminal() {
use roboticus_db::task_events::TaskLifecycleState;
assert!(TaskLifecycleState::Completed.is_terminal());
assert!(TaskLifecycleState::Failed.is_terminal());
assert!(TaskLifecycleState::Cancelled.is_terminal());
assert!(!TaskLifecycleState::Running.is_terminal());
assert!(!TaskLifecycleState::Pending.is_terminal());
assert!(!TaskLifecycleState::Assigned.is_terminal());
}
#[test]
fn task_lifecycle_state_as_str_roundtrip() {
use roboticus_db::task_events::TaskLifecycleState;
let states = [
TaskLifecycleState::Pending,
TaskLifecycleState::Running,
TaskLifecycleState::Failed,
TaskLifecycleState::Completed,
];
for s in states {
let parsed = TaskLifecycleState::from_str_opt(s.as_str());
assert_eq!(parsed, Some(s), "round-trip failed for {}", s.as_str());
}
}
#[test]
fn agent_task_state_returns_idle_for_no_events() {
let db = task_events_test_db();
let state = agent_task_state(&db, "nonexistent-agent");
assert_eq!(state, "idle");
}
#[test]
fn agent_task_state_returns_running_for_active_task() {
use roboticus_db::task_events::{TaskEventRow, TaskLifecycleState, insert_task_event};
let db = task_events_test_db();
insert_task_event(
&db,
&TaskEventRow {
id: "evt-run-1".into(),
task_id: "task-run-1".into(),
parent_task_id: None,
assigned_to: Some("worker-agent".into()),
event_type: TaskLifecycleState::Running,
summary: Some("Doing work".into()),
detail_json: None,
percentage: Some(50.0),
retry_count: 0,
created_at: String::new(),
},
)
.unwrap();
assert_eq!(agent_task_state(&db, "worker-agent"), "running");
}
#[test]
fn agent_task_state_returns_idle_after_completed_task() {
use roboticus_db::task_events::{TaskEventRow, TaskLifecycleState, insert_task_event};
let db = task_events_test_db();
insert_task_event(
&db,
&TaskEventRow {
id: "evt-done-1".into(),
task_id: "task-done-1".into(),
parent_task_id: None,
assigned_to: Some("done-agent".into()),
event_type: TaskLifecycleState::Running,
summary: None,
detail_json: None,
percentage: None,
retry_count: 0,
created_at: "2026-03-23 10:00:00".into(),
},
)
.unwrap();
insert_task_event(
&db,
&TaskEventRow {
id: "evt-done-2".into(),
task_id: "task-done-1".into(),
parent_task_id: None,
assigned_to: Some("done-agent".into()),
event_type: TaskLifecycleState::Completed,
summary: None,
detail_json: None,
percentage: None,
retry_count: 0,
created_at: "2026-03-23 10:00:01".into(),
},
)
.unwrap();
assert_eq!(agent_task_state(&db, "done-agent"), "idle");
}
#[test]
fn task_event_feed_shape() {
let db = task_events_test_db();
let feed = task_event_feed(&db);
assert!(feed["recent_events"].is_array());
assert!(feed["active_tasks"].is_array());
assert_eq!(feed["active_count"].as_u64(), Some(0));
}