use std::fs;
use cucumber::{then, when};
use crate::CartularyWorld;
#[when(expr = "I create an issue with title {string} type {string} and body {string}")]
async fn create_issue_with_type(
world: &mut CartularyWorld,
title: String,
kind: String,
body: String,
) {
run_create(world, &title, Some(&kind), &body, None).await;
}
#[when(expr = "I create an issue with title {string} and body {string}")]
async fn create_issue_default_type(world: &mut CartularyWorld, title: String, body: String) {
run_create(world, &title, None, &body, None).await;
}
#[when(
expr = "I create an issue with title {string} type {string} body {string} and blocked-by {string}"
)]
async fn create_issue_with_blocked_by(
world: &mut CartularyWorld,
title: String,
kind: String,
body: String,
blocked_by: String,
) {
run_create(world, &title, Some(&kind), &body, Some(&blocked_by)).await;
}
async fn run_create(
world: &mut CartularyWorld,
title: &str,
kind: Option<&str>,
body: &str,
depends_on: Option<&str>,
) {
let dir = world.workspace.as_ref().expect("workspace not initialized");
let bin = assert_cmd::cargo_bin!("cartu");
let title_words: Vec<&str> = title.split_whitespace().collect();
let mut args = vec!["issue", "new"];
args.extend(title_words.iter().copied());
let tag;
if let Some(k) = kind {
tag = format!("flow:{k}");
args.extend(["--tag", tag.as_str()]);
}
if let Some(dep) = depends_on {
args.extend(["--blocked-by", dep]);
}
let output = std::process::Command::new(bin)
.args(&args)
.current_dir(dir.path())
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.and_then(|mut child| {
use std::io::Write;
if let Some(mut stdin) = child.stdin.take() {
let _ = stdin.write_all(body.as_bytes());
}
child.wait_with_output()
})
.expect("failed to run cartu issue new");
world.last_output = Some(output);
}
#[then(expr = "the issue file {string} exists")]
async fn issue_file_exists(world: &mut CartularyWorld, path: String) {
let dir = world.workspace.as_ref().expect("workspace not initialized");
let path = resolve_path(world, &path);
assert!(
dir.path().join(&path).exists(),
"expected issue file {path} to exist"
);
}
#[then(expr = "the issue file {string} contains {string}")]
async fn issue_file_contains(world: &mut CartularyWorld, path: String, expected: String) {
let dir = world.workspace.as_ref().expect("workspace not initialized");
let path = resolve_path(world, &path);
let expected = resolve_template(world, &expected);
let content = fs::read_to_string(dir.path().join(&path))
.unwrap_or_else(|_| panic!("issue file {path} not found"));
assert!(
content.contains(&expected),
"expected issue file {path} to contain {expected:?}\n--- content ---\n{content}"
);
}
#[then(expr = "the issue file {string} does not contain {string}")]
async fn issue_file_does_not_contain(world: &mut CartularyWorld, path: String, expected: String) {
let dir = world.workspace.as_ref().expect("workspace not initialized");
let path = resolve_path(world, &path);
let content = fs::read_to_string(dir.path().join(&path))
.unwrap_or_else(|_| panic!("issue file {path} not found"));
assert!(
!content.contains(&expected),
"expected issue file {path} NOT to contain {expected:?}\n--- content ---\n{content}"
);
}
pub fn resolve_path(world: &CartularyWorld, path: &str) -> String {
let dir = world.workspace.as_ref().expect("workspace not initialized");
let p = std::path::Path::new(path);
let comps: Vec<String> = p
.components()
.map(|c| c.as_os_str().to_string_lossy().into_owned())
.collect();
if comps.len() < 3 {
return path.to_string();
}
for i in 0..comps.len() {
let seg = &comps[i];
if !seg
.chars()
.next()
.map(|c| c.is_ascii_digit())
.unwrap_or(false)
{
continue;
}
let Some((num, slug)) = seg.split_once('-') else {
break;
};
if !num.chars().all(|c| c.is_ascii_digit()) {
break;
}
let mut parent = dir.path().to_path_buf();
for c in &comps[..i] {
parent.push(c);
}
let Ok(read) = std::fs::read_dir(&parent) else {
break;
};
for entry in read.flatten() {
let name = entry.file_name().to_string_lossy().into_owned();
if let Some((pfx, rest)) = name.split_once('-') {
if (pfx.len() == 13 || pfx.len() == 26) && rest == slug {
let mut pb = std::path::PathBuf::new();
for (k, c) in comps.iter().enumerate() {
if k == i {
pb.push(&name);
} else {
pb.push(c);
}
}
return pb.to_string_lossy().into_owned();
}
}
}
break;
}
path.to_string()
}
pub fn resolve_template(world: &CartularyWorld, s: &str) -> String {
let mut out = s.to_string();
for (key, value) in &world.captured {
out = out.replace(&format!("{{{key}}}"), value);
}
out
}