use apm_core::{config::Config, ticket};
use std::process::Command;
use tempfile::TempDir;
fn git(dir: &std::path::Path, args: &[&str]) {
Command::new("git")
.arg("-c").arg("init.defaultBranch=main")
.args(args)
.current_dir(dir)
.env("GIT_AUTHOR_NAME", "test")
.env("GIT_AUTHOR_EMAIL", "test@test.com")
.env("GIT_COMMITTER_NAME", "test")
.env("GIT_COMMITTER_EMAIL", "test@test.com")
.status()
.unwrap();
}
fn setup() -> TempDir {
let dir = tempfile::tempdir().unwrap();
let p = dir.path();
git(p, &["init", "-q", "-b", "main"]);
git(p, &["config", "user.email", "test@test.com"]);
git(p, &["config", "user.name", "test"]);
std::fs::create_dir_all(p.join(".apm")).unwrap();
std::fs::write(
p.join(".apm/config.toml"),
r#"[project]
name = "test"
[tickets]
dir = "tickets"
[agents]
max_concurrent = 3
[workflow.prioritization]
priority_weight = 10.0
effort_weight = -2.0
risk_weight = -1.0
[[workflow.states]]
id = "new"
label = "New"
actionable = ["agent"]
[[workflow.states.transitions]]
to = "in_design"
trigger = "manual"
[[workflow.states]]
id = "in_design"
label = "In Design"
"#,
)
.unwrap();
git(p, &["add", ".apm/config.toml"]);
git(p, &["-c", "commit.gpgsign=false", "commit", "-m", "init"]);
dir
}
#[test]
fn create_returns_ticket_with_correct_fields() {
let dir = setup();
let root = dir.path();
let config = Config::load(root).unwrap();
let mut warnings = Vec::new();
let t = ticket::create(
root,
&config,
"My test ticket".to_string(),
"agent-x".to_string(),
"test".to_string(),
None,
None,
false,
vec![],
None,
None,
None,
None,
&mut warnings,
)
.unwrap();
assert_eq!(t.frontmatter.state, "new");
assert_eq!(t.frontmatter.title, "My test ticket");
assert_eq!(t.frontmatter.author.as_deref(), Some("agent-x"));
assert!(t.frontmatter.branch.as_deref().unwrap_or("").starts_with("ticket/"));
assert!(t.frontmatter.created_at.is_some());
let branch = t.frontmatter.branch.unwrap();
assert!(branch.contains("my-test-ticket"));
}
#[test]
fn create_sets_owner_to_author() {
let dir = setup();
let root = dir.path();
let config = Config::load(root).unwrap();
let mut warnings = Vec::new();
let t = ticket::create(
root,
&config,
"Owner test ticket".to_string(),
"agent-owner".to_string(),
"test".to_string(),
None,
None,
false,
vec![],
None,
None,
None,
None,
&mut warnings,
)
.unwrap();
assert_eq!(t.frontmatter.owner.as_deref(), Some("agent-owner"));
let branch = t.frontmatter.branch.as_deref().unwrap();
let slug = branch.strip_prefix("ticket/").unwrap();
let git_path = format!("tickets/{slug}.md");
let out = Command::new("git")
.args(["show", &format!("{branch}:{git_path}")])
.current_dir(root)
.output()
.unwrap();
let content = String::from_utf8(out.stdout).unwrap();
assert!(content.contains("owner = \"agent-owner\""), "owner not in persisted frontmatter: {content}");
}
#[test]
fn create_branch_exists_in_repo() {
let dir = setup();
let root = dir.path();
let config = Config::load(root).unwrap();
let mut warnings = Vec::new();
let t = ticket::create(
root,
&config,
"Branch check".to_string(),
"agent-y".to_string(),
"test".to_string(),
None,
None,
false,
vec![],
None,
None,
None,
None,
&mut warnings,
)
.unwrap();
let branch = t.frontmatter.branch.unwrap();
let out = Command::new("git")
.args(["branch", "--list", &branch])
.current_dir(root)
.output()
.unwrap();
let stdout = String::from_utf8(out.stdout).unwrap();
assert!(stdout.contains(&branch), "branch {branch} not found in repo");
}
#[test]
fn create_context_injected_into_problem() {
let dir = setup();
let root = dir.path();
let config = Config::load(root).unwrap();
let mut warnings = Vec::new();
let t = ticket::create(
root,
&config,
"Context ticket".to_string(),
"agent-z".to_string(),
"test".to_string(),
Some("the context text".to_string()),
None,
false,
vec![],
None,
None,
None,
None,
&mut warnings,
)
.unwrap();
assert!(t.body.contains("the context text"), "context not injected into body");
assert!(t.body.contains("### Problem\n\nthe context text"), "context not in Problem section");
}
#[test]
fn create_no_push_when_not_aggressive() {
let dir = setup();
let root = dir.path();
let config = Config::load(root).unwrap();
let mut warnings = Vec::new();
ticket::create(
root,
&config,
"No push ticket".to_string(),
"agent-q".to_string(),
"test".to_string(),
None,
None,
false,
vec![],
None,
None,
None,
None,
&mut warnings,
)
.unwrap();
}
fn setup_with_remote() -> (TempDir, TempDir) {
let bare = tempfile::tempdir().unwrap();
Command::new("git")
.args(["init", "--bare", "-q"])
.current_dir(bare.path())
.status()
.unwrap();
let work = setup();
git(work.path(), &["remote", "add", "origin", bare.path().to_str().unwrap()]);
(work, bare)
}
#[test]
fn create_pushes_branch_when_aggressive() {
let (work, bare) = setup_with_remote();
let root = work.path();
let config = Config::load(root).unwrap();
let mut warnings = Vec::new();
let t = ticket::create(
root,
&config,
"Aggressive push ticket".to_string(),
"agent-push".to_string(),
"test".to_string(),
None,
None,
true,
vec![],
None,
None,
None,
None,
&mut warnings,
)
.unwrap();
assert!(warnings.is_empty(), "unexpected warnings: {warnings:?}");
let branch = t.frontmatter.branch.unwrap();
let out = Command::new("git")
.args(["branch", "--list", &branch])
.current_dir(bare.path())
.output()
.unwrap();
let stdout = String::from_utf8(out.stdout).unwrap();
assert!(stdout.contains(&branch), "branch {branch} not found in origin after aggressive push");
}
#[test]
fn create_no_push_when_not_aggressive_with_remote() {
let (work, bare) = setup_with_remote();
let root = work.path();
let config = Config::load(root).unwrap();
let mut warnings = Vec::new();
let t = ticket::create(
root,
&config,
"Non-aggressive with remote".to_string(),
"agent-nopush".to_string(),
"test".to_string(),
None,
None,
false,
vec![],
None,
None,
None,
None,
&mut warnings,
)
.unwrap();
let branch = t.frontmatter.branch.unwrap();
let out = Command::new("git")
.args(["branch", "--list", &branch])
.current_dir(bare.path())
.output()
.unwrap();
let stdout = String::from_utf8(out.stdout).unwrap();
assert!(!stdout.contains(&branch), "branch {branch} should NOT be in origin when not aggressive");
}
#[test]
fn create_push_failure_is_warning() {
let dir = setup();
let root = dir.path();
let config = Config::load(root).unwrap();
let mut warnings = Vec::new();
let result = ticket::create(
root,
&config,
"Push failure ticket".to_string(),
"agent-fail".to_string(),
"test".to_string(),
None,
None,
true,
vec![],
None,
None,
None,
None,
&mut warnings,
);
assert!(result.is_ok(), "create() should succeed even when push fails");
assert!(!warnings.is_empty(), "expected a warning about push failure, got none");
}