use mcp_project::store::ProjectStore;
use mcp_project::types::*;
fn store() -> ProjectStore {
ProjectStore::new()
}
fn project(s: &ProjectStore) -> String {
s.create_project("TEST".into(), "Test".into(), "d".into(), Some("alice".into()), vec!["alice".into()], None, None).id
}
#[test]
fn seeded_project_present() {
let s = store();
let ps = s.list_projects(None);
assert!(ps.iter().any(|p| p.key == "APOLLO"));
}
#[test]
fn task_ids_are_project_keyed() {
let s = store();
let pid = project(&s);
let t = s.create_task(&pid, TaskType::Task, "x".into(), "d".into(), Priority::Medium, "alice".into(), None, None, Some(3.0), None).unwrap();
assert!(t.id.starts_with("TEST-"), "task id should be project-keyed, got {}", t.id);
}
#[test]
fn workflow_transitions_enforced() {
let s = store();
let pid = project(&s);
let t = s.create_task(&pid, TaskType::Task, "x".into(), "d".into(), Priority::Medium, "alice".into(), None, None, None, None).unwrap();
assert!(s.transition_task(&t.id, TaskStatus::InProgress).is_err());
assert!(s.transition_task(&t.id, TaskStatus::Todo).is_ok());
assert!(s.transition_task(&t.id, TaskStatus::InProgress).is_ok());
let done = s.transition_task(&t.id, TaskStatus::Done).unwrap();
assert!(done.completed_at.is_some());
}
#[test]
fn blocked_by_prevents_completion() {
let s = store();
let pid = project(&s);
let a = s.create_task(&pid, TaskType::Task, "A".into(), "".into(), Priority::Medium, "alice".into(), None, None, None, None).unwrap();
let b = s.create_task(&pid, TaskType::Task, "B".into(), "".into(), Priority::Medium, "alice".into(), None, None, None, None).unwrap();
s.add_dependency(&a.id, DependencyType::BlockedBy, &b.id).unwrap();
s.transition_task(&a.id, TaskStatus::Todo).unwrap();
assert!(s.transition_task(&a.id, TaskStatus::InProgress).is_err());
s.transition_task(&b.id, TaskStatus::Todo).unwrap();
s.transition_task(&b.id, TaskStatus::InProgress).unwrap();
s.transition_task(&b.id, TaskStatus::Done).unwrap();
assert!(s.transition_task(&a.id, TaskStatus::InProgress).is_ok());
}
#[test]
fn dependency_cycle_rejected() {
let s = store();
let pid = project(&s);
let a = s.create_task(&pid, TaskType::Task, "A".into(), "".into(), Priority::Medium, "alice".into(), None, None, None, None).unwrap();
let b = s.create_task(&pid, TaskType::Task, "B".into(), "".into(), Priority::Medium, "alice".into(), None, None, None, None).unwrap();
s.add_dependency(&a.id, DependencyType::BlockedBy, &b.id).unwrap();
assert!(s.add_dependency(&b.id, DependencyType::BlockedBy, &a.id).is_err());
}
#[test]
fn self_dependency_rejected() {
let s = store();
let pid = project(&s);
let a = s.create_task(&pid, TaskType::Task, "A".into(), "".into(), Priority::Medium, "alice".into(), None, None, None, None).unwrap();
assert!(s.add_dependency(&a.id, DependencyType::BlockedBy, &a.id).is_err());
}
#[test]
fn sprint_assignment_and_report() {
let s = store();
let pid = project(&s);
let sp = s.create_sprint(&pid, "S1".into(), None, None, None).unwrap();
let t = s.create_task(&pid, TaskType::Story, "feature".into(), "".into(), Priority::High, "alice".into(), None, None, Some(5.0), None).unwrap();
s.assign_to_sprint(&t.id, Some(sp.id.clone())).unwrap();
let rep = s.sprint_report(&sp.id).unwrap();
assert_eq!(rep["task_count"], 1);
assert_eq!(rep["points_total"], 5.0);
assert_eq!(rep["points_remaining"], 5.0);
}
#[test]
fn milestone_assignment() {
let s = store();
let pid = project(&s);
let ms = s.create_milestone(&pid, "v1".into(), "".into(), None).unwrap();
let t = s.create_task(&pid, TaskType::Task, "x".into(), "".into(), Priority::Low, "alice".into(), None, None, None, None).unwrap();
let t = s.assign_to_milestone(&t.id, Some(ms.id.clone())).unwrap();
assert_eq!(t.milestone_id.as_deref(), Some(ms.id.as_str()));
}
#[test]
fn time_logging_accumulates_in_progress() {
let s = store();
let pid = project(&s);
let t = s.create_task(&pid, TaskType::Task, "x".into(), "".into(), Priority::Medium, "alice".into(), None, None, None, None).unwrap();
s.log_time(&t.id, "alice", 2.0, Some("am".into())).unwrap();
s.log_time(&t.id, "alice", 3.5, None).unwrap();
assert!(s.log_time(&t.id, "alice", -1.0, None).is_err(), "negative hours rejected");
let prog = s.project_progress(&pid).unwrap();
assert_eq!(prog["hours_logged"], 5.5);
}
#[test]
fn project_progress_counts() {
let s = store();
let pid = project(&s);
for n in 0..4 {
let t = s.create_task(&pid, TaskType::Task, format!("t{n}"), "".into(), Priority::Medium, "alice".into(), None, None, Some(2.0), None).unwrap();
if n < 2 {
s.transition_task(&t.id, TaskStatus::Todo).unwrap();
s.transition_task(&t.id, TaskStatus::InProgress).unwrap();
s.transition_task(&t.id, TaskStatus::Done).unwrap();
}
}
let prog = s.project_progress(&pid).unwrap();
assert_eq!(prog["total_tasks"], 4);
assert_eq!(prog["completed_tasks"], 2);
assert_eq!(prog["completion_pct"], 50.0);
}
#[test]
fn critical_path_finds_chain() {
let s = store();
let pid = project(&s);
let a = s.create_task(&pid, TaskType::Task, "A".into(), "".into(), Priority::Medium, "alice".into(), None, None, None, None).unwrap();
let b = s.create_task(&pid, TaskType::Task, "B".into(), "".into(), Priority::Medium, "alice".into(), None, None, None, None).unwrap();
let c = s.create_task(&pid, TaskType::Task, "C".into(), "".into(), Priority::Medium, "alice".into(), None, None, None, None).unwrap();
s.add_dependency(&b.id, DependencyType::BlockedBy, &a.id).unwrap();
s.add_dependency(&c.id, DependencyType::BlockedBy, &b.id).unwrap();
let cp = s.critical_path(&pid);
assert!(cp["length"].as_u64().unwrap() >= 3, "expected chain >=3, got {cp}");
}