mod common;
use blazingly_json::Value;
use weavatrix_refactor_plan::{
Completeness, EditPlan, GraphRevision, PlanAnnotations, PlanErrorCode, RefactorOperation,
RefactorPlan, attach_annotations, extract_annotations,
};
#[test]
fn legacy_text_only_roundtrip_is_lossless() {
let mut legacy = EditPlan::new("rename", vec![common::file_edit("src/a.rs")]);
legacy.completeness = Some(Completeness::new(Completeness::PARTIAL));
let annotations = PlanAnnotations {
created_at: Some("2026-08-02T12:00:00Z".to_owned()),
graph_revision: GraphRevision::Null,
uncertain_references: Some(Vec::new()),
not_modified: Some(Vec::new()),
warnings: Some(Vec::new()),
extensions: [("vendor".to_owned(), Value::String("kept".to_owned()))]
.into_iter()
.collect(),
..PlanAnnotations::default()
};
legacy = attach_annotations(legacy, &annotations).unwrap();
let refactor = RefactorPlan::from_text_edit_plan(legacy.clone()).unwrap();
assert_eq!(extract_annotations(&legacy).unwrap(), refactor.evidence);
let restored = refactor.try_into_text_edit_plan().unwrap();
assert_eq!(restored, legacy);
}
#[test]
fn non_modify_operations_cannot_be_mislabeled_as_legacy_text_plans() {
let plan = RefactorPlan::new(
"create",
vec![RefactorOperation::Create(
weavatrix_refactor_plan::CreateFile::new("a.rs", "x"),
)],
);
let error = plan.try_into_text_edit_plan().unwrap_err();
assert_eq!(error.code(), PlanErrorCode::NotTextOnly);
assert_eq!(error.operation_index(), Some(0));
}
#[test]
fn annotation_attachment_rejects_nested_reserved_collisions() {
let mut annotations = common::strict_plan(Completeness::COMPLETE).evidence;
annotations
.completeness_proof
.as_mut()
.unwrap()
.scope
.extensions
.insert("kind".to_owned(), Value::String("shadow".to_owned()));
let error = attach_annotations(
EditPlan::new("rename", vec![common::file_edit("src/a.rs")]),
&annotations,
)
.unwrap_err();
assert_eq!(error.code(), PlanErrorCode::ExtensionConflict);
}