use weavatrix_refactor_plan::{RefactorPlanLimits, parse_refactor_plan};
const PREFIX: &str = r#"{"schemaVersion":"weavatrix.refactor-plan.v1","operation":"create","operations":[{"kind":"create","value":{"path":"a.rs","contents":"x"}}]"#;
#[test]
fn bounded_parser_accepts_a_valid_plan() {
let source = format!("{PREFIX}}}");
let plan = parse_refactor_plan(source.as_bytes(), RefactorPlanLimits::default()).unwrap();
assert_eq!(plan.operation, "create");
}
#[test]
fn duplicate_names_fail_at_top_operation_evidence_and_extension_depths() {
let cases = [
r#"{"schemaVersion":"weavatrix.refactor-plan.v1","schemaVersion":"weavatrix.refactor-plan.v1","operation":"x","operations":[{"kind":"create","value":{"path":"a.rs","contents":"x"}}]}"#,
r#"{"schemaVersion":"weavatrix.refactor-plan.v1","operation":"x","operations":[{"kind":"create","value":{"path":"a.rs","path":"b.rs","contents":"x"}}]}"#,
r#"{"schemaVersion":"weavatrix.refactor-plan.v1","operation":"x","operations":[{"kind":"create","value":{"path":"a.rs","contents":"x"}}],"completenessProof":{"scope":{"kind":"REPOSITORY","value":"root"},"planner":{"name":"p","name":"q","version":"1","backend":"b"}}}"#,
r#"{"schemaVersion":"weavatrix.refactor-plan.v1","operation":"x","operations":[{"kind":"create","value":{"path":"a.rs","contents":"x","future":{"same":1,"same":2}}}]}"#,
];
for source in cases {
let error =
parse_refactor_plan(source.as_bytes(), RefactorPlanLimits::default()).unwrap_err();
assert!(error.message().contains("duplicate JSON object member"));
}
}
#[test]
fn raw_unsafe_numbers_are_rejected_after_duplicate_safe_parse() {
for number in ["-0.0", "9007199254740992", "9007199254740993"] {
let source = format!("{PREFIX},\"futureNumber\":{number}}}");
assert!(parse_refactor_plan(source.as_bytes(), RefactorPlanLimits::default()).is_err());
}
}
#[test]
fn malformed_recognized_evidence_is_not_demoted_to_an_extension() {
let source = format!("{PREFIX},\"warnings\":\"not-an-array\"}}");
assert!(parse_refactor_plan(source.as_bytes(), RefactorPlanLimits::default()).is_err());
}
#[test]
fn non_extensible_operation_wrappers_and_permissions_reject_unknown_fields() {
for source in [
r#"{"schemaVersion":"weavatrix.refactor-plan.v1","operation":"x","operations":[{"kind":"create","value":{"path":"a.rs","contents":"x"},"ignored":true}]}"#,
r#"{"schemaVersion":"weavatrix.refactor-plan.v1","operation":"x","operations":[{"kind":"create","value":{"path":"a.rs","contents":"x","permissions":{"executable":false,"ignored":true}}}]}"#,
] {
assert!(parse_refactor_plan(source.as_bytes(), RefactorPlanLimits::default()).is_err());
}
}
#[test]
fn serialized_input_limit_is_checked_before_json_allocation() {
let limits = RefactorPlanLimits {
max_total_create_bytes: 1,
max_create_bytes_per_file: 1,
max_total_text_bytes: 1,
max_extension_bytes: 1,
max_evidence_text_bytes: 1,
max_paths: 1,
max_path_bytes: 1,
..RefactorPlanLimits::default()
};
let oversized = vec![b' '; 1024 * 1024 + 7];
assert!(parse_refactor_plan(&oversized, limits).is_err());
}
#[test]
fn json_member_name_limit_accepts_4096_and_rejects_4097_bytes() {
for (length, accepted) in [(4096, true), (4097, false)] {
let key = "k".repeat(length);
let source = format!("{PREFIX},\"{key}\":null}}");
assert_eq!(
parse_refactor_plan(source.as_bytes(), RefactorPlanLimits::default()).is_ok(),
accepted
);
}
}
#[test]
fn duplicate_huge_key_error_does_not_echo_attacker_input() {
let key = "attacker".repeat(6_000);
let source = format!("{PREFIX},\"{key}\":1,\"{key}\":2}}");
let limits = RefactorPlanLimits {
max_path_bytes: key.len(),
..RefactorPlanLimits::default()
};
let error = parse_refactor_plan(source.as_bytes(), limits).unwrap_err();
assert!(error.message().contains("duplicate JSON object member"));
assert!(error.message().len() < 256);
}