story-kit: merge 128_story_test_coverage_worktree_rs

This commit is contained in:
Dave
2026-02-24 00:26:49 +00:00
parent 4233966d5e
commit 9b2969fdba

View File

@@ -306,6 +306,7 @@ async fn run_shell_command(cmd: &str, cwd: &Path) -> Result<(), String> {
#[cfg(test)]
mod tests {
use super::*;
use crate::config::ComponentConfig;
use std::fs;
use tempfile::TempDir;
@@ -437,4 +438,229 @@ mod tests {
".story_kit/work/ must still exist in the main checkout"
);
}
#[test]
fn branch_name_format() {
assert_eq!(branch_name("42_my_story"), "feature/story-42_my_story");
assert_eq!(branch_name("1_test"), "feature/story-1_test");
}
#[test]
fn detect_base_branch_returns_branch_in_git_repo() {
let tmp = TempDir::new().unwrap();
let project_root = tmp.path().join("my-project");
fs::create_dir_all(&project_root).unwrap();
init_git_repo(&project_root);
let branch = detect_base_branch(&project_root);
assert!(!branch.is_empty());
}
#[test]
fn detect_base_branch_falls_back_to_master_for_non_git_dir() {
let tmp = TempDir::new().unwrap();
let branch = detect_base_branch(tmp.path());
assert_eq!(branch, "master");
}
#[test]
fn configure_sparse_checkout_is_noop() {
let tmp = TempDir::new().unwrap();
assert!(configure_sparse_checkout(tmp.path()).is_ok());
}
#[tokio::test]
async fn run_shell_command_succeeds_for_echo() {
let tmp = TempDir::new().unwrap();
let result = run_shell_command("echo hello", tmp.path()).await;
assert!(result.is_ok(), "Expected success: {:?}", result.err());
}
#[tokio::test]
async fn run_shell_command_fails_for_nonzero_exit() {
let tmp = TempDir::new().unwrap();
let result = run_shell_command("exit 1", tmp.path()).await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("failed"));
}
#[tokio::test]
async fn run_setup_commands_no_components_succeeds() {
let tmp = TempDir::new().unwrap();
let config = ProjectConfig {
component: vec![],
agent: vec![],
};
assert!(run_setup_commands(tmp.path(), &config).await.is_ok());
}
#[tokio::test]
async fn run_setup_commands_runs_each_command_successfully() {
let tmp = TempDir::new().unwrap();
let config = ProjectConfig {
component: vec![ComponentConfig {
name: "test".to_string(),
path: ".".to_string(),
setup: vec!["echo setup_ok".to_string()],
teardown: vec![],
}],
agent: vec![],
};
assert!(run_setup_commands(tmp.path(), &config).await.is_ok());
}
#[tokio::test]
async fn run_setup_commands_propagates_failure() {
let tmp = TempDir::new().unwrap();
let config = ProjectConfig {
component: vec![ComponentConfig {
name: "test".to_string(),
path: ".".to_string(),
setup: vec!["exit 1".to_string()],
teardown: vec![],
}],
agent: vec![],
};
let result = run_setup_commands(tmp.path(), &config).await;
assert!(result.is_err(), "Expected failure from failing setup command");
assert!(result.unwrap_err().contains("failed"));
}
#[tokio::test]
async fn run_teardown_commands_ignores_failures() {
let tmp = TempDir::new().unwrap();
let config = ProjectConfig {
component: vec![ComponentConfig {
name: "test".to_string(),
path: ".".to_string(),
setup: vec![],
teardown: vec!["exit 1".to_string()],
}],
agent: vec![],
};
// Teardown failures are best-effort — should not propagate
assert!(run_teardown_commands(tmp.path(), &config).await.is_ok());
}
#[tokio::test]
async fn create_worktree_fresh_creates_dir_and_mcp_json() {
let tmp = TempDir::new().unwrap();
let project_root = tmp.path().join("my-project");
fs::create_dir_all(&project_root).unwrap();
init_git_repo(&project_root);
let config = ProjectConfig {
component: vec![],
agent: vec![],
};
let info = create_worktree(&project_root, "42_fresh_test", &config, 3001)
.await
.unwrap();
assert!(info.path.exists());
assert!(info.path.join(".mcp.json").exists());
let mcp = fs::read_to_string(info.path.join(".mcp.json")).unwrap();
assert!(mcp.contains("3001"));
assert_eq!(info.branch, "feature/story-42_fresh_test");
}
#[tokio::test]
async fn create_worktree_reuses_existing_path_and_updates_port() {
let tmp = TempDir::new().unwrap();
let project_root = tmp.path().join("my-project");
fs::create_dir_all(&project_root).unwrap();
init_git_repo(&project_root);
let config = ProjectConfig {
component: vec![],
agent: vec![],
};
// First creation
let _info1 = create_worktree(&project_root, "43_reuse_test", &config, 3001)
.await
.unwrap();
// Second call — worktree already exists, reuse path, update port
let info2 = create_worktree(&project_root, "43_reuse_test", &config, 3002)
.await
.unwrap();
let mcp = fs::read_to_string(info2.path.join(".mcp.json")).unwrap();
assert!(mcp.contains("3002"), "MCP json should be updated to new port");
}
#[test]
fn remove_worktree_sync_cleans_up_directory() {
let tmp = TempDir::new().unwrap();
let project_root = tmp.path().join("my-project");
fs::create_dir_all(&project_root).unwrap();
init_git_repo(&project_root);
let wt_path = project_root
.join(".story_kit")
.join("worktrees")
.join("test_rm");
create_worktree_sync(&project_root, &wt_path, "feature/test-rm").unwrap();
assert!(wt_path.exists());
remove_worktree_sync(&project_root, &wt_path, "feature/test-rm").unwrap();
assert!(!wt_path.exists());
}
#[tokio::test]
async fn remove_worktree_by_story_id_returns_err_when_not_found() {
let tmp = TempDir::new().unwrap();
let config = ProjectConfig {
component: vec![],
agent: vec![],
};
let result = remove_worktree_by_story_id(tmp.path(), "99_nonexistent", &config).await;
assert!(result.is_err());
assert!(
result
.unwrap_err()
.contains("Worktree not found for story: 99_nonexistent")
);
}
#[tokio::test]
async fn remove_worktree_by_story_id_removes_existing_worktree() {
let tmp = TempDir::new().unwrap();
let project_root = tmp.path().join("my-project");
fs::create_dir_all(&project_root).unwrap();
init_git_repo(&project_root);
let config = ProjectConfig {
component: vec![],
agent: vec![],
};
create_worktree(&project_root, "88_remove_by_id", &config, 3001)
.await
.unwrap();
let result =
remove_worktree_by_story_id(&project_root, "88_remove_by_id", &config).await;
assert!(result.is_ok(), "Expected removal to succeed: {:?}", result.err());
}
#[tokio::test]
async fn remove_worktree_async_removes_directory() {
let tmp = TempDir::new().unwrap();
let project_root = tmp.path().join("my-project");
fs::create_dir_all(&project_root).unwrap();
init_git_repo(&project_root);
let config = ProjectConfig {
component: vec![],
agent: vec![],
};
let info = create_worktree(&project_root, "77_remove_async", &config, 3001)
.await
.unwrap();
let path = info.path.clone();
assert!(path.exists());
remove_worktree(&project_root, &info, &config).await.unwrap();
assert!(!path.exists());
}
}