mod test_helpers;
use std::process::Command;
use test_helpers::{jj_available, jjj_binary, run_jjj};
fn setup_test_repo() -> tempfile::TempDir {
let temp_dir = test_helpers::setup_test_repo();
run_jjj(temp_dir.path(), &["problem", "new", "Workflow Problem"]);
temp_dir
}
#[test]
fn test_workflow_start_new_solution() {
if !jj_available() {
return;
}
let temp_dir = setup_test_repo();
let dir = temp_dir.path();
let output = run_jjj(
dir,
&[
"solution",
"new",
"New Solution",
"--problem",
"Workflow Problem",
],
);
assert!(
output.status.success(),
"solution new failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let list = run_jjj(dir, &["solution", "list"]);
let stdout = String::from_utf8_lossy(&list.stdout);
assert!(stdout.contains("New Solution"));
let jj_log = Command::new("jj")
.current_dir(dir)
.args(&["log", "--no-graph", "-r", "@", "-T", "description"])
.output()
.unwrap();
if !jj_log.status.success() {
println!(
"DEBUG: jj log failed: {}",
String::from_utf8_lossy(&jj_log.stderr)
);
}
let desc = String::from_utf8_lossy(&jj_log.stdout);
println!("DEBUG: Current Change Description: '{}'", desc);
assert!(
desc.contains("New Solution"),
"Description mismatch. Got: '{}'",
desc
);
}
#[test]
fn test_workflow_start_resume_solution() {
if !jj_available() {
return;
}
let temp_dir = setup_test_repo();
let dir = temp_dir.path();
run_jjj(
dir,
&[
"solution",
"new",
"Resume Me",
"--problem",
"Workflow Problem",
],
);
let output = run_jjj(dir, &["solution", "resume", "Resume Me"]);
assert!(
output.status.success(),
"Resume failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let jj_log = Command::new("jj")
.current_dir(dir)
.args(&["log", "--no-graph", "-r", "@", "-T", "description"])
.output()
.unwrap();
let desc = String::from_utf8_lossy(&jj_log.stdout);
println!("DEBUG: Resume Description: '{}'", desc);
assert!(
desc.contains("Resume Me"),
"Resume desc mismatch. Got: '{}'",
desc
);
}
#[test]
fn test_workflow_submit_force() {
if !jj_available() {
return;
}
let temp_dir = setup_test_repo();
let dir = temp_dir.path();
let output = run_jjj(
dir,
&[
"solution",
"new",
"Solution to Submit",
"--problem",
"Workflow Problem",
],
);
assert!(
output.status.success(),
"solution new 1 failed: {}",
String::from_utf8_lossy(&output.stderr)
);
std::fs::write(dir.join("file.txt"), "content").unwrap();
let status = Command::new("jj")
.current_dir(dir)
.args(&["new", "root()", "-m", "initial"])
.status()
.unwrap();
assert!(status.success(), "Failed to create initial commit");
let status = Command::new("jj")
.current_dir(dir)
.args(&["bookmark", "create", "main"])
.status()
.unwrap();
assert!(status.success());
let log = Command::new("jj")
.current_dir(dir)
.args(&["log", "--no-graph", "-r", "all"])
.output()
.unwrap();
println!(
"DEBUG: Repo Graph before start 2:\nSTDOUT:{}\nSTDERR:{}",
String::from_utf8_lossy(&log.stdout),
String::from_utf8_lossy(&log.stderr)
);
let output = run_jjj(
dir,
&[
"solution",
"new",
"Solution to Submit 2",
"--problem",
"Workflow Problem",
],
);
assert!(
output.status.success(),
"solution new 2 failed: {}",
String::from_utf8_lossy(&output.stderr)
);
std::fs::write(dir.join("file2.txt"), "content").unwrap();
let log = Command::new("jj")
.current_dir(dir)
.args(&["log", "--no-graph", "-r", "all"])
.output()
.unwrap();
println!(
"DEBUG: Repo Graph before submit:\nSTDOUT:{}\nSTDERR:{}",
String::from_utf8_lossy(&log.stdout),
String::from_utf8_lossy(&log.stderr)
);
run_jjj(dir, &["solution", "submit", "Solution to Submit 2"]);
let output = run_jjj(dir, &["solution", "approve", "--force"]);
if !output.status.success() {
println!("Submit failed: {}", String::from_utf8_lossy(&output.stderr));
}
assert!(output.status.success());
let list = run_jjj(dir, &["solution", "list", "--status", "approved"]);
let stdout = String::from_utf8_lossy(&list.stdout);
assert!(stdout.contains("Solution to Submit 2") || stdout.contains("approved"));
}
#[test]
fn test_solution_status_workflow() {
if !jj_available() {
return;
}
let temp_dir = setup_test_repo();
let dir = temp_dir.path();
run_jjj(
dir,
&[
"solution",
"new",
"Test Solution",
"--problem",
"Workflow Problem",
],
);
let show = run_jjj(dir, &["solution", "show", "Test Solution"]);
let stdout = String::from_utf8_lossy(&show.stdout);
assert!(
stdout.contains("Proposed") || stdout.contains("proposed"),
"Expected proposed status after solution new. Got: {}",
stdout
);
run_jjj(dir, &["solution", "submit", "Test Solution"]);
let output = run_jjj(dir, &["solution", "approve", "Test Solution"]);
assert!(output.status.success());
let show = run_jjj(dir, &["solution", "show", "Test Solution"]);
let stdout = String::from_utf8_lossy(&show.stdout);
assert!(stdout.contains("Approved") || stdout.contains("approved"));
}
#[test]
fn test_critique_blocks_acceptance() {
if !jj_available() {
return;
}
let temp_dir = setup_test_repo();
let dir = temp_dir.path();
run_jjj(
dir,
&[
"solution",
"new",
"Test Solution",
"--problem",
"Workflow Problem",
],
);
run_jjj(dir, &["solution", "submit", "Test Solution"]);
run_jjj(
dir,
&[
"critique",
"new",
"Test Solution",
"Major flaw in approach",
"--severity",
"high",
],
);
let output = run_jjj(dir, &["solution", "approve", "Test Solution"]);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let combined = format!("{}{}", stdout, stderr);
assert!(
!output.status.success() && (combined.contains("critique") || combined.contains("open")),
"Expected failure due to open critique. Got: {}",
combined
);
}
#[test]
fn test_submit_blocked_by_critiques() {
if !jj_available() {
return;
}
let temp_dir = setup_test_repo();
let dir = temp_dir.path();
Command::new("jj")
.current_dir(dir)
.args(&["new", "root()", "-m", "initial"])
.status()
.unwrap();
Command::new("jj")
.current_dir(dir)
.args(&["bookmark", "create", "main"])
.status()
.unwrap();
run_jjj(
dir,
&[
"solution",
"new",
"Token refresh",
"--problem",
"Workflow Problem",
],
);
run_jjj(dir, &["solution", "submit", "Token refresh"]);
run_jjj(
dir,
&[
"critique",
"new",
"Token refresh",
"Not thread safe",
"--severity",
"high",
],
);
let output = run_jjj(dir, &["solution", "approve"]);
assert!(
!output.status.success(),
"Expected submit to fail with open critiques"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("critique") || stderr.contains("Cannot"),
"Expected critique blocking message in stderr: {}",
stderr
);
}
#[test]
fn test_no_stale_working_copy_after_metadata_writes() {
if !jj_available() {
return;
}
let temp_dir = setup_test_repo();
let dir = temp_dir.path();
let assert_not_stale = |label: &str| {
let out = Command::new("jj")
.current_dir(dir)
.args(&["status"])
.output()
.expect("Failed to run jj status");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
!stderr.contains("stale"),
"{}: jj status reported stale working copy:\n{}",
label,
stderr
);
assert!(
out.status.success(),
"{}: jj status exited non-zero:\n{}",
label,
stderr
);
};
assert_not_stale("after setup");
run_jjj(
dir,
&[
"solution",
"new",
"Stale Regression Solution",
"--problem",
"Workflow Problem",
],
);
assert_not_stale("after solution new");
run_jjj(
dir,
&[
"critique",
"new",
"Stale Regression Solution",
"Needs more tests",
"--severity",
"low",
],
);
assert_not_stale("after critique new");
run_jjj(dir, &["solution", "submit", "Stale Regression Solution"]);
assert_not_stale("after solution review");
run_jjj(
dir,
&[
"solution",
"approve",
"Stale Regression Solution",
"--force",
],
);
assert_not_stale("after submit");
}
#[test]
fn test_submit_blocked_by_awaiting_review() {
if !jj_available() {
return;
}
let temp_dir = setup_test_repo();
let dir = temp_dir.path();
run_jjj(dir, &["init"]);
run_jjj(dir, &["problem", "new", "Test problem"]);
run_jjj(
dir,
&[
"solution",
"new",
"Test solution",
"--problem",
"Test problem",
"--reviewer",
"bob",
],
);
run_jjj(dir, &["solution", "submit", "Test solution"]);
let output = Command::new(jjj_binary())
.args(["solution", "approve"])
.current_dir(dir)
.output()
.expect("Failed to execute jjj solution approve");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(!output.status.success(), "Approve should have failed");
assert!(
stderr.contains("open critique") || stderr.contains("Awaiting review"),
"Expected open critique error, got: {}",
stderr
);
}
#[test]
fn test_events_logged_on_status_changes() {
if !jj_available() {
return;
}
let temp_dir = setup_test_repo();
let dir = temp_dir.path();
run_jjj(
dir,
&[
"solution",
"new",
"Test Solution",
"--problem",
"Workflow Problem",
],
);
run_jjj(dir, &["solution", "submit", "Test Solution"]);
run_jjj(dir, &["solution", "approve", "Test Solution", "--force"]);
let output = run_jjj(dir, &["events", "--json"]);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("solution_created"),
"Missing solution_created event"
);
assert!(
stdout.contains("solution_approved"),
"Missing solution_approved event"
);
}