use crate::assets::{Asset, AssetContent, AssetData};
use crate::document::{CadIr, Model};
use crate::features::{Feature, FeatureDefinition, FeatureOperation};
use crate::math::Point3;
use crate::native::{Native, NativeRecord};
use crate::topology::Point;
fn staged_document() -> CadIr {
let mut ir = CadIr::empty();
ir.model.points.push(Point::new(
"test:append:point#new".try_into().unwrap(),
crate::features::FinitePoint3::new(Point3::new(1.0, 2.0, 3.0))
.expect("a finite position is a point"),
None,
));
ir.model.assets.push(Asset {
id: "test:append:asset#new".try_into().unwrap(),
name: None,
media_type: None,
content: AssetContent::Embedded {
data: AssetData::new(vec![1, 2, 3]).unwrap(),
},
native_ref: None,
});
for (ordinal, key) in ["parent", "child"].into_iter().enumerate() {
ir.model.features.push(Feature {
id: format!("test:append:feature#{key}").try_into().unwrap(),
ordinal: cadmpeg_core::decode::u64_from_index(ordinal),
name: None,
suppressed: None,
dependencies: crate::features::DistinctMembers::default(),
source_properties: std::collections::BTreeMap::default(),
source_tag: None,
source_text: None,
source_content: crate::features::FeatureContent::default(),
evaluation: crate::features::FeatureEvaluation::from_definition(
FeatureDefinition::Operation(FeatureOperation::StoredGeometry {}),
),
native_ref: None,
});
}
ir.model
.set_feature_regeneration_parent(
&("test:append:feature#child".try_into().unwrap()),
&("test:append:feature#parent".try_into().unwrap()),
)
.unwrap();
for (format, arena, key) in [
("test", "existing", "appended"),
("test", "new", "new-arena"),
("other", "new", "new-namespace"),
] {
ir.native.namespace_mut(format).arenas_mut().insert(
arena.into(),
vec![NativeRecord::new(
crate::ids::Identity::new(format!("test:append:record#{key}"))
.expect("valid identity"),
serde_json::Map::new(),
)
.unwrap()],
);
}
CadIr::from_json(&ir.to_canonical_json().unwrap()).unwrap()
}
#[test]
fn rejected_append_restores_neutral_native_and_parent_state() {
let mut ir = crate::examples::unit_cube().unwrap();
ir.native.namespace_mut("test").arenas_mut().insert(
"existing".into(),
vec![NativeRecord::new(
crate::ids::Identity::new("test:append:record#original").expect("valid identity"),
serde_json::Map::new(),
)
.unwrap()],
);
ir.native.namespace_mut("empty");
let before = ir.clone();
let staged = staged_document();
let result = ir.try_append(staged.model, staged.native, |combined| {
assert_eq!(combined.model.points.len(), before.model.points.len() + 1);
assert_eq!(combined.model.assets.len(), 1);
assert!(combined
.model
.feature_regeneration_parent(&"test:append:feature#child".try_into().unwrap())
.is_some());
assert_eq!(
combined.native.namespace("test").unwrap().arenas()["existing"].len(),
2
);
assert!(combined.native.namespace("other").is_some());
Ok(Err::<(), _>("independent rejection"))
});
assert_eq!(result.unwrap(), Err("independent rejection"));
assert_eq!(ir, before);
}
#[test]
fn accepted_append_preserves_all_staged_records_and_parent_relations() {
let mut ir = CadIr::empty();
let staged = staged_document();
ir.try_append(staged.model.clone(), staged.native.clone(), |_| {
Ok(Ok::<_, ()>(()))
})
.unwrap()
.unwrap();
assert_eq!(ir.model, staged.model);
assert_eq!(ir.native, staged.native);
assert_eq!(
CadIr::from_json(&ir.to_canonical_json().unwrap()).unwrap(),
ir
);
}
#[test]
fn actual_admission_rejects_duplicate_identity_without_changing_the_document() {
let mut ir = crate::examples::unit_cube().unwrap();
let before = ir.clone();
let model = Model {
points: vec![ir.model.points[0].clone()],
..Model::default()
};
let result = ir.try_append(model, Native::default(), |combined| {
let report = crate::admit(
&cadmpeg_test_support::service_decode_context(),
combined,
crate::DRAFT_CORE_CHECKS,
Vec::new(),
)
.expect("admit the model");
if report.is_ok() {
Ok(Ok(()))
} else {
Ok(Err(report))
}
});
assert!(result.unwrap().is_err());
assert_eq!(ir, before);
}
#[test]
fn resource_refused_append_restores_neutral_native_and_parent_state() {
let mut ir = crate::examples::unit_cube().unwrap();
let before = ir.clone();
let staged = staged_document();
let arena = cadmpeg_core::decode::DecodeArena::new();
let mut policy = cadmpeg_core::decode::DecodePolicy::service();
policy.limits.max_recursion_depth = 0;
let (ctx, _) =
cadmpeg_core::decode::DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
let result = ir.try_append(staged.model, staged.native, |_| {
let _depth = ctx.enter_nested("test append callback refusal")?;
Ok(Ok::<(), ()>(()))
});
let Err(cadmpeg_core::CodecError::ResourceLimit(limit)) = result else {
panic!("callback resource refusal must stay outer");
};
assert_eq!(limit.operation, "test append callback refusal");
assert_eq!(ir, before);
assert!(
matches!(ctx.finish_session(), Err(cadmpeg_core::CodecError::ResourceLimit(sticky)) if sticky == limit)
);
}