#[cfg(test)]
mod tests {
use serial_test::serial;
use std::path::Path;
use std::process::{Command, Stdio};
use tempfile::TempDir;
fn kasl_cmd(dir: &Path) -> Command {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_kasl"));
cmd.env("HOME", dir).env("LOCALAPPDATA", dir).stdin(Stdio::null());
cmd
}
#[serial]
#[test]
fn task_add_with_a_name_needs_no_terminal() {
let dir = TempDir::new().unwrap();
let out = kasl_cmd(dir.path())
.args(["task", "add", "--name", "Scripted task", "--completeness", "30"])
.output()
.unwrap();
assert!(out.status.success(), "task add failed: {}", String::from_utf8_lossy(&out.stderr));
let listed = kasl_cmd(dir.path()).args(["task", "list"]).output().unwrap();
let listed = String::from_utf8_lossy(&listed.stdout);
assert!(listed.contains("Scripted task"), "task missing from list:\n{listed}");
assert!(listed.contains("30%"), "completeness not applied:\n{listed}");
}
#[serial]
#[test]
fn task_add_from_a_template_needs_no_terminal() {
let dir = TempDir::new().unwrap();
kasl_cmd(dir.path())
.args([
"template",
"add",
"--name",
"review",
"--task-name",
"Code review",
"--comment",
"daily",
"--completeness",
"80",
])
.output()
.unwrap();
let out = kasl_cmd(dir.path()).args(["task", "add", "--template", "review"]).output().unwrap();
assert!(
out.status.success(),
"task add --template failed: {}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let listed = kasl_cmd(dir.path()).args(["task", "list"]).output().unwrap();
let listed = String::from_utf8_lossy(&listed.stdout);
assert!(
listed.contains("Code review"),
"template task missing from list:
{listed}"
);
assert!(
listed.contains("80%"),
"template completeness not applied:
{listed}"
);
}
#[serial]
#[test]
fn flags_win_over_the_template_they_are_passed_with() {
let dir = TempDir::new().unwrap();
kasl_cmd(dir.path())
.args([
"template",
"add",
"--name",
"review",
"--task-name",
"Code review",
"--comment",
"daily",
"--completeness",
"80",
])
.output()
.unwrap();
let out = kasl_cmd(dir.path())
.args(["task", "add", "--template", "review", "--name", "Review PR 318", "--completeness", "40"])
.output()
.unwrap();
assert!(out.status.success(), "task add failed: {}", String::from_utf8_lossy(&out.stderr));
let listed = kasl_cmd(dir.path()).args(["task", "list"]).output().unwrap();
let listed = String::from_utf8_lossy(&listed.stdout);
assert!(
listed.contains("Review PR 318"),
"--name did not win over the template:
{listed}"
);
assert!(
!listed.contains("Code review"),
"the template name was used despite --name:
{listed}"
);
assert!(
listed.contains("40%"),
"--completeness did not win over the template:
{listed}"
);
assert!(
listed.contains("daily"),
"template comment was lost:
{listed}"
);
}
#[serial]
#[test]
fn template_add_without_a_name_fails_instead_of_hanging() {
let dir = TempDir::new().unwrap();
let out = kasl_cmd(dir.path()).args(["template", "add"]).output().unwrap();
assert!(!out.status.success(), "expected a refusal without a name");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("--name"),
"error should name the missing flag:
{stderr}"
);
assert!(
!stderr.contains("not a terminal"),
"the raw dialoguer error leaked through:
{stderr}"
);
}
#[serial]
#[test]
fn task_add_without_a_name_fails_instead_of_hanging() {
let dir = TempDir::new().unwrap();
let out = kasl_cmd(dir.path()).args(["task", "add"]).output().unwrap();
assert!(!out.status.success(), "expected a refusal without a name");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(stderr.contains("--name"), "error should name the missing flag:\n{stderr}");
}
#[serial]
#[test]
fn destructive_commands_refuse_without_yes() {
let dir = TempDir::new().unwrap();
kasl_cmd(dir.path()).args(["task", "add", "--name", "Doomed"]).output().unwrap();
let out = kasl_cmd(dir.path()).args(["task", "remove", "1"]).output().unwrap();
assert!(!out.status.success(), "expected a refusal without --yes");
let out = kasl_cmd(dir.path()).args(["task", "remove", "1", "--yes"]).output().unwrap();
assert!(out.status.success(), "task remove --yes failed: {}", String::from_utf8_lossy(&out.stderr));
}
#[serial]
#[test]
fn server_connect_refuses_before_touching_the_network() {
let dir = TempDir::new().unwrap();
let out = kasl_cmd(dir.path())
.args(["server", "connect", "--url", "http://127.0.0.1:1"])
.output()
.unwrap();
assert!(!out.status.success(), "expected a refusal with no terminal");
let combined = format!("{}{}", String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr));
assert!(combined.contains("terminal"), "the refusal should name the cause:\n{combined}");
assert!(
!combined.contains("cannot reach"),
"the server must not be contacted by a run that cannot finish:\n{combined}"
);
}
#[serial]
#[test]
fn server_status_and_disconnect_work_unattended() {
let dir = TempDir::new().unwrap();
for args in [vec!["server", "status"], vec!["server", "disconnect"]] {
let out = kasl_cmd(dir.path()).args(&args).output().unwrap();
assert!(
out.status.success(),
"`kasl {}` failed unattended: {}",
args.join(" "),
String::from_utf8_lossy(&out.stderr)
);
}
}
#[serial]
#[test]
fn read_only_commands_work_unattended() {
let dir = TempDir::new().unwrap();
for args in [
vec!["pauses", "list"],
vec!["task", "list"],
vec!["tag", "list"],
vec!["report"],
vec!["sum"],
vec!["completions", "bash"],
] {
let out = kasl_cmd(dir.path()).args(&args).output().unwrap();
assert!(
out.status.success(),
"`kasl {}` failed unattended: {}",
args.join(" "),
String::from_utf8_lossy(&out.stderr)
);
}
}
}