use super::{command_workflows, discover_workflow_sources, resolve_start_command};
use pretty_assertions::assert_eq;
fn workspace(files: &[&str]) -> tempfile::TempDir {
let root = tempfile::tempdir().unwrap();
for file in files {
let path = root.path().join(file);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, "x").unwrap();
}
root
}
#[test]
fn discover_finds_folder_and_flat_entries() {
let root = tempfile::tempdir().unwrap();
let workflows = root.path().join(".rho").join("workflows");
std::fs::create_dir_all(workflows.join("review")).unwrap();
std::fs::write(workflows.join("review").join("workflow.star"), "x").unwrap();
std::fs::write(workflows.join("solo.star"), "y").unwrap();
std::fs::write(workflows.join("review").join("helper.py"), "z").unwrap();
std::fs::write(workflows.join("review").join("common.star"), "skip").unwrap();
let found = discover_workflow_sources(root.path());
let paths = found
.iter()
.map(|item| item.relative_path.as_str())
.collect::<Vec<_>>();
assert_eq!(
paths,
vec![
".rho/workflows/review/workflow.star",
".rho/workflows/solo.star",
]
);
}
#[test]
fn missing_workflows_dir_is_empty() {
let root = tempfile::tempdir().unwrap();
assert!(discover_workflow_sources(root.path()).is_empty());
}
#[cfg(unix)]
#[test]
fn discover_skips_nested_workflow_star_symlinks() {
use std::os::unix::fs::symlink;
let root = tempfile::tempdir().unwrap();
let workflows = root.path().join(".rho").join("workflows");
std::fs::create_dir_all(workflows.join("linked")).unwrap();
std::fs::write(workflows.join("target.star"), "x").unwrap();
symlink(
workflows.join("target.star"),
workflows.join("linked").join("workflow.star"),
)
.unwrap();
let found = discover_workflow_sources(root.path());
assert_eq!(
found
.iter()
.map(|workflow| workflow.relative_path.as_str())
.collect::<Vec<_>>(),
vec![".rho/workflows/target.star"]
);
}
#[test]
fn start_command_resolves_one_workflow_and_its_inputs() {
let root = workspace(&[
".rho/workflows/review/workflow.star",
".rho/workflows/twin.star",
".rho/workflows/twin/workflow.star",
".rho/workflows/Lint.star",
".rho/workflows/Case.star",
".rho/workflows/case/workflow.star",
]);
let resolve = |name: &str, args: &str| {
resolve_start_command(root.path(), name, args).map(|resolved| {
resolved.map(|command| (command.workflow.relative_path, command.inputs))
})
};
assert_eq!(resolve("prompt:review", ""), None);
assert_eq!(
resolve("workflow:review", r#" {"target": "src", "depth": 2} "#),
Some(Ok((
".rho/workflows/review/workflow.star".to_owned(),
[
("depth".to_owned(), serde_json::json!(2)),
("target".to_owned(), serde_json::json!("src")),
]
.into(),
)))
);
assert_eq!(
resolve("workflow:review", "").map(|resolved| resolved.map(|(_, inputs)| inputs)),
Some(Ok(Default::default()))
);
for name in ["workflow:lint", "Workflow:LINT"] {
assert_eq!(
resolve(name, "").map(|resolved| resolved.map(|(path, _)| path)),
Some(Ok(".rho/workflows/Lint.star".to_owned())),
"/{name} ignores ASCII case"
);
}
for (name, args) in [
("workflow:missing", ""),
("workflow:twin", ""),
("workflow:case", ""),
("workflow:review", "src"),
("workflow:review", "[1]"),
] {
assert!(
matches!(resolve(name, args), Some(Err(_))),
"/{name} {args} should be rejected"
);
}
}
#[test]
fn command_workflows_lists_each_name_once() {
let root = workspace(&[
".rho/workflows/twin.star",
".rho/workflows/twin/workflow.star",
".rho/workflows/solo.star",
".rho/workflows/Case.star",
".rho/workflows/case/workflow.star",
]);
let names = command_workflows(root.path())
.iter()
.filter_map(super::DiscoveredWorkflow::command_name)
.collect::<Vec<_>>();
assert_eq!(
names,
vec!["workflow:Case", "workflow:solo", "workflow:twin"]
);
}