use super::*;
use crate::reads::test_support::{git_store, task};
use crate::taskfile::read_task;
#[test]
fn update_builds_extras_priority_and_tags() {
let d = tempdir().unwrap();
let dir = d.path();
write(dir, "bl-1", TASK);
let mut f = flags();
f.positionals = vec!["bl-1".into(), "state=doing".into()];
f.priority = Some(5);
f.tags = vec!["urgent".into()];
let (base, before) = base_change(Verb::Update, dir, &f, 9).unwrap();
assert_eq!(before.unwrap().title, "A task");
base.stage(dir).unwrap();
let t = read_task(dir, "bl-1").unwrap();
assert_eq!(t.extra.get("state").and_then(toml::Value::as_str), Some("doing"));
assert_eq!(t.priority, Some(5));
assert_eq!(t.tags, ["urgent"]);
assert_eq!(t.updated, 9);
}
#[test]
fn update_requires_a_task_id() {
let err = base_change(Verb::Update, tempdir().unwrap().path(), &flags(), 0).err().unwrap();
assert!(err.to_string().contains("needs a task id"));
}
#[test]
fn update_refuses_reserved_keys_in_the_extra_seam() {
let d = tempdir().unwrap();
write(d.path(), "bl-1", TASK);
for kv in ["id=bl-feed", "body=shadow", "created=999", "updated=999", "root_commit=forged", "id="] {
let mut f = flags();
f.positionals = vec!["bl-1".into(), kv.into()];
let err = base_change(Verb::Update, d.path(), &f, 0).err().unwrap();
assert!(err.to_string().contains("reserved"), "{kv}: {err}");
}
}
#[test]
fn update_rejects_a_non_key_value_positional() {
let d = tempdir().unwrap();
write(d.path(), "bl-1", TASK);
let mut f = flags();
f.positionals = vec!["bl-1".into(), "notpair".into()];
let err = base_change(Verb::Update, d.path(), &f, 0).err().unwrap();
assert!(err.to_string().contains("not key=value"));
}
#[test]
fn update_rejects_only_blocks_as_create_only() {
let mut f = flags();
f.positionals = vec!["bl-1".into()];
f.blocks = vec!["bl-2:close".into()];
let err = base_change(Verb::Update, tempdir().unwrap().path(), &f, 0).err().unwrap();
assert!(err.to_string().contains("create-only"));
}
#[test]
fn update_overwrites_title_body_parent_and_clears_scalars() {
let d = tempdir().unwrap();
let dir = d.path();
write(dir, "bl-1", "+++\ntitle = \"Old\"\ncreated = 0\nupdated = 0\nparent = \"bl-p\"\npriority = 5\n+++\nold body\n");
let mut f = flags();
f.positionals = vec!["bl-1".into()];
f.title = Some("New".into());
f.body = Some("new body\n".into());
f.no_parent = true;
f.no_priority = true;
let (base, _) = base_change(Verb::Update, dir, &f, 7).unwrap();
base.stage(dir).unwrap();
let t = read_task(dir, "bl-1").unwrap();
assert_eq!(t.title, "New");
assert_eq!(t.body, "new body\n");
assert!(t.parent.is_none());
assert!(t.priority.is_none());
}
#[test]
fn update_sets_parent_and_removes_a_tag_and_extra() {
let d = tempdir().unwrap();
let dir = d.path();
write(dir, "bl-1", "+++\ntitle = \"A\"\ncreated = 0\nupdated = 0\ntags = [\"a\", \"b\"]\nstate = \"doing\"\n+++\n");
let mut f = flags();
f.positionals = vec!["bl-1".into(), "state=".into()]; f.parent = Some("bl-new".into());
f.no_tags = vec!["a".into()];
let (base, _) = base_change(Verb::Update, dir, &f, 0).unwrap();
base.stage(dir).unwrap();
let t = read_task(dir, "bl-1").unwrap();
assert_eq!(t.parent.as_deref(), Some("bl-new"));
assert_eq!(t.tags, ["b"]);
assert!(!t.extra.contains_key("state"));
}
#[test]
fn update_rejects_contradictory_set_and_clear() {
for mut f in [
{
let mut f = flags();
f.parent = Some("bl-2".into());
f.no_parent = true;
f
},
{
let mut f = flags();
f.priority = Some(1);
f.no_priority = true;
f
},
] {
f.positionals = vec!["bl-1".into()];
let err = base_change(Verb::Update, tempdir().unwrap().path(), &f, 0).err().unwrap();
assert!(err.to_string().contains("conflict"));
}
}
#[test]
fn update_rejects_edit_combined_with_field_flags() {
let d = tempdir().unwrap();
write(d.path(), "bl-1", TASK);
let mut f = flags();
f.positionals = vec!["bl-1".into()];
f.edit = true;
f.title = Some("New".into());
let err = base_change(Verb::Update, d.path(), &f, 0).err().unwrap();
assert!(err.to_string().contains("mutually exclusive"));
}
#[test]
fn update_rejects_edit_combined_with_key_value_extras() {
let d = tempdir().unwrap();
write(d.path(), "bl-1", TASK);
let mut f = flags();
f.positionals = vec!["bl-1".into(), "state=doing".into()];
f.edit = true;
let err = base_change(Verb::Update, d.path(), &f, 0).err().unwrap();
assert!(err.to_string().contains("the buffer is the payload"));
}
#[test]
fn update_edit_refuses_a_non_tty_so_agents_keep_field_flags() {
let d = tempdir().unwrap();
write(d.path(), "bl-1", TASK);
let mut f = flags();
f.positionals = vec!["bl-1".into()];
f.edit = true;
let err = base_change(Verb::Update, d.path(), &f, 0).err().unwrap();
assert!(err.to_string().contains("not a tty"));
}
#[test]
fn create_rejects_the_update_only_edit_flag() {
let mut f = flags();
f.positionals = vec!["a title".into()];
f.edit = true;
let err = base_change(Verb::Create, tempdir().unwrap().path(), &f, 0).err().unwrap();
assert!(err.to_string().contains("--edit is update-only"));
}
#[test]
fn update_adds_and_drops_its_own_blockers() {
let d = tempdir().unwrap();
let dir = d.path();
let before = Task {
title: "A".into(),
blockers: vec![Blocker { id: "bl-old".into(), on: On::Claim }],
..Task::default()
};
write_task(dir, "bl-1", &before).unwrap();
write(dir, "bl-new", TASK); let mut f = flags();
f.positionals = vec!["bl-1".into()];
f.needs = vec!["bl-new:close".into()]; f.no_needs = vec!["bl-old".into(), "bl-z:claim".into()];
let (base, _) = base_change(Verb::Update, dir, &f, 3).unwrap();
base.stage(dir).unwrap();
let t = read_task(dir, "bl-1").unwrap();
assert_eq!(t.blockers, vec![Blocker { id: "bl-new".into(), on: On::Close }]);
assert_eq!(t.updated, 3);
}
#[test]
fn update_refuses_a_non_live_needs_target() {
let s = git_store();
s.create("bl-1", &task("A", 1), 1);
s.create("bl-dead", &task("D", 2), 2).retire("bl-dead", "close", 3);
for (target, fate) in [("bl-nope", "'bl-nope' is not a known id"), ("bl-dead", "'bl-dead' is already closed")] {
let mut f = flags();
f.positionals = vec!["bl-1".into()];
f.needs = vec![target.into()];
let err = base_change(Verb::Update, s.dir(), &f, 0).err().unwrap();
assert!(err.to_string().contains(fate), "{target}: {err}");
}
}
#[test]
fn update_refuses_a_parent_that_is_not_an_id() {
let d = tempdir().unwrap();
write(d.path(), "bl-1", TASK);
let mut f = flags();
f.positionals = vec!["bl-1".into()];
f.parent = Some("--needs".into());
let err = base_change(Verb::Update, d.path(), &f, 0).err().unwrap();
assert!(err.to_string().contains("update: parent '--needs' is not a task id"), "{err}");
}
#[test]
fn update_rejects_subtask_of() {
let d = tempdir().unwrap();
write(d.path(), "bl-1", TASK);
let mut f = flags();
f.positionals = vec!["bl-1".into()];
f.subtask_of = Some("bl-e".into());
let err = base_change(Verb::Update, d.path(), &f, 0).err().unwrap();
assert!(err.to_string().contains("create-only"));
}