Files
huskies/server/src/agents/pool/pipeline/advance/tests.rs
T

238 lines
6.9 KiB
Rust

//! Basic pipeline advance tests.
use super::super::super::AgentPool;
use crate::agents::CompletionReport;
use crate::io::watcher::WatcherEvent;
// ── pipeline advance tests ────────────────────────────────────────────────
#[tokio::test]
async fn pipeline_advance_coder_gates_pass_server_qa_moves_to_merge() {
use std::fs;
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
// Set up story in 2_current/ (no qa frontmatter → uses project default "server").
// Use a unique high-numbered ID to avoid collision with the agent_qa test.
let current = root.join(".huskies/work/2_current");
fs::create_dir_all(&current).unwrap();
fs::write(current.join("9908_story_server_qa.md"), "test").unwrap();
crate::db::ensure_content_store();
crate::db::write_content("9908_story_server_qa", "test");
let pool = AgentPool::new_test(3001);
pool.run_pipeline_advance(
"9908_story_server_qa",
"coder-1",
CompletionReport {
summary: "done".to_string(),
gates_passed: true,
gate_output: String::new(),
},
Some(root.to_path_buf()),
None,
false,
None,
)
.await;
// With default qa: server, story skips QA and goes straight to 4_merge/
// Lifecycle moves now update the content store, not the filesystem.
assert!(
crate::db::read_content("9908_story_server_qa").is_some(),
"story should still exist in content store after move to merge"
);
}
#[tokio::test]
async fn pipeline_advance_coder_gates_pass_agent_qa_moves_to_qa() {
use std::fs;
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
// Set up story in 2_current/ with qa: agent frontmatter.
// Use a unique high-numbered ID to avoid collision with the server_qa test.
let current = root.join(".huskies/work/2_current");
fs::create_dir_all(&current).unwrap();
fs::write(
current.join("9909_story_agent_qa.md"),
"---\nname: Test\nqa: agent\n---\ntest",
)
.unwrap();
crate::db::ensure_content_store();
crate::db::write_content(
"9909_story_agent_qa",
"---\nname: Test\nqa: agent\n---\ntest",
);
let pool = AgentPool::new_test(3001);
pool.run_pipeline_advance(
"9909_story_agent_qa",
"coder-1",
CompletionReport {
summary: "done".to_string(),
gates_passed: true,
gate_output: String::new(),
},
Some(root.to_path_buf()),
None,
false,
None,
)
.await;
// With qa: agent, story should move to 3_qa/
// Lifecycle moves now update the content store, not the filesystem.
assert!(
crate::db::read_content("9909_story_agent_qa").is_some(),
"story should still exist in content store after move to qa"
);
}
#[tokio::test]
async fn pipeline_advance_qa_gates_pass_moves_story_to_merge() {
use std::fs;
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
// Set up story in 3_qa/
let qa_dir = root.join(".huskies/work/3_qa");
fs::create_dir_all(&qa_dir).unwrap();
// qa: server so the story skips human review and goes straight to merge.
fs::write(
qa_dir.join("51_story_test.md"),
"---\nname: Test\nqa: server\n---\ntest",
)
.unwrap();
crate::db::ensure_content_store();
crate::db::write_content("51_story_test", "---\nname: Test\nqa: server\n---\ntest");
let pool = AgentPool::new_test(3001);
pool.run_pipeline_advance(
"51_story_test",
"qa",
CompletionReport {
summary: "QA done".to_string(),
gates_passed: true,
gate_output: String::new(),
},
Some(root.to_path_buf()),
None,
false,
None,
)
.await;
// Story should have moved to 4_merge/
// Lifecycle moves now update the content store, not the filesystem.
assert!(
crate::db::read_content("51_story_test").is_some(),
"story should still exist in content store after move to merge"
);
}
#[tokio::test]
async fn pipeline_advance_supervisor_does_not_advance() {
use std::fs;
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
let current = root.join(".huskies/work/2_current");
fs::create_dir_all(&current).unwrap();
fs::write(current.join("52_story_test.md"), "test").unwrap();
let pool = AgentPool::new_test(3001);
pool.run_pipeline_advance(
"52_story_test",
"supervisor",
CompletionReport {
summary: "supervised".to_string(),
gates_passed: true,
gate_output: String::new(),
},
Some(root.to_path_buf()),
None,
false,
None,
)
.await;
// Story should NOT have moved (supervisors don't advance pipeline)
assert!(
current.join("52_story_test.md").exists(),
"story should still be in 2_current/ for supervisor"
);
}
#[tokio::test]
async fn pipeline_advance_sends_agent_state_changed_to_watcher_tx() {
use std::fs;
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
// Seed story via CRDT (the only source of truth).
crate::db::ensure_content_store();
crate::db::write_item_with_content("173_story_test", "2_current", "---\nname: test\n---\n");
// Write a project.toml with a qa agent so start_agent can resolve it.
fs::create_dir_all(root.join(".huskies")).unwrap();
fs::write(
root.join(".huskies/project.toml"),
r#"
default_qa = "agent"
[[agent]]
name = "coder-1"
role = "Coder"
command = "echo"
args = ["noop"]
prompt = "test"
stage = "coder"
[[agent]]
name = "qa"
role = "QA"
command = "echo"
args = ["noop"]
prompt = "test"
stage = "qa"
"#,
)
.unwrap();
let pool = AgentPool::new_test(3001);
// Subscribe to the watcher channel BEFORE the pipeline advance.
let mut rx = pool.watcher_tx.subscribe();
pool.run_pipeline_advance(
"173_story_test",
"coder-1",
CompletionReport {
summary: "done".to_string(),
gates_passed: true,
gate_output: String::new(),
},
Some(root.to_path_buf()),
None,
false,
None,
)
.await;
// The pipeline advance should have sent AgentStateChanged events via
// the pool's watcher_tx (not a dummy channel). Collect all events.
let mut got_agent_state_changed = false;
while let Ok(evt) = rx.try_recv() {
if matches!(evt, WatcherEvent::AgentStateChanged) {
got_agent_state_changed = true;
break;
}
}
assert!(
got_agent_state_changed,
"pipeline advance should send AgentStateChanged through the real watcher_tx \
(bug 173: lozenges must update when agents are assigned during pipeline advance)"
);
}