1mod agent;
7mod error;
8mod import;
9mod model;
10mod pr;
11pub mod scheduler;
12mod store;
13mod transition;
14
15pub use agent::{
16 AgentSessionEntry, AgentTemplate, AgentsConfig, PROMPT_FILE_PLACEHOLDER, Provenance,
17 ResolvedAgent, SESSION_PLACEHOLDER, TASK_ID_PLACEHOLDER, VIEWER_BASE_PLACEHOLDER,
18 VIEWER_BRANCH_PLACEHOLDER, VIEWER_PATH_PLACEHOLDER, ViewerTemplate, parse_sessions_json,
19};
20pub use error::{Error, Result};
21pub use import::{GithubIssue, already_imported, issue_new_task, issue_task_body};
22pub use model::{
23 Dep, DepKind, DepRef, Event, NextAction, Priority, Project, ReviewAction, ReviewMedium,
24 RunningRow, Session, SessionOutcome, Task, TaskState,
25};
26pub use pr::{PrPlan, PrRef, format_review_feedback, plan_pr};
27pub use scheduler::{Candidate, ScoreBreakdown, StateCounts};
28pub use store::{NewTask, Store, TaskEdit};
29pub use transition::{Action, Triage};
30
31#[cfg(test)]
32mod tests {
33 use super::*;
34
35 fn store() -> Store {
36 Store::open_in_memory().unwrap()
37 }
38
39 fn new_task(project_id: i64, title: &str, state: TaskState) -> NewTask {
40 NewTask {
41 project_id,
42 title: title.into(),
43 body: String::new(),
44 priority: Priority::P2,
45 state,
46 agent: None,
47 human: false,
48 }
49 }
50
51 #[test]
52 fn project_crud_and_weight_bounds() {
53 let mut s = store();
54 let p = s.create_project("voro", "/tmp/voro").unwrap();
55 assert_eq!(p.weight, 3);
56 s.set_weight(p.id, 5).unwrap();
57 assert_eq!(s.project(p.id).unwrap().weight, 5);
58 assert!(s.set_weight(p.id, 6).is_err());
59 assert!(s.set_weight(999, 2).is_err());
60 }
61
62 #[test]
63 fn task_create_defaults_and_event() {
64 let mut s = store();
65 let p = s.create_project("voro", "/tmp/voro").unwrap();
66 let t = s
67 .create_task(new_task(p.id, "First", TaskState::Ready))
68 .unwrap();
69 assert_eq!(t.state, TaskState::Ready);
70 assert!(t.question.is_none());
71 assert!(t.closed_at.is_none());
72 assert!(!t.state_since.is_empty());
73 let events = s.events_for(t.id).unwrap();
74 assert_eq!(events.len(), 1);
75 assert_eq!(events[0].kind, "created");
76 assert_eq!(events[0].detail.as_deref(), Some("ready"));
77 }
78
79 #[test]
80 fn task_cannot_be_created_in_active_or_closed_states() {
81 let mut s = store();
82 let p = s.create_project("voro", "/tmp/voro").unwrap();
83 for state in [
84 TaskState::Running,
85 TaskState::NeedsInput,
86 TaskState::Review,
87 TaskState::Done,
88 TaskState::Rejected,
89 ] {
90 assert!(s.create_task(new_task(p.id, "bad", state)).is_err());
91 }
92 }
93
94 #[test]
95 fn dep_rejects_self_reference() {
96 let mut s = store();
97 let p = s.create_project("voro", "/tmp/voro").unwrap();
98 let t = s
99 .create_task(new_task(p.id, "t", TaskState::Parked))
100 .unwrap();
101 assert!(s.add_dep(t.id, t.id, DepKind::Blocks).is_err());
102 }
103
104 #[test]
105 fn dep_add_and_remove() {
106 let mut s = store();
107 let p = s.create_project("voro", "/tmp/voro").unwrap();
108 let a = s
109 .create_task(new_task(p.id, "a", TaskState::Ready))
110 .unwrap();
111 let b = s
112 .create_task(new_task(p.id, "b", TaskState::Parked))
113 .unwrap();
114 s.add_dep(b.id, a.id, DepKind::Blocks).unwrap();
115 let deps = s.deps_of(b.id).unwrap();
116 assert_eq!(deps.len(), 1);
117 assert_eq!(deps[0].depends_on, a.id);
118 assert_eq!(deps[0].kind, DepKind::Blocks);
119 s.remove_dep(b.id, a.id).unwrap();
120 assert!(s.deps_of(b.id).unwrap().is_empty());
121 }
122}