mod common;
use common::TempDir;
use std::process::Command;
fn okf() -> Command {
Command::new(env!("CARGO_BIN_EXE_okf"))
}
#[test]
fn cli_validate_and_lint_show_fixable_count() {
let tmp = TempDir::new();
tmp.write("index.md", "---\nokf_version: \"0.2\"\n---\n\n# Test\n");
tmp.write(
"metrics/revenue.md",
"---\ntype: Metric\n---\nRevenue is money.\n",
);
let output = okf()
.args(["lint", tmp.path().to_str().unwrap()])
.output()
.unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("fixable with `--fix`"), "Output: {stdout}");
let val_output = okf()
.args(["validate", tmp.path().to_str().unwrap()])
.output()
.unwrap();
let val_stdout = String::from_utf8(val_output.stdout).unwrap();
assert!(
val_stdout.contains("fixable with `--fix`"),
"Output: {val_stdout}"
);
}
#[test]
fn validate_fix_only_fixes_validation_issues_not_lint() {
let tmp = TempDir::new();
tmp.write("index.md", "---\nokf_version: \"0.2\"\n---\n\n# Test\n");
let original_body = "Revenue is money.\n";
tmp.write(
"metrics/revenue.md",
&format!("---\ntype: Metric\n---\n{original_body}"),
);
let output = okf()
.args(["validate", tmp.path().to_str().unwrap(), "--fix"])
.output()
.unwrap();
assert!(output.status.success());
let content = tmp.read("metrics/revenue.md");
assert!(content.contains("title: Revenue"));
assert!(content.contains("generated:"));
assert!(content.contains(original_body));
assert!(!content.contains("# Revenue\n\nRevenue is money."));
let lint_output = okf()
.args(["lint", tmp.path().to_str().unwrap(), "--fix"])
.output()
.unwrap();
let lint_stdout = String::from_utf8(lint_output.stdout).unwrap();
assert!(lint_stdout.contains("Applied"));
let content_after_lint = tmp.read("metrics/revenue.md");
assert!(content_after_lint.contains("# Revenue\n\nRevenue is money."));
}
#[test]
fn cli_lint_with_fix_flag_remediates_and_rechecks() {
let tmp = TempDir::new();
tmp.write(
"overview.md",
"---\ntype: Concept\ntitle: Overview\ndescription: Overview of the bundle.\ngenerated:\n by: human:alice\n at: 2026-01-01T00:00:00Z\n---\n\n# Overview\n\nSee [revenue](metrics/revenue.md).\n",
);
tmp.write(
"metrics/revenue.md",
"---\ntype: Metric\ntitle: Revenue\ndescription: Revenue tracking.\ntimestamp: 2026-01-01T00:00:00Z\n---\n\nRevenue is money.\n",
);
tmp.write(
"index.md",
"---\nokf_version: \"0.2\"\n---\n\n# Concepts\n\n* [Overview](overview.md)\n* [Revenue](metrics/revenue.md)\n",
);
let output = okf()
.args(["lint", tmp.path().to_str().unwrap(), "--fix"])
.output()
.unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("Applied"), "Output: {stdout}");
let content = tmp.read("metrics/revenue.md");
assert!(!content.contains("timestamp:"));
assert!(content.contains("generated:"));
assert!(content.contains("# Revenue"));
}
#[test]
fn cli_validate_with_fix_flag_remediates_and_validates() {
let tmp = TempDir::new();
tmp.write("index.md", "---\nokf_version: \"0.2\"\n---\n\n# Test\n");
tmp.write(
"metrics/revenue.md",
"---\ntype: Metric\ntimestamp: 2026-01-01T00:00:00Z\n---\n\n# Revenue\n\nRevenue is money.\n",
);
let output = okf()
.args(["validate", tmp.path().to_str().unwrap(), "--fix"])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("Applied"), "Output: {stdout}");
assert!(
stdout.contains("conformant with OKF v0.2"),
"Output: {stdout}"
);
}
#[test]
fn cli_validate_with_fix_normalizes_date_formats() {
let tmp = TempDir::new();
tmp.write(
"index.md",
"---\nokf_version: \"0.2\"\n---\n\n# Test\n\n* [Revenue](metrics/revenue.md)\n",
);
tmp.write(
"metrics/revenue.md",
"---\ntype: Metric\ntitle: Revenue\ndescription: Revenue tracking.\ngenerated:\n by: human:alice\n at: '2026-06-30'\nverified:\n - by: human:bob\n at: '2026-07-01 12:00:00'\nstale_after: '2026-12-31'\nusage_window:\n from: '2026-01-01'\n to: '2026-06-01'\nsources:\n - id: '1'\n resource: https://example.com\n last_modified: '2026-05-15'\n---\n\n# Revenue\n\nRevenue is money.\n",
);
let val_before = okf()
.args(["validate", tmp.path().to_str().unwrap(), "--json"])
.output()
.unwrap();
let val_json_before: serde_json::Value = serde_json::from_slice(&val_before.stdout).unwrap();
assert!(
val_json_before["diagnostics"]
.as_array()
.unwrap()
.iter()
.any(|d| d["fixable"].as_bool() == Some(true)),
"Expected fixable date diagnostics"
);
let fix_output = okf()
.args(["validate", tmp.path().to_str().unwrap(), "--fix", "--json"])
.output()
.unwrap();
assert!(fix_output.status.success());
let val_after = okf()
.args(["validate", tmp.path().to_str().unwrap(), "--json"])
.output()
.unwrap();
let val_json_after: serde_json::Value = serde_json::from_slice(&val_after.stdout).unwrap();
assert_eq!(
val_json_after["error_count"].as_i64(),
Some(0),
"Expected 0 errors after fix"
);
assert_eq!(
val_json_after["warning_count"].as_i64(),
Some(0),
"Expected 0 warnings after fix"
);
assert_eq!(
val_json_after["conformant"].as_bool(),
Some(true),
"Expected conformant after fix"
);
let content = tmp.read("metrics/revenue.md");
assert!(content.contains("2026-06-30T00:00:00Z"));
assert!(content.contains("2026-07-01T12:00:00Z"));
assert!(content.contains("2026-12-31T00:00:00Z"));
assert!(content.contains("2026-01-01T00:00:00Z"));
assert!(content.contains("2026-06-01T00:00:00Z"));
assert!(content.contains("2026-05-15T00:00:00Z"));
}