use std::path::{Path, PathBuf};
use std::process::Command;
const BIN: &str = env!("CARGO_BIN_EXE_roteiro");
fn git(dir: &Path, args: &[&str]) {
let status = Command::new("git")
.args([
"-c",
"user.name=Test",
"-c",
"user.email=test@example.com",
"-c",
"commit.gpgsign=false",
"-c",
"init.defaultBranch=main",
])
.args(args)
.current_dir(dir)
.status()
.expect("run git");
assert!(status.success(), "git {args:?} failed");
}
fn git_as(dir: &Path, who: &str, when: &str, args: &[&str]) {
let status = Command::new("git")
.args([
"-c",
&format!("user.name={who}"),
"-c",
&format!("user.email={}@example.com", who.to_ascii_lowercase()),
"-c",
"commit.gpgsign=false",
])
.args(args)
.env("GIT_AUTHOR_DATE", when)
.env("GIT_COMMITTER_DATE", when)
.current_dir(dir)
.status()
.expect("run git");
assert!(status.success(), "git {args:?} as {who} failed");
}
fn commit_as(dir: &Path, who: &str, when: &str, message: &str) {
git_as(dir, who, when, &["commit", "-q", "-m", message]);
}
fn write(dir: &Path, rel: &str, content: &str) {
let path = dir.join(rel);
std::fs::create_dir_all(path.parent().unwrap()).expect("mkdir");
std::fs::write(path, content).expect("write");
}
fn fresh_dir(tag: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("roteiro-render-cli-{tag}-{}", std::process::id()));
std::fs::remove_dir_all(&dir).ok();
std::fs::create_dir_all(&dir).expect("mkdir");
dir
}
#[test]
fn render_docs_builds_site_from_adrs_and_assets() {
let dir = fresh_dir("docs");
git(&dir, &["init", "-q"]);
write(&dir, "website/public/style.css", "body{color:#111}\n");
write(&dir, "website/public/index.html", "<h1>Home</h1>\n");
write(&dir, "website/public/favicon.svg", "<svg/>\n");
write(
&dir,
"docs/adr/0001-example.md",
"---\nadr-id: \"0001\"\nstatus: Accepted\n---\n\n# ADR-0001: Example\n\n## Context\n\n| a | b |\n|---|---|\n| 1 | 2 |\n",
);
write(&dir, "docs/adr/README.md", "index, not an ADR\n");
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "init"]);
let out = Command::new(BIN)
.args(["render", "docs", "--out", "site"])
.current_dir(&dir)
.output()
.expect("run render");
assert!(out.status.success(), "render failed: {out:?}");
let site = dir.join("site");
assert!(site.join("style.css").exists());
assert!(site.join("index.html").exists());
assert!(site.join("favicon.svg").exists());
let page = std::fs::read_to_string(site.join("adr/0001-example.html")).expect("adr page");
assert!(page.starts_with("<!doctype html>"));
assert!(page.contains("<h1 id=\"adr-0001-example\">ADR-0001: Example</h1>"));
assert!(page.contains("<table>"), "GFM table should render");
assert!(!page.contains("adr-id"), "frontmatter should be stripped");
assert!(page.contains("← Back to roteiro.dev"));
assert!(
!site.join("adr/README.html").exists(),
"README is not an ADR page"
);
let index = std::fs::read_to_string(site.join("adr/index.html")).expect("index");
assert!(index.contains("<a href=\"0001-example.html\">ADR-0001: Example</a>"));
std::fs::remove_dir_all(&dir).ok();
}
const CURRENT_PAGE: &str = "<marked current, not a link>";
fn bar_entries(html: &str) -> Vec<(String, String)> {
let Some(start) = html.find("<nav class=\"sitenav\">") else {
return Vec::new();
};
let bar = &html[start..];
let bar = &bar[..bar.find("</nav>").unwrap_or(bar.len())];
let mut entries = Vec::new();
let mut rest = bar;
while let Some(open) = rest.find('<') {
let Some((tag, after)) = rest[open + 1..].split_once('>') else {
break;
};
let label: String = after.chars().take_while(|c| *c != '<').collect();
let label = label.trim().to_owned();
if !label.is_empty() {
let dest = tag
.split_once("href=\"")
.and_then(|(_, r)| r.split_once('"'))
.map_or_else(|| CURRENT_PAGE.to_owned(), |(href, _)| href.to_owned());
entries.push((label, dest));
}
rest = after;
}
entries
}
fn bar_as_seen_from(html: &str, own_href: &str) -> Vec<(String, String)> {
let entries = bar_entries(html);
let marked = entries
.iter()
.filter(|(_, dest)| dest == CURRENT_PAGE)
.count();
assert_eq!(
marked, 1,
"the bar on {own_href} marks {marked} pages as current, expected exactly 1: {entries:?}"
);
entries
.into_iter()
.map(|(label, dest)| {
if dest == CURRENT_PAGE {
(label, own_href.to_owned())
} else {
(label, dest)
}
})
.collect()
}
#[test]
fn a_declared_site_page_is_emitted_with_the_shared_bar() {
let dir = fresh_dir("sitepage");
git(&dir, &["init", "-q"]);
write(&dir, "website/public/style.css", "body{color:#111}\n");
write(&dir, "website/public/index.html", "<h1>Home</h1>\n");
write(
&dir,
"docs/adr/0001-example.md",
"---\nadr-id: \"0001\"\nstatus: Accepted\n---\n\n# ADR-0001: Example\n",
);
write(
&dir,
"website/pages/modes.md",
"---\nsite-page: modes\nsite-nav: Modes\nsite-order: 2\n---\n\n\
# The five ways to run it {#modes}\n\n## Offline mode\n\nNo models, no network.\n",
);
write(
&dir,
"docs/GUIDE.md",
"---\nsite-page: guide\nsite-nav: Guide\nsite-order: 1\n---\n\n\
# A guide\n\nSequenced in [the plan](BUILD_PLAN_V2.md).\n",
);
write(
&dir,
"docs/history/BUILD_PLAN_V2.md",
"---\nsite-page: build-plan-v2\nsite-nav: Roadmap\nsite-order: 3\n---\n\n# Roadmap\n",
);
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "init"]);
let out = Command::new(BIN)
.args(["render", "docs", "--out", "site"])
.current_dir(&dir)
.output()
.expect("run render");
assert!(out.status.success(), "render failed: {out:?}");
let site = dir.join("site");
let modes = std::fs::read_to_string(site.join("modes.html")).expect("modes page");
assert!(
site.join("guide.html").exists(),
"docs/GUIDE.md → guide.html"
);
assert!(site.join("build-plan-v2.html").exists());
assert!(
modes.contains("id=\"modes\""),
"explicit anchor preserved: {modes}"
);
assert!(!modes.contains("site-page"), "frontmatter is not content");
let expected: Vec<(String, String)> = [
("Home", "./"),
("Guide", "guide.html"),
("Modes", "modes.html"),
("Roadmap", "build-plan-v2.html"),
]
.into_iter()
.map(|(label, dest)| (label.to_owned(), dest.to_owned()))
.collect();
for page in ["modes.html", "guide.html", "build-plan-v2.html"] {
let html = std::fs::read_to_string(site.join(page)).expect("page");
assert_eq!(bar_as_seen_from(&html, page), expected, "bar on {page}");
}
assert!(
modes.contains("<span aria-current=\"page\">Modes</span>"),
"current page unlinked: {modes}"
);
let guide = std::fs::read_to_string(site.join("guide.html")).expect("guide page");
assert!(
guide.contains("href=\"build-plan-v2.html\""),
"link resolved to the published slug: {guide}"
);
assert!(
!guide.contains("BUILD_PLAN_V2.html"),
"the file-name guess is gone: {guide}"
);
}
#[test]
fn a_source_link_is_aimed_at_the_repository_at_the_rendered_commit() {
let dir = fresh_dir("sourcelink");
git(&dir, &["init", "-q"]);
write(&dir, "website/public/index.html", "<h1>Home</h1>\n");
write(
&dir,
"docs/adr/0001-example.md",
"---\nadr-id: \"0001\"\nstatus: Accepted\n---\n\n# ADR-0001: Example\n\n\
Root config: [Cargo](../../Cargo.toml).\n",
);
write(
&dir,
"docs/history/BUILD_PLAN.md",
"# Build Plan\n\nEvidence: [sync](../../crates/x/src/sync.rs).\n\n\
Site link: [adrs](adr/).\n",
);
write(&dir, "crates/x/src/sync.rs", "pub fn f() {}\n");
write(&dir, "Cargo.toml", "[workspace]\n");
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "init"]);
git(&dir, &["remote", "add", "origin", "git@github.com:o/r.git"]);
let sha = String::from_utf8(
Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(&dir)
.output()
.expect("rev-parse")
.stdout,
)
.expect("utf8");
let sha = sha.trim();
let out = Command::new(BIN)
.args(["render", "docs", "--out", "site"])
.current_dir(&dir)
.output()
.expect("run render");
assert!(out.status.success(), "render failed: {out:?}");
let plan =
std::fs::read_to_string(dir.join("site/history/build-plan.html")).expect("plan page");
assert!(
plan.contains(&format!(
"href=\"https://github.com/o/r/blob/{sha}/crates/x/src/sync.rs\""
)),
"pinned to the rendered commit, not to a branch: {plan}"
);
assert!(plan.contains("href=\"adr/\""), "{plan}");
let adr = std::fs::read_to_string(dir.join("site/adr/0001-example.html")).expect("adr page");
assert!(
adr.contains(&format!(
"href=\"https://github.com/o/r/blob/{sha}/Cargo.toml\""
)),
"{adr}"
);
}
#[test]
fn without_an_origin_remote_a_source_link_is_left_as_authored() {
let dir = fresh_dir("noorigin");
git(&dir, &["init", "-q"]);
write(&dir, "website/public/index.html", "<h1>Home</h1>\n");
write(
&dir,
"docs/adr/0001-example.md",
"---\nadr-id: \"0001\"\nstatus: Accepted\n---\n\n# ADR-0001: Example\n",
);
write(
&dir,
"docs/history/BUILD_PLAN.md",
"# Build Plan\n\nEvidence: [sync](../crates/x/src/sync.rs).\n",
);
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "init"]);
let out = Command::new(BIN)
.args(["render", "docs", "--out", "site"])
.current_dir(&dir)
.output()
.expect("run render");
assert!(out.status.success(), "render failed: {out:?}");
let plan =
std::fs::read_to_string(dir.join("site/history/build-plan.html")).expect("plan page");
assert!(!plan.contains("github.com"), "nothing invented: {plan}");
assert!(
plan.contains("href=\"../crates/x/src/sync.rs\""),
"left exactly as authored: {plan}"
);
}
#[test]
fn the_bar_on_the_landing_page_is_rendered_over_whatever_the_file_carried() {
let dir = fresh_dir("landingnav");
git(&dir, &["init", "-q"]);
write(
&dir,
"website/public/index.html",
"<h1>Roteiro</h1>\n<nav class=\"sitenav\">\n<a href=\"gone.html\">Gone</a>\n</nav>\n\
<p>tail</p>\n",
);
write(
&dir,
"docs/adr/0001-example.md",
"---\nadr-id: \"0001\"\nstatus: Accepted\n---\n\n# ADR-0001: Example\n",
);
write(
&dir,
"website/pages/modes.md",
"---\nsite-page: modes\nsite-nav: Modes\nsite-order: 1\n---\n\n# Modes\n",
);
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "init"]);
let out = Command::new(BIN)
.args(["render", "docs", "--out", "site"])
.current_dir(&dir)
.output()
.expect("run render");
assert!(out.status.success(), "render failed: {out:?}");
let landing = std::fs::read_to_string(dir.join("site/index.html")).expect("landing page");
assert!(
!landing.contains("gone.html"),
"the hand-written list is overwritten, not merged: {landing}"
);
assert_eq!(
bar_as_seen_from(&landing, "./"),
vec![
("Home".to_owned(), "./".to_owned()),
("Modes".to_owned(), "modes.html".to_owned()),
],
"the landing page carries the computed bar: {landing}"
);
assert!(landing.starts_with("<h1>Roteiro</h1>\n"), "{landing}");
assert!(landing.ends_with("<p>tail</p>\n"), "{landing}");
}
#[test]
fn the_landing_page_carries_the_bar_the_renderer_emits() {
let out_dir = std::env::temp_dir().join(format!("roteiro-website-bar-{}", std::process::id()));
std::fs::remove_dir_all(&out_dir).ok();
let out = Command::new(BIN)
.args(["render", "docs", "--out"])
.arg(&out_dir)
.current_dir(env!("CARGO_MANIFEST_DIR"))
.output()
.expect("run render");
assert!(out.status.success(), "render failed: {out:?}");
let landing = std::fs::read_to_string(out_dir.join("index.html")).expect("landing page");
let landing_bar = bar_as_seen_from(&landing, "./");
assert!(
!landing_bar.is_empty(),
"the landing page carries a site bar"
);
let rendered = std::fs::read_dir(&out_dir)
.expect("read site")
.filter_map(Result::ok)
.map(|e| e.path())
.find(|p| {
p.extension().and_then(|e| e.to_str()) == Some("html")
&& p.file_name().and_then(|n| n.to_str()) != Some("index.html")
&& std::fs::read_to_string(p).is_ok_and(|h| h.contains("<nav class=\"sitenav\">"))
})
.expect("at least one rendered site page");
let rendered_href = rendered
.file_name()
.and_then(|n| n.to_str())
.expect("rendered page name");
let emitted = bar_as_seen_from(
&std::fs::read_to_string(&rendered).expect("rendered page"),
rendered_href,
);
assert_eq!(
landing_bar,
emitted,
"website/public/index.html's site bar disagrees with the bar {} carries \
(labels *and* destinations are compared)",
rendered.display()
);
std::fs::remove_dir_all(&out_dir).ok();
}
fn workspace_fixture(tag: &str) -> (PathBuf, PathBuf) {
let base = fresh_dir(tag);
let home = base.join("home");
let app = base.join("app");
let deploy = base.join("deploy");
for d in [&home, &app, &deploy] {
std::fs::create_dir_all(d).expect("mkdir");
}
write(&app, "README.md", "# App\n\nThe hub.\n");
write(
&app,
"config.toml",
"[serve]\naddr = \"127.0.0.1:8017\"\ntools = true\n",
);
git(&app, &["init", "-q"]);
git(&app, &["add", "."]);
git(&app, &["commit", "-q", "-m", "init"]);
write(&deploy, "README.md", "# Deploy\n\nThe spoke.\n");
write(
&deploy,
"prod.env",
"SERVE_ADDR=0.0.0.0:8443\nSERVE_TOOLS=false\n",
);
write(
&deploy,
"roteiro.toml",
"[[links]]\nfrom = \"cfgkey:prod.env#SERVE_ADDR\"\n\
to = \"app::cfgkey:config.toml#serve.addr\"\nkind = \"references\"\n",
);
git(&deploy, &["init", "-q"]);
git(&deploy, &["add", "."]);
git(&deploy, &["commit", "-q", "-m", "init"]);
std::fs::write(
home.join("config.toml"),
format!(
"[[workspaces]]\nname = \"prod\"\nrepos = [\"{}\", \"{}\"]\n",
app.display(),
deploy.display()
),
)
.expect("write config");
(base, home)
}
fn roteiro_in(dir: &Path, home: &Path, args: &[&str]) -> std::process::Output {
Command::new(BIN)
.args(args)
.current_dir(dir)
.env("ROTEIRO_HOME", home)
.output()
.expect("run roteiro")
}
#[test]
fn every_concept_is_written_and_the_count_printed_is_the_count_written() {
let dir = fresh_dir("okf-lossless");
git(&dir, &["init", "-q"]);
let vendored = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/assets/cytoscape.min.js");
assert!(
vendored.is_file(),
"the vendored bundle this test measures against is missing at {vendored:?} — \
it moved or was removed, and the test must be re-pointed rather than \
allowed to pass over a repository with no colliding keys"
);
std::fs::create_dir_all(dir.join("assets")).expect("mkdir");
std::fs::copy(&vendored, dir.join("assets/cytoscape.min.js")).expect("copy bundle");
write(
&dir,
"src/lib.rs",
"pub struct Store;\npub fn store() {}\npub mod r#mod {\n pub struct STORE;\n}\n",
);
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "init"]);
let out = Command::new(BIN)
.args(["render", "okf", "--out", "bundle"])
.current_dir(&dir)
.output()
.expect("run render okf");
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
out.status.success(),
"render failed: {stdout}{}",
String::from_utf8_lossy(&out.stderr)
);
let printed: usize = stdout
.split(" concept(s)")
.next()
.and_then(|p| p.rsplit('(').next())
.and_then(|n| n.trim().parse().ok())
.unwrap_or_else(|| panic!("no concept count in: {stdout}"));
let bundle = dir.join("bundle");
let mut written = 0usize;
let mut stack = vec![bundle.clone()];
while let Some(d) = stack.pop() {
for e in std::fs::read_dir(&d).expect("read_dir") {
let path = e.expect("entry").path();
if path.is_dir() {
stack.push(path);
} else if path.extension().is_some_and(|x| x == "md")
&& path
.file_name()
.is_some_and(|n| n != "index.md" && n != "log.md")
{
written += 1;
}
}
}
assert!(printed > 0, "the fixture must produce concepts: {stdout}");
assert_eq!(
printed, written,
"the count printed must equal the files written — a bundle that lost one \
silently is the defect this replaces"
);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn render_okf_rejects_an_unknown_workspace_name() {
let dir = fresh_dir("okf-unknown-ws");
git(&dir, &["init", "-q"]);
write(&dir, "src/lib.rs", "pub struct Thing;\n");
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "init"]);
let out = Command::new(BIN)
.args([
"render",
"okf",
"--workspace-name",
"nope",
"--out",
"bundle",
])
.current_dir(&dir)
.output()
.expect("run render okf");
assert!(
!out.status.success(),
"an unknown workspace must fail, not fall back to the current project"
);
assert!(
!dir.join("bundle").exists(),
"and must not have written a bundle first"
);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn render_obsidian_explains_that_it_became_okf() {
let dir = fresh_dir("okf-removed-target");
git(&dir, &["init", "-q"]);
write(&dir, "src/lib.rs", "pub struct Thing;\n");
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "init"]);
let out = Command::new(BIN)
.args(["render", "obsidian"])
.current_dir(&dir)
.output()
.expect("run render obsidian");
assert!(!out.status.success(), "the removed target must fail");
let msg = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(msg.contains("render okf"), "names its replacement: {msg}");
assert!(
msg.contains("Obsidian still opens"),
"and says Obsidian still works, since that is the reader's first question: {msg}"
);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn a_prose_concept_carries_its_whole_source() {
let dir = fresh_dir("okf-prose");
git(&dir, &["init", "-q"]);
let body = "# Title\n\nA distinctive sentence that must survive whole.\n";
write(&dir, "README.md", body);
write(&dir, "src/lib.rs", "pub struct Thing;\n");
write(&dir, "roteiro.toml", "[ingest]\nprose = true\n");
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "init"]);
let out = Command::new(BIN)
.args(["render", "okf", "--out", "bundle"])
.current_dir(&dir)
.output()
.expect("run render okf");
assert!(
out.status.success(),
"render failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let files = dir.join("bundle/files");
let found = std::fs::read_dir(&files)
.expect("files/ must exist")
.filter_map(std::result::Result::ok)
.map(|e| std::fs::read_to_string(e.path()).unwrap_or_default())
.any(|t| t.contains("A distinctive sentence that must survive whole."));
assert!(found, "the prose body must reach the bundle");
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn a_workspace_bundle_keeps_both_members_readme() {
let (base, home) = workspace_fixture("okf-ws");
let app = base.join("app");
let out = roteiro_in(
&app,
&home,
&[
"render",
"okf",
"--workspace-name",
"prod",
"--out",
"bundle",
],
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
out.status.success(),
"workspace render failed: {stdout}{}",
String::from_utf8_lossy(&out.stderr)
);
let bundle = app.join("bundle");
let mut readmes = Vec::new();
let mut readme_paths = Vec::new();
let mut stack = vec![bundle.clone()];
while let Some(d) = stack.pop() {
for e in std::fs::read_dir(&d).expect("read_dir") {
let path = e.expect("entry").path();
if path.is_dir() {
stack.push(path);
} else if path.extension().is_some_and(|x| x == "md") {
let text = std::fs::read_to_string(&path).unwrap_or_default();
if text.contains("type: \"file\"") && text.contains("README.md") {
readme_paths.push(path.strip_prefix(&bundle).unwrap_or(&path).to_path_buf());
readmes.push(text);
}
}
}
}
assert_eq!(
readmes.len(),
2,
"both members' README concepts must be written, not one overwriting the \
other: found {}",
readmes.len()
);
assert!(
readmes.iter().any(|t| t.contains("The hub.")),
"the hub's README text is missing"
);
assert!(
readmes.iter().any(|t| t.contains("The spoke.")),
"the spoke's README text is missing"
);
let dirs: std::collections::BTreeSet<String> = readme_paths
.iter()
.filter_map(|p| p.iter().next())
.map(|c| c.to_string_lossy().into_owned())
.collect();
assert_eq!(
dirs.len(),
2,
"each member's README belongs under its own member directory, got {dirs:?}"
);
std::fs::remove_dir_all(&base).ok();
}
fn bundle_files(root: &Path) -> Vec<(String, String)> {
let mut out = Vec::new();
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
for entry in std::fs::read_dir(&dir).expect("read_dir") {
let path = entry.expect("entry").path();
if path.is_dir() {
stack.push(path);
} else if path.extension().is_some_and(|x| x == "md") {
let rel = path
.strip_prefix(root)
.expect("under root")
.to_string_lossy()
.replace('\\', "/");
out.push((rel, std::fs::read_to_string(&path).expect("read")));
}
}
}
out.sort();
out
}
#[test]
fn the_verifier_is_the_documents_own_author_and_the_bundle_is_dated_by_the_commit() {
const ADA: &str = "2020-01-02T03:04:05+00:00";
const GRACE: &str = "2021-02-03T04:05:06+00:00";
const HEAD: &str = "2022-03-04T05:06:07+00:00";
let dir = fresh_dir("okf-attribution");
git(&dir, &["init", "-q"]);
let adr = |id: &str, title: &str| {
format!(
"---\nadr-id: \"{id}\"\nstatus: Accepted\n---\n\n# ADR-{id}: {title}\n\n\
## Context\n\nProse.\n"
)
};
write(&dir, "docs/adr/0001-alpha.md", &adr("0001", "Alpha"));
git(&dir, &["add", "."]);
commit_as(&dir, "Ada", ADA, "alpha");
write(&dir, "docs/adr/0002-beta.md", &adr("0002", "Beta"));
git(&dir, &["add", "."]);
commit_as(&dir, "Grace", GRACE, "beta");
write(&dir, "src/lib.rs", "pub struct Thing;\n");
git(&dir, &["add", "."]);
commit_as(&dir, "Mallory", HEAD, "unrelated code");
let out = Command::new(BIN)
.args(["render", "okf", "--out", "bundle"])
.current_dir(&dir)
.output()
.expect("run render okf");
assert!(
out.status.success(),
"render failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let files = bundle_files(&dir.join("bundle"));
let human_verified = files
.iter()
.filter(|(_, text)| text.contains("verified:\n - by: \"human:"))
.count();
assert!(
human_verified >= 2,
"the fixture must produce human-verified concepts: {:?}",
files.iter().map(|(p, _)| p).collect::<Vec<_>>()
);
let carrying = |needle: String| -> Vec<String> {
files
.iter()
.filter(|(_, text)| text.contains(&needle))
.map(|(path, _)| path.clone())
.collect()
};
for (source, who, when) in [
("docs/adr/0001-alpha.md", "Ada", "2020-01-02T03:04:05Z"),
("docs/adr/0002-beta.md", "Grace", "2021-02-03T04:05:06Z"),
] {
let concepts: Vec<&(String, String)> = files
.iter()
.filter(|(_, text)| text.contains(&format!("- resource: \"/{source}\"")))
.filter(|(_, text)| text.contains("verified:\n - by: \"human:"))
.collect();
assert!(
!concepts.is_empty(),
"no human-verified concept is sourced from {source}"
);
for (path, text) in &concepts {
assert!(
text.contains(&format!("by: \"human:{who}\"")),
"{path} is sourced from {source} but is not confirmed by {who}:\n{text}"
);
assert!(
text.contains(&format!("at: \"{when}\"")),
"{path} must carry {who}'s own commit time {when}:\n{text}"
);
}
}
let leaked = carrying("human:Mallory".to_owned());
assert!(
leaked.is_empty(),
"the HEAD author must not be recorded as confirming documents they never \
touched: {leaked:?}"
);
let head_dated = carrying("at: \"2022-03-04T05:06:07Z\"".to_owned());
assert!(
!head_dated.is_empty(),
"concepts with no document of their own must be dated by HEAD, not by the \
wall clock: {:?}",
files
.iter()
.filter(|(_, t)| t.contains("generated:"))
.map(|(p, _)| p)
.collect::<Vec<_>>()
);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn two_renders_of_one_commit_are_byte_identical() {
let dir = fresh_dir("okf-reproducible");
git(&dir, &["init", "-q"]);
write(&dir, "src/lib.rs", "pub struct Thing;\npub fn thing() {}\n");
write(
&dir,
"docs/adr/0001-a.md",
"---\nadr-id: \"0001\"\nstatus: Accepted\n---\n\n# ADR-0001: A\n\n## Context\n\nProse.\n",
);
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "init"]);
let render = |out: &str| {
let done = Command::new(BIN)
.args(["render", "okf", "--out", out])
.current_dir(&dir)
.output()
.expect("run render okf");
assert!(
done.status.success(),
"render failed: {}",
String::from_utf8_lossy(&done.stderr)
);
bundle_files(&dir.join(out))
};
let once = render("bundle-a");
let twice = render("bundle-b");
assert!(
!once.is_empty(),
"the fixture must produce a bundle to compare"
);
assert_eq!(
once, twice,
"a bundle rendered twice from one commit must not differ"
);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn a_shallow_clone_claims_no_human_verifier_rather_than_the_wrong_one() {
let dir = fresh_dir("okf-shallow");
git(&dir, &["init", "-q"]);
let adr =
"---\nadr-id: \"0001\"\nstatus: Accepted\n---\n\n# ADR-0001: A\n\n## Context\n\nProse.\n";
write(&dir, "docs/adr/0001-a.md", adr);
git(&dir, &["add", "."]);
commit_as(&dir, "Ada", "2020-01-02T03:04:05+00:00", "the adr");
write(&dir, "src/lib.rs", "pub struct Thing;\n");
git(&dir, &["add", "."]);
commit_as(
&dir,
"Mallory",
"2022-03-04T05:06:07+00:00",
"unrelated code",
);
let deep = Command::new(BIN)
.args(["render", "okf", "--out", "deep"])
.current_dir(&dir)
.output()
.expect("run render okf");
assert!(
deep.status.success(),
"{}",
String::from_utf8_lossy(&deep.stderr)
);
assert!(
bundle_files(&dir.join("deep"))
.iter()
.any(|(_, t)| t.contains("by: \"human:Ada\"")),
"the full-depth control must attribute, or this test proves nothing"
);
let shallow = dir.join("shallow");
git(
&dir,
&[
"clone",
"-q",
"--depth",
"1",
&format!("file://{}", dir.display()),
shallow.to_str().expect("utf-8 path"),
],
);
assert!(
shallow.join(".git/shallow").is_file(),
"the clone must actually be shallow, or this test proves nothing"
);
let out = Command::new(BIN)
.args(["render", "okf", "--out", "bundle"])
.current_dir(&shallow)
.output()
.expect("run render okf");
assert!(
out.status.success(),
"a shallow checkout must still render: {}",
String::from_utf8_lossy(&out.stderr)
);
let files = bundle_files(&shallow.join("bundle"));
assert!(
!files.is_empty(),
"the shallow render must produce a bundle"
);
let claimed: Vec<&String> = files
.iter()
.filter(|(_, text)| text.contains("by: \"human:"))
.map(|(path, _)| path)
.collect();
assert!(
claimed.is_empty(),
"no human may be named when the history that would name them is absent: \
{claimed:?}"
);
std::fs::remove_dir_all(&dir).ok();
}
fn frontmatter_of(text: &str) -> Vec<(String, String)> {
let Some(rest) = text.strip_prefix("---\n") else {
return Vec::new();
};
let Some(end) = rest.find("\n---\n") else {
return Vec::new();
};
rest[..end]
.lines()
.filter(|l| !l.starts_with(' ') && !l.starts_with('-'))
.filter_map(|l| l.split_once(": "))
.map(|(k, v)| (k.to_owned(), v.trim_matches('"').to_owned()))
.collect()
}
#[test]
fn an_adrs_status_does_not_leak_onto_the_file_or_its_debt_markers() {
let dir = fresh_dir("okf-status-scope");
git(&dir, &["init", "-q"]);
write(
&dir,
"docs/adr/0001-a.md",
"---\nadr-id: \"0001\"\nstatus: Superseded\n---\n\n# ADR-0001: A\n\n## Context\n\n\
Prose, and a marker: TODO tidy this up.\n",
);
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "init"]);
let out = Command::new(BIN)
.args(["render", "okf", "--out", "bundle"])
.current_dir(&dir)
.output()
.expect("run render okf");
assert!(
out.status.success(),
"render failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let mut by_kind: std::collections::BTreeMap<String, Vec<(String, Option<String>)>> =
std::collections::BTreeMap::new();
for (path, text) in bundle_files(&dir.join("bundle")) {
let fm = frontmatter_of(&text);
let Some((_, kind)) = fm.iter().find(|(k, _)| k == "type") else {
continue;
};
let status = fm
.iter()
.find(|(k, _)| k == "status")
.map(|(_, v)| v.clone());
by_kind
.entry(kind.clone())
.or_default()
.push((path, status));
}
for kind in ["adr", "adr_section", "file", "marker"] {
assert!(
by_kind.contains_key(kind),
"the fixture must emit a `{kind}` concept: {:?}",
by_kind.keys().collect::<Vec<_>>()
);
}
for kind in ["adr", "adr_section"] {
for (path, status) in &by_kind[kind] {
assert_eq!(
status.as_deref(),
Some("deprecated"),
"{path} is the decision (or part of it) and must carry its status"
);
}
}
for kind in ["file", "marker"] {
for (path, status) in &by_kind[kind] {
assert_eq!(
status.as_deref(),
None,
"{path} is a `{kind}`, not the decision — it must claim no lifecycle \
of the decision's"
);
}
}
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn a_commit_whose_parent_cannot_be_read_attributes_nobody() {
const BASE: &str = "2020-01-02T03:04:05+00:00";
const SIDE: &str = "2021-02-03T04:05:06+00:00";
const MERGE: &str = "2022-03-04T05:06:07+00:00";
let dir = fresh_dir("okf-unreadable-parent");
git(&dir, &["init", "-q"]);
write(&dir, "src/lib.rs", "pub struct Thing;\n");
git(&dir, &["add", "."]);
commit_as(&dir, "Ada", BASE, "base");
git(&dir, &["checkout", "-q", "-b", "feature"]);
write(
&dir,
"docs/adr/0001-a.md",
"---\nadr-id: \"0001\"\nstatus: Accepted\n---\n\n# ADR-0001: A\n\n## Context\n\nProse.\n",
);
git(&dir, &["add", "."]);
commit_as(&dir, "Bob", SIDE, "the adr");
let side = String::from_utf8(
Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(&dir)
.output()
.expect("rev-parse")
.stdout,
)
.expect("utf-8")
.trim()
.to_owned();
git(&dir, &["checkout", "-q", "main"]);
git_as(
&dir,
"Mallory",
MERGE,
&["merge", "-q", "--no-ff", "-m", "merge", "feature"],
);
let control = Command::new(BIN)
.args(["render", "okf", "--out", "control"])
.current_dir(&dir)
.output()
.expect("run render okf");
assert!(
control.status.success(),
"the control render must succeed: {}",
String::from_utf8_lossy(&control.stderr)
);
assert!(
bundle_files(&dir.join("control"))
.iter()
.any(|(_, t)| t.contains("by: \"human:Bob\"")),
"the intact fixture must attribute the ADR to Bob, or the damage below \
proves nothing"
);
let object = dir.join(".git/objects").join(&side[..2]).join(&side[2..]);
assert!(object.is_file(), "expected a loose object at {object:?}");
std::fs::remove_file(&object).expect("remove the side commit");
let out = Command::new(BIN)
.args(["render", "okf", "--out", "bundle"])
.current_dir(&dir)
.output()
.expect("run render okf");
if out.status.success() {
let named: Vec<String> = bundle_files(&dir.join("bundle"))
.into_iter()
.filter(|(_, t)| t.contains("human:Mallory"))
.map(|(p, _)| p)
.collect();
assert!(
named.is_empty(),
"the merge's author must not inherit the confirmation of a branch whose \
history cannot be read: {named:?}"
);
}
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn a_cross_repo_link_resolves_into_the_other_member() {
let (base, home) = workspace_fixture("okf-xrepo");
let app = base.join("app");
let deploy = base.join("deploy");
for member in [&app, &deploy] {
assert!(
roteiro_in(member, &home, &["sync"]).status.success(),
"sync {member:?}"
);
}
let linked = roteiro_in(
&app,
&home,
&["links", "--workspace-name", "prod", "--write"],
);
assert!(
linked.status.success(),
"links --write failed: {}{}",
String::from_utf8_lossy(&linked.stdout),
String::from_utf8_lossy(&linked.stderr)
);
let out = roteiro_in(
&app,
&home,
&[
"render",
"okf",
"--workspace-name",
"prod",
"--out",
"bundle",
],
);
assert!(
out.status.success(),
"workspace render failed: {}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let files = bundle_files(&app.join("bundle"));
let emitted: std::collections::BTreeSet<&str> = files.iter().map(|(p, _)| p.as_str()).collect();
assert!(
emitted
.iter()
.any(|p| p.starts_with("deploy/") && p.contains("extref-app-")),
"the fixture must produce a cross-repo placeholder in `deploy`: {emitted:?}"
);
let referrer = "deploy/symbols/cfgkey-prod-env-serve-addr.md";
let (_, text) = files
.iter()
.find(|(p, _)| p == referrer)
.unwrap_or_else(|| panic!("no {referrer} in {emitted:?}"));
let mut targets: Vec<String> = Vec::new();
let mut rest = text.as_str();
while let Some(open) = rest.find("](/") {
rest = &rest[open + 3..];
let Some(close) = rest.find(')') else { break };
targets.push(rest[..close].to_owned());
rest = &rest[close..];
}
assert!(
!targets.is_empty(),
"{referrer} must emit relationship links:\n{text}"
);
for target in &targets {
assert!(
emitted.contains(target.as_str()),
"{referrer} links to /{target}, which the bundle does not contain: \
{emitted:?}"
);
}
assert!(
targets.iter().any(|t| t.starts_with("app/")),
"the cross-repo reference must land in `app`, not on `deploy`'s own \
placeholder: {targets:?}"
);
std::fs::remove_dir_all(&base).ok();
}
#[test]
fn a_slug_that_names_a_directory_is_served_from_it() {
let dir = fresh_dir("nestedslug");
git(&dir, &["init", "-q"]);
write(&dir, "website/public/index.html", "<h1>Home</h1>\n");
write(&dir, "website/public/style.css", "body{}\n");
write(
&dir,
"website/pages/modes.md",
"---\nsite-page: modes\nsite-nav: Modes\nsite-order: 1\n---\n\n# Modes\n",
);
write(
&dir,
"docs/history/BUILD_PLAN_V2.md",
"---\nsite-page: history/build-plan-v2\nsite-nav: Roadmap\nsite-order: 3\n---\n\n\
# Roadmap\n",
);
write(
&dir,
"docs/adr/0001-example.md",
"---\nadr-id: \"0001\"\nstatus: Accepted\n---\n\n# ADR-0001: Example\n\n\
Sequenced in [V2](../history/BUILD_PLAN_V2.md).\n",
);
git(&dir, &["add", "."]);
git(&dir, &["commit", "-q", "-m", "init"]);
let out = Command::new(BIN)
.args(["render", "docs", "--out", "site"])
.current_dir(&dir)
.output()
.expect("run render");
assert!(out.status.success(), "render failed: {out:?}");
let site = dir.join("site");
let page = std::fs::read_to_string(site.join("history/build-plan-v2.html"))
.expect("page under history/");
assert!(
!site.join("build-plan-v2.html").exists(),
"and not also at the root"
);
assert!(
page.contains("href=\"../style.css\""),
"theme resolves from where the page sits: {page}"
);
assert!(
page.contains("href=\"../modes.html\""),
"nav entries are root-relative and must climb: {page}"
);
let adr = std::fs::read_to_string(site.join("adr/0001-example.html")).expect("adr page");
assert!(
adr.contains("href=\"../history/build-plan-v2.html\""),
"the ADR link resolves to the served path: {adr}"
);
}