fabro/lib/crates/fabro-dev/tests/it/generate_cli_reference.rs
Bryan Helmkamp 0f3c28db87
feat(dev): generate CLI reference
Add a cargo dev generator for the CLI reference and gate the generated docs in CI. The generator reads the fabro clap command tree through a narrow public reference surface so CLI docs drift is caught without exposing runtime command internals.
2026-04-24 16:35:06 -04:00

152 lines
3.9 KiB
Rust

use std::fs;
use std::path::Path;
fn fabro_dev() -> assert_cmd::Command {
assert_cmd::cargo::cargo_bin_cmd!("fabro-dev")
}
fn output_text(bytes: &[u8]) -> String {
String::from_utf8(bytes.to_vec()).expect("command output should be valid utf-8")
}
#[expect(
clippy::disallowed_methods,
reason = "integration tests stage temporary CLI reference fixtures with sync std::fs::write"
)]
fn write_file(root: &Path, path: &str, contents: &str) {
let path = root.join(path);
fs::create_dir_all(path.parent().expect("fixture path should have parent"))
.expect("creating fixture parent directory");
fs::write(path, contents).expect("writing fixture file");
}
#[expect(
clippy::disallowed_methods,
reason = "integration tests inspect generated CLI reference fixtures with sync std::fs::read_to_string"
)]
fn read_file(root: &Path, path: &str) -> String {
fs::read_to_string(root.join(path)).expect("reading fixture file")
}
fn cli_reference(root: &Path) -> assert_cmd::Command {
let mut cmd = fabro_dev();
cmd.args(["generate-cli-reference", "--root"]).arg(root);
cmd
}
#[test]
fn write_updates_only_generated_region() {
let fixture = tempfile::tempdir().expect("creating fixture");
write_file(
fixture.path(),
"docs/reference/cli.mdx",
r"---
title: CLI
---
Intro copy.
<!-- generated:cli -->
stale
<!-- /generated:cli -->
Tail copy.
",
);
cli_reference(fixture.path()).assert().success();
let contents = read_file(fixture.path(), "docs/reference/cli.mdx");
assert!(
contents.contains("Intro copy."),
"manual intro should be preserved:\n{contents}"
);
assert!(
contents.contains("Tail copy."),
"manual tail should be preserved:\n{contents}"
);
assert!(
contents.contains("## `fabro`"),
"generated output should include root command reference:\n{contents}"
);
assert!(
contents.contains("### `fabro run`"),
"generated output should include subcommand reference:\n{contents}"
);
assert!(
contents.contains("TODO: add CLI help text."),
"undocumented clap args should be visible follow-up work:\n{contents}"
);
assert!(
!contents.contains("stale"),
"stale generated content should be replaced:\n{contents}"
);
}
#[test]
fn check_passes_after_write() {
let fixture = tempfile::tempdir().expect("creating fixture");
write_file(
fixture.path(),
"docs/reference/cli.mdx",
r"<!-- generated:cli -->
stale
<!-- /generated:cli -->
",
);
cli_reference(fixture.path()).assert().success();
cli_reference(fixture.path())
.arg("--check")
.assert()
.success();
}
#[test]
fn check_fails_when_generated_region_is_stale() {
let fixture = tempfile::tempdir().expect("creating fixture");
write_file(
fixture.path(),
"docs/reference/cli.mdx",
r"<!-- generated:cli -->
stale
<!-- /generated:cli -->
",
);
let output = cli_reference(fixture.path())
.arg("--check")
.assert()
.failure()
.code(1)
.get_output()
.clone();
let stderr = output_text(&output.stderr);
assert!(
stderr.contains("docs/reference/cli.mdx is stale; run `cargo dev generate-cli-reference`"),
"check failure should explain how to regenerate:\n{stderr}"
);
}
#[test]
fn generated_reference_is_deterministic() {
let fixture = tempfile::tempdir().expect("creating fixture");
write_file(
fixture.path(),
"docs/reference/cli.mdx",
r"<!-- generated:cli -->
stale
<!-- /generated:cli -->
",
);
cli_reference(fixture.path()).assert().success();
let first = read_file(fixture.path(), "docs/reference/cli.mdx");
cli_reference(fixture.path()).assert().success();
let second = read_file(fixture.path(), "docs/reference/cli.mdx");
assert_eq!(first, second);
}