story-kit: merge 108_story_test_coverage_http_agents_rs_to_70
This commit is contained in:
@@ -379,4 +379,307 @@ mod tests {
|
|||||||
let result = api.list_agents().await.unwrap().0;
|
let result = api.list_agents().await.unwrap().0;
|
||||||
assert!(result.iter().any(|a| a.story_id == "42_story_whatever"));
|
assert!(result.iter().any(|a| a.story_id == "42_story_whatever"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_project_toml(root: &path::Path, content: &str) {
|
||||||
|
let sk_dir = root.join(".story_kit");
|
||||||
|
std::fs::create_dir_all(&sk_dir).unwrap();
|
||||||
|
std::fs::write(sk_dir.join("project.toml"), content).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- get_agent_config tests ---
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_agent_config_returns_default_when_no_toml() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api.get_agent_config().await.unwrap().0;
|
||||||
|
// Default config has one agent named "default"
|
||||||
|
assert_eq!(result.len(), 1);
|
||||||
|
assert_eq!(result[0].name, "default");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_agent_config_returns_configured_agents() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
make_project_toml(
|
||||||
|
tmp.path(),
|
||||||
|
r#"
|
||||||
|
[[agent]]
|
||||||
|
name = "coder-1"
|
||||||
|
role = "Full-stack engineer"
|
||||||
|
model = "sonnet"
|
||||||
|
max_turns = 30
|
||||||
|
max_budget_usd = 5.0
|
||||||
|
|
||||||
|
[[agent]]
|
||||||
|
name = "qa"
|
||||||
|
role = "QA reviewer"
|
||||||
|
model = "haiku"
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api.get_agent_config().await.unwrap().0;
|
||||||
|
assert_eq!(result.len(), 2);
|
||||||
|
assert_eq!(result[0].name, "coder-1");
|
||||||
|
assert_eq!(result[0].role, "Full-stack engineer");
|
||||||
|
assert_eq!(result[0].model, Some("sonnet".to_string()));
|
||||||
|
assert_eq!(result[0].max_turns, Some(30));
|
||||||
|
assert_eq!(result[0].max_budget_usd, Some(5.0));
|
||||||
|
assert_eq!(result[1].name, "qa");
|
||||||
|
assert_eq!(result[1].model, Some("haiku".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_agent_config_returns_error_when_no_project_root() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
*ctx.state.project_root.lock().unwrap() = None;
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api.get_agent_config().await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- reload_config tests ---
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn reload_config_returns_default_when_no_toml() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api.reload_config().await.unwrap().0;
|
||||||
|
assert_eq!(result.len(), 1);
|
||||||
|
assert_eq!(result[0].name, "default");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn reload_config_returns_configured_agents() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
make_project_toml(
|
||||||
|
tmp.path(),
|
||||||
|
r#"
|
||||||
|
[[agent]]
|
||||||
|
name = "supervisor"
|
||||||
|
role = "Coordinator"
|
||||||
|
model = "opus"
|
||||||
|
allowed_tools = ["Read", "Bash"]
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api.reload_config().await.unwrap().0;
|
||||||
|
assert_eq!(result.len(), 1);
|
||||||
|
assert_eq!(result[0].name, "supervisor");
|
||||||
|
assert_eq!(result[0].role, "Coordinator");
|
||||||
|
assert_eq!(result[0].model, Some("opus".to_string()));
|
||||||
|
assert_eq!(
|
||||||
|
result[0].allowed_tools,
|
||||||
|
Some(vec!["Read".to_string(), "Bash".to_string()])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn reload_config_returns_error_when_no_project_root() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
*ctx.state.project_root.lock().unwrap() = None;
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api.reload_config().await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- list_worktrees tests ---
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn list_worktrees_returns_empty_when_no_worktree_dir() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api.list_worktrees().await.unwrap().0;
|
||||||
|
assert!(result.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn list_worktrees_returns_entries_from_dir() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let worktrees_dir = tmp.path().join(".story_kit").join("worktrees");
|
||||||
|
std::fs::create_dir_all(worktrees_dir.join("42_story_foo")).unwrap();
|
||||||
|
std::fs::create_dir_all(worktrees_dir.join("43_story_bar")).unwrap();
|
||||||
|
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let mut result = api.list_worktrees().await.unwrap().0;
|
||||||
|
result.sort_by(|a, b| a.story_id.cmp(&b.story_id));
|
||||||
|
|
||||||
|
assert_eq!(result.len(), 2);
|
||||||
|
assert_eq!(result[0].story_id, "42_story_foo");
|
||||||
|
assert_eq!(result[1].story_id, "43_story_bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn list_worktrees_returns_error_when_no_project_root() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
*ctx.state.project_root.lock().unwrap() = None;
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api.list_worktrees().await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- stop_agent tests ---
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn stop_agent_returns_error_when_no_project_root() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
*ctx.state.project_root.lock().unwrap() = None;
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api
|
||||||
|
.stop_agent(Json(StopAgentPayload {
|
||||||
|
story_id: "42_story_foo".to_string(),
|
||||||
|
agent_name: "coder-1".to_string(),
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn stop_agent_returns_error_when_agent_not_found() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api
|
||||||
|
.stop_agent(Json(StopAgentPayload {
|
||||||
|
story_id: "nonexistent_story".to_string(),
|
||||||
|
agent_name: "coder-1".to_string(),
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn stop_agent_succeeds_with_running_agent() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
ctx.agents
|
||||||
|
.inject_test_agent("42_story_foo", "coder-1", AgentStatus::Running);
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api
|
||||||
|
.stop_agent(Json(StopAgentPayload {
|
||||||
|
story_id: "42_story_foo".to_string(),
|
||||||
|
agent_name: "coder-1".to_string(),
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
assert!(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- start_agent error path ---
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn start_agent_returns_error_when_no_project_root() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
*ctx.state.project_root.lock().unwrap() = None;
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api
|
||||||
|
.start_agent(Json(StartAgentPayload {
|
||||||
|
story_id: "42_story_foo".to_string(),
|
||||||
|
agent_name: None,
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- create_worktree error path ---
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn create_worktree_returns_error_when_no_project_root() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
*ctx.state.project_root.lock().unwrap() = None;
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api
|
||||||
|
.create_worktree(Json(CreateWorktreePayload {
|
||||||
|
story_id: "42_story_foo".to_string(),
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn create_worktree_returns_error_when_not_a_git_repo() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
// project_root is set but has no git repo — git worktree add will fail
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api
|
||||||
|
.create_worktree(Json(CreateWorktreePayload {
|
||||||
|
story_id: "42_story_foo".to_string(),
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- remove_worktree error paths ---
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn remove_worktree_returns_error_when_no_project_root() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
*ctx.state.project_root.lock().unwrap() = None;
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api
|
||||||
|
.remove_worktree(Path("42_story_foo".to_string()))
|
||||||
|
.await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn remove_worktree_returns_error_when_worktree_not_found() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
// project_root is set but no worktree exists for this story_id
|
||||||
|
let ctx = AppContext::new_test(tmp.path().to_path_buf());
|
||||||
|
let api = AgentsApi {
|
||||||
|
ctx: Arc::new(ctx),
|
||||||
|
};
|
||||||
|
let result = api
|
||||||
|
.remove_worktree(Path("nonexistent_story".to_string()))
|
||||||
|
.await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user