mod common;
use common::{stderr, Sandbox};
fn add_minimal(s: &Sandbox, statement: &str, kind: &str, accepts: &[&str]) -> std::process::Output {
let mut args = vec![
"add",
"--title",
"A reasonable title",
"--statement",
statement,
"--rationale",
"Verifies a validator rule end-to-end.",
"--kind",
kind,
"--priority",
"should",
];
for a in accepts {
args.push("--accept");
args.push(*a);
}
s.run(&args)
}
#[test]
fn req_0006_modal_verb_required_rejects_missing() {
let s = Sandbox::new();
s.init("v");
let out = add_minimal(
&s,
"The user clicks the big shiny button.",
"constraint",
&[],
);
assert!(!out.status.success());
assert!(
stderr(&out).contains("modal verb"),
"stderr was: {}",
stderr(&out)
);
}
#[test]
fn req_0006_modal_verb_present_passes() {
let s = Sandbox::new();
s.init("v");
let out = add_minimal(
&s,
"The user shall click the button to submit the form.",
"constraint",
&[],
);
assert!(out.status.success(), "stderr was: {}", stderr(&out));
}
#[test]
fn req_0006_modal_verb_in_url_does_not_count() {
let s = Sandbox::new();
s.init("v");
let out = add_minimal(
&s,
"The implementation might visit https://shall.example.com/ to do work.",
"constraint",
&[],
);
assert!(
!out.status.success(),
"URL modal should not satisfy the rule"
);
}
#[test]
fn req_0008_functional_without_acceptance_rejected() {
let s = Sandbox::new();
s.init("v");
let out = add_minimal(
&s,
"The system shall greet the user when they open the application.",
"functional",
&[],
);
assert!(!out.status.success());
assert!(stderr(&out).contains("acceptance"));
}
#[test]
fn req_0008_functional_with_acceptance_passes() {
let s = Sandbox::new();
s.init("v");
let out = add_minimal(
&s,
"The system shall greet the user when they open the application.",
"functional",
&["Launch screen shows the greeting modal within 200ms"],
);
assert!(out.status.success(), "stderr was: {}", stderr(&out));
}
#[test]
fn req_0011_empty_rationale_rejected() {
let s = Sandbox::new();
s.init("v");
let out = s.run(&[
"add",
"--title",
"Reasonable title",
"--statement",
"The system shall send a confirmation email after order placement.",
"--rationale",
"",
"--kind",
"functional",
"--priority",
"should",
"--accept",
"Confirmation email arrives within 60s in test fixture",
]);
assert!(!out.status.success());
assert!(stderr(&out).contains("rationale"));
}
#[test]
fn req_0029_compound_statement_warns_on_double_shall() {
let s = Sandbox::new();
s.init("v");
let out = add_minimal(
&s,
"The system shall do alpha and the system shall do beta as separate workflows.",
"constraint",
&[],
);
assert!(
out.status.success(),
"compound is warning, not error: {}",
stderr(&out)
);
assert!(
stderr(&out).contains("compound"),
"expected compound warning, stderr={}",
stderr(&out)
);
}
#[test]
fn req_0030_emoji_title_too_short_rejected() {
let s = Sandbox::new();
s.init("v");
let out = s.run(&[
"add",
"--title",
"ππΈπͺπ",
"--statement",
"The system shall do something useful for the user.",
"--rationale",
"Verify title is counted in chars not bytes.",
"--kind",
"constraint",
"--priority",
"could",
]);
assert!(!out.status.success());
assert!(stderr(&out).contains("min 5 characters"));
}
#[test]
fn req_0045_validator_emits_stable_rule_codes() {
let s = Sandbox::new();
s.init("v");
let _ = add_minimal(&s, "Bad short.", "constraint", &[]);
let out = s.run(&["validate"]);
assert!(out.status.success() || out.status.code() == Some(1));
}
#[test]
fn req_0010_failed_add_does_not_burn_id() {
let s = Sandbox::new();
s.init("v");
let _ = add_minimal(&s, "Quietly does the wrong thing.", "constraint", &[]);
let ok = add_minimal(
&s,
"The system shall greet the user with a clear hello.",
"constraint",
&[],
);
assert!(ok.status.success(), "stderr={}", stderr(&ok));
let listed = s.run(&["list", "--json"]);
let body = common::stdout(&listed);
assert!(
body.contains("REQ-0001"),
"expected REQ-0001 to be first allocation: {}",
body
);
assert!(
!body.contains("REQ-0002"),
"no second allocation expected: {}",
body
);
}