use std::path::{Path, PathBuf};
use assert_cmd::Command;
fn shipshape() -> Command {
Command::cargo_bin("shipshape").expect("shipshape binary builds")
}
fn skills_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("skills")
}
fn repo_root() -> Option<PathBuf> {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let root = manifest_dir.parent()?.parent()?;
(root.join("Cargo.toml").is_file() && root.join("OSS-RELEASE.md").is_file())
.then(|| root.to_path_buf())
}
fn bundled_templates() -> Vec<(String, PathBuf)> {
let mut out = Vec::new();
for entry in std::fs::read_dir(skills_dir())
.expect("skills/ dir exists")
.flatten()
{
let dir = entry.path();
if !dir.is_dir() {
continue;
}
let template = dir.join("SKILL.template.md");
if template.exists() {
let name = dir.file_name().unwrap().to_string_lossy().into_owned();
out.push((name, template));
}
}
out.sort();
assert!(!out.is_empty(), "at least one skill must be bundled");
out
}
#[test]
fn frontmatter_is_well_formed_and_pins_version() {
for (name, path) in bundled_templates() {
let raw = std::fs::read_to_string(&path).unwrap();
assert!(
raw.contains("{{CLI_VERSION}}"),
"{name}: template must carry the {{{{CLI_VERSION}}}} token"
);
let fm_name = frontmatter_field(&raw, "name")
.unwrap_or_else(|| panic!("{name}: no `name:` in frontmatter"));
assert_eq!(fm_name, name, "{name}: frontmatter name matches directory");
let out = shipshape()
.args(["skill", "print", &name])
.output()
.unwrap();
assert!(
out.status.success(),
"{name}: template exists on disk but is not printable (missing from CATALOG?): {}",
String::from_utf8_lossy(&out.stderr)
);
let rendered = String::from_utf8(out.stdout).unwrap();
for token in ["{{CLI_VERSION}}", "{{SKILL_SCHEMA_VERSION}}"] {
assert!(
!rendered.contains(token),
"{name}: rendered skill still contains {token}"
);
}
}
}
#[test]
fn catalog_matches_disk_and_names_are_safe_slugs() {
let mut on_disk: Vec<String> = bundled_templates().into_iter().map(|(n, _)| n).collect();
on_disk.sort();
let out = shipshape()
.args(["skill", "list", "--json"])
.output()
.unwrap();
assert!(out.status.success());
let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
let mut catalog: Vec<String> = v["data"]["skills"]
.as_array()
.unwrap()
.iter()
.map(|s| s["name"].as_str().unwrap().to_string())
.collect();
catalog.sort();
assert_eq!(
on_disk, catalog,
"every bundled template must be in the catalog and vice-versa"
);
for name in &catalog {
assert!(
!name.is_empty()
&& name
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
&& !name.starts_with('-'),
"skill name `{name}` is not a safe slug (would risk path escape under the install root)"
);
}
}
#[test]
fn referenced_commands_and_flags_exist() {
for (name, path) in bundled_templates() {
let raw = std::fs::read_to_string(&path).unwrap();
for cmd in fenced_shipshape_commands(&raw) {
check_command(&name, &cmd);
}
}
}
#[test]
fn repository_workflows_select_the_registry_cli_package() {
let Some(root) = repo_root() else {
assert!(
std::env::var_os("CI").is_none(),
"CI must run this repository guard from a complete source checkout"
);
return;
};
let ci = std::fs::read_to_string(root.join(".github/workflows/ci.yml"))
.expect("source checkout must track the CI workflow");
let lockstep = ci
.lines()
.map(str::trim)
.find(|line| {
!line.starts_with('#')
&& line.contains("cargo test")
&& line.contains("--test skill")
&& line.contains("--test skill_lockstep")
})
.expect("the named lockstep job must run both skill integration tests");
assert!(
command_selects_package(lockstep, "shipshape-cli"),
"the lockstep job must select Cargo package `shipshape-cli`: {lockstep}"
);
assert!(
!command_selects_package(lockstep, "shipshape"),
"the lockstep job must not select the cargo-dist wrapper: {lockstep}"
);
let publish = std::fs::read_to_string(root.join(".github/workflows/publish-crates.yml"))
.expect("source checkout must track the crates.io publish workflow");
let mut publishes_cli = false;
for line in publish.lines().map(str::trim) {
if line.is_empty() || line.starts_with('#') || line.starts_with("publish()") {
continue;
}
let tokens = shellish_tokens(line);
for pair in tokens.windows(2) {
if matches!(pair[0].as_str(), "-p" | "--package") {
assert_ne!(
pair[1], "shipshape",
"crates.io workflow selects the non-published cargo-dist wrapper: {line}"
);
}
}
let helper_arg = match tokens.as_slice() {
[verb, package, ..] if verb == "publish" => Some(package),
[condition, verb, package, ..] if condition == "if" && verb == "publish" => {
Some(package)
}
_ => None,
};
if let Some(package) = helper_arg {
assert!(
matches!(package.as_str(), "shipshape-core" | "shipshape-cli"),
"publish helper calls must use a literal registry package: {line}"
);
publishes_cli |= package == "shipshape-cli";
}
}
assert!(
publishes_cli,
"the crates.io workflow must publish Cargo package `shipshape-cli`"
);
}
fn shellish_tokens(line: &str) -> Vec<String> {
line.split_whitespace()
.map(|token| {
token
.trim_matches(|c: char| matches!(c, '\'' | '"' | ';' | '(' | ')' | ','))
.to_string()
})
.collect()
}
fn command_selects_package(command: &str, package: &str) -> bool {
let tokens = shellish_tokens(command);
tokens
.windows(2)
.any(|pair| matches!(pair[0].as_str(), "-p" | "--package") && pair[1] == package)
|| tokens.iter().any(|token| {
token == &format!("-p={package}") || token == &format!("--package={package}")
})
}
#[test]
#[should_panic(expected = "unknown flag")]
fn gate_rejects_a_bogus_flag() {
check_command("self-test", "shipshape audit --no-such-flag");
}
#[test]
#[should_panic(expected = "unknown subcommand")]
fn gate_rejects_a_bogus_subcommand() {
check_command("self-test", "shipshape frobnicate widgets");
}
#[test]
#[should_panic(expected = "unknown subcommand")]
fn gate_rejects_a_bogus_child_of_a_real_parent() {
check_command("self-test", "shipshape release frobnicate --json");
}
#[test]
#[should_panic(expected = "unknown flag")]
fn gate_rejects_a_prefix_of_a_real_flag() {
check_command("self-test", "shipshape release cut --pla");
}
fn fenced_shipshape_commands(text: &str) -> Vec<String> {
let mut cmds = Vec::new();
let mut in_fence = false;
for line in text.lines() {
if line.trim_start().starts_with("```") {
in_fence = !in_fence;
continue;
}
if !in_fence {
continue;
}
let line = line.trim();
if let Some(rest) = line.strip_prefix("shipshape ") {
let end = rest
.find(&['|', '>'][..])
.or_else(|| rest.find("&&"))
.unwrap_or(rest.len());
let cmd = format!("shipshape {}", rest[..end].trim());
cmds.push(cmd);
}
}
cmds
}
fn check_command(skill: &str, cmd: &str) {
let tokens: Vec<&str> = cmd.split_whitespace().collect();
assert_eq!(
tokens[0], "shipshape",
"{skill}: `{cmd}` must start with shipshape"
);
let args = &tokens[1..];
let path_len = args
.iter()
.take_while(|t| !t.starts_with('-') && !t.starts_with('<'))
.count();
let path = &args[..path_len];
let out = shipshape().args(path).arg("--help").output().unwrap();
assert!(
out.status.success(),
"{skill}: `{cmd}` references an unknown subcommand `shipshape {}`",
path.join(" ")
);
let help_text = String::from_utf8_lossy(&out.stdout).into_owned();
for tok in args {
if let Some(flag) = tok.strip_prefix("--") {
let flag = flag.split('=').next().unwrap();
if flag.is_empty() {
continue;
}
assert!(
help_lists_flag(&help_text, flag),
"{skill}: `{cmd}` references unknown flag `--{flag}` on `shipshape {}`",
path.join(" ")
);
}
}
}
fn help_lists_flag(help: &str, flag: &str) -> bool {
let want = format!("--{flag}");
help.split_whitespace()
.any(|t| t.trim_end_matches(',') == want)
}
fn frontmatter_field(text: &str, key: &str) -> Option<String> {
let text = text.strip_prefix('\u{FEFF}').unwrap_or(text);
let body = text.strip_prefix("---")?;
let end = body.find("\n---")?;
for line in body[..end].lines() {
if line.starts_with(char::is_whitespace) {
continue;
}
let Some(rest) = line.trim_end().strip_prefix(&format!("{key}:")) else {
continue;
};
let v = rest.split(" #").next().unwrap_or(rest).trim();
let v = v.trim_matches(|c| c == '"' || c == '\'');
if !v.is_empty() {
return Some(v.to_string());
}
}
None
}