use std::fs;
use tempfile::TempDir;
use zenith_cli::commands::render::to_png_with_dir;
use zenith_cli::config::CliPolicyFlags;
const DOC_WITH_UNUSED_TOKEN: &str = r##"zenith version=1 {
project id="proj.rp" name="Render Policy"
tokens format="zenith-token-v1" {
token id="color.bg" type="color" value="#ffffff"
token id="color.unused" type="color" value="#abcdef"
}
styles {}
document id="doc.rp" title="Render Policy" {
page id="page.rp" w=(px)100 h=(px)100 {
rect id="rect.bg" x=(px)0 y=(px)0 w=(px)100 h=(px)100 fill=(token)"color.bg"
}
}
}
"##;
const DOC_WITH_UNAVAILABLE_FONT: &str = r##"zenith version=1 {
project id="proj.rf" name="Render Font"
tokens format="zenith-token-v1" {
token id="font.missing" type="fontFamily" value="Totally Missing Family"
}
styles {}
document id="doc.rf" title="Render Font" {
page id="page.rf" w=(px)200 h=(px)100 {
text id="text.rf" x=(px)0 y=(px)0 w=(px)200 h=(px)100 font-family=(token)"font.missing" {
span "hello"
}
}
}
}
"##;
#[test]
fn render_succeeds_without_flags() {
let result = to_png_with_dir(
DOC_WITH_UNUSED_TOKEN,
None,
1,
false,
&CliPolicyFlags::default(),
None,
);
assert!(
result.is_ok(),
"render must succeed with no flags; got: {:?}",
result.err().map(|e| e.message)
);
}
#[test]
fn deny_flag_turns_advisory_into_render_failure() {
let flags = CliPolicyFlags {
deny: vec!["token.unused".to_owned()],
..Default::default()
};
let result = to_png_with_dir(DOC_WITH_UNUSED_TOKEN, None, 1, false, &flags, None);
assert!(
result.is_err(),
"render must fail when advisory is --deny'd; got Ok"
);
let err = result.unwrap_err();
assert_eq!(
err.exit_code, 1,
"validation error must produce exit code 1; got {}",
err.exit_code
);
assert!(
err.message.contains("token.unused"),
"error message must mention the denied code; got: {}",
err.message
);
}
#[test]
fn local_config_deny_blocks_render() {
let tmp = TempDir::new().expect("tempdir");
fs::write(
tmp.path().join(".zenith.kdl"),
b"diagnostics {\n deny \"token.unused\"\n}\n",
)
.expect("write .zenith.kdl");
let doc_path = tmp.path().join("test.zen");
fs::write(&doc_path, DOC_WITH_UNUSED_TOKEN.as_bytes()).expect("write doc");
let result = to_png_with_dir(
DOC_WITH_UNUSED_TOKEN,
Some(tmp.path()),
1,
false,
&CliPolicyFlags::default(),
None,
);
assert!(
result.is_err(),
"render must fail when local config denies the advisory; got Ok"
);
let err = result.unwrap_err();
assert_eq!(
err.exit_code, 1,
"config-driven elevation must produce exit code 1; got {}",
err.exit_code
);
}
#[test]
fn local_config_allow_keeps_render_clean() {
let tmp = TempDir::new().expect("tempdir");
fs::write(
tmp.path().join(".zenith.kdl"),
b"diagnostics {\n allow \"token.unused\"\n}\n",
)
.expect("write .zenith.kdl");
let result = to_png_with_dir(
DOC_WITH_UNUSED_TOKEN,
Some(tmp.path()),
1,
false,
&CliPolicyFlags::default(),
None,
);
assert!(
result.is_ok(),
"render must succeed when local config allows the advisory; got: {:?}",
result.err().map(|e| e.message)
);
}
#[test]
fn allow_flag_on_advisory_is_transparent() {
let flags = CliPolicyFlags {
allow: vec!["token.unused".to_owned()],
..Default::default()
};
let result = to_png_with_dir(DOC_WITH_UNUSED_TOKEN, None, 1, false, &flags, None);
assert!(
result.is_ok(),
"render must still succeed when an advisory code is --allow'd; got: {:?}",
result.err().map(|e| e.message)
);
let png_with_flags = result.unwrap().png;
let png_no_flags = to_png_with_dir(
DOC_WITH_UNUSED_TOKEN,
None,
1,
false,
&CliPolicyFlags::default(),
None,
)
.expect("baseline render must succeed")
.png;
assert_eq!(
png_with_flags, png_no_flags,
"--allow on a non-Error advisory must produce byte-identical output to no flags"
);
}
#[test]
fn unresolved_font_advisory_shown_and_render_succeeds() {
let result = to_png_with_dir(
DOC_WITH_UNAVAILABLE_FONT,
None,
1,
false,
&CliPolicyFlags::default(),
None,
);
let artifact = result.expect("render must succeed with the font advisory present");
assert!(
artifact
.diagnostics
.iter()
.any(|d| d.code == "font.unresolved"),
"font.unresolved advisory must be surfaced on the artifact; got: {:?}",
artifact.diagnostics
);
}
#[test]
fn deny_unresolved_font_elevates_to_error_severity() {
let flags = CliPolicyFlags {
deny: vec!["font.unresolved".to_owned()],
..Default::default()
};
let artifact = to_png_with_dir(DOC_WITH_UNAVAILABLE_FONT, None, 1, false, &flags, None)
.expect("entry must return Ok; dispatch decides the exit code");
let diag = artifact
.diagnostics
.iter()
.find(|d| d.code == "font.unresolved")
.expect("font.unresolved must be present in the artifact diagnostics");
assert_eq!(
diag.severity,
zenith_core::Severity::Error,
"--deny must elevate font.unresolved to Error severity; got {:?}",
diag.severity
);
}
#[test]
fn allow_unresolved_font_suppresses_advisory() {
let flags = CliPolicyFlags {
allow: vec!["font.unresolved".to_owned()],
..Default::default()
};
let result = to_png_with_dir(DOC_WITH_UNAVAILABLE_FONT, None, 1, false, &flags, None);
let artifact = result.expect("render must succeed when the advisory is allowed");
assert!(
!artifact
.diagnostics
.iter()
.any(|d| d.code == "font.unresolved"),
"font.unresolved must be suppressed by --allow; got: {:?}",
artifact.diagnostics
);
}
#[test]
fn malformed_local_config_causes_render_error_exit_2() {
let tmp = TempDir::new().expect("tempdir");
fs::write(tmp.path().join(".zenith.kdl"), b"diagnostics {{{ bad kdl")
.expect("write bad config");
let result = to_png_with_dir(
DOC_WITH_UNUSED_TOKEN,
Some(tmp.path()),
1,
false,
&CliPolicyFlags::default(),
None,
);
assert!(
result.is_err(),
"render must fail when local config is malformed; got Ok"
);
let err = result.unwrap_err();
assert_eq!(
err.exit_code, 2,
"malformed config must produce exit code 2; got {}",
err.exit_code
);
assert!(
err.message.contains("config.error"),
"error message must mention config.error; got: {}",
err.message
);
}