#![allow(missing_docs)]
use noyalib::policy::{
DenyAnchors, DenyTags, MaxScalarLength, Policy, PolicyEvent, PolicyEventKind,
};
use noyalib::{ParserConfig, Value, from_str_with_config};
#[test]
fn deny_anchors_rejects_anchor_definition() {
let cfg = ParserConfig::new().with_policy(DenyAnchors);
let res: Result<Value, _> = from_str_with_config("a: &x 1\n", &cfg);
assert!(res.is_err());
let msg = format!("{}", res.err().unwrap());
assert!(msg.contains("DenyAnchors"), "got: {msg}");
assert!(msg.contains("&x"), "should name the anchor: {msg}");
}
#[test]
fn deny_anchors_rejects_alias_dereference() {
let cfg = ParserConfig::new().with_policy(DenyAnchors);
let res: Result<Value, _> = from_str_with_config("a: &x 1\nb: *x\n", &cfg);
assert!(res.is_err());
}
#[test]
fn deny_anchors_passes_anchor_free_input() {
let cfg = ParserConfig::new().with_policy(DenyAnchors);
let v: Value = from_str_with_config("a: 1\nb: 2\n", &cfg).unwrap();
assert!(matches!(v, Value::Mapping(_)));
}
#[test]
fn deny_tags_rejects_custom_tag() {
let cfg = ParserConfig::new().with_policy(DenyTags);
let res: Result<Value, _> = from_str_with_config("a: !Custom 1\n", &cfg);
assert!(res.is_err());
let msg = format!("{}", res.err().unwrap());
assert!(msg.contains("DenyTags"), "got: {msg}");
}
#[test]
fn deny_tags_allows_core_tags() {
let cfg = ParserConfig::new().with_policy(DenyTags);
let v: Value = from_str_with_config("a: !!str 42\n", &cfg).unwrap();
assert!(matches!(v, Value::Mapping(_)));
}
#[test]
fn max_scalar_length_rejects_oversize_scalars() {
let cfg = ParserConfig::new().with_policy(MaxScalarLength(8));
let res: Result<Value, _> = from_str_with_config("a: thisisreallylong\n", &cfg);
assert!(res.is_err());
}
#[test]
fn max_scalar_length_accepts_short_scalars() {
let cfg = ParserConfig::new().with_policy(MaxScalarLength(8));
let v: Value = from_str_with_config("a: short\n", &cfg).unwrap();
assert!(matches!(v, Value::Mapping(_)));
}
#[derive(Debug, Default)]
struct DenyKeyContains(&'static str);
impl Policy for DenyKeyContains {
fn check_event(&self, event: PolicyEvent<'_>) -> noyalib::Result<()> {
if event.kind == PolicyEventKind::Scalar {
if let Some(s) = event.scalar {
if s.contains(self.0) {
return Err(noyalib::Error::Deserialize(format!(
"policy DenyKeyContains: scalar contains forbidden substring `{}`",
self.0
)));
}
}
}
Ok(())
}
}
#[test]
fn custom_policy_fires() {
let cfg = ParserConfig::new().with_policy(DenyKeyContains("secret"));
let res: Result<Value, _> = from_str_with_config("password: secret123\n", &cfg);
assert!(res.is_err());
}
#[test]
fn multiple_policies_short_circuit_on_first_failure() {
let cfg = ParserConfig::new()
.with_policy(DenyAnchors)
.with_policy(DenyTags);
let res: Result<Value, _> = from_str_with_config("a: !Custom &x 1\n", &cfg);
assert!(res.is_err());
}
#[test]
fn multiple_policies_compose_when_all_accept() {
let cfg = ParserConfig::new()
.with_policy(DenyAnchors)
.with_policy(DenyTags)
.with_policy(MaxScalarLength(64));
let v: Value = from_str_with_config("a: ok\nb: 42\n", &cfg).unwrap();
assert!(matches!(v, Value::Mapping(_)));
}
#[test]
fn policies_route_through_ast_path_to_enforce_contract() {
#[derive(Debug, serde::Deserialize)]
struct Doc {
#[allow(dead_code)]
a: u32,
}
let cfg = ParserConfig::new().with_policy(DenyAnchors);
let res: Result<Doc, _> = from_str_with_config("a: &x 1\n", &cfg);
assert!(
res.is_err(),
"policy must fire even on inputs that would normally stream"
);
}