use crate::draft::CommitSession;
use crate::CadIr;
use cadmpeg_core::decode::{DecodeArena, DecodeContext, DecodePolicy};
#[test]
fn owned_commit_session_preserves_cross_candidate_references_and_rejections() {
let ctx = cadmpeg_test_support::service_decode_context();
let mut session = CommitSession::new(CadIr::empty(), &ctx, None).unwrap();
let point = "test:model:point#committed";
let vertex = "test:model:vertex#committed";
session
.commit_model(super::point_draft(point))
.unwrap()
.unwrap();
assert!(session.contains(point).unwrap());
assert!(session
.commit_model(super::point_draft(point))
.unwrap()
.is_err());
session
.commit_model(super::vertex_draft(vertex, point))
.unwrap()
.unwrap();
assert!(session.contains(point).unwrap());
assert!(session.contains(vertex).unwrap());
let ir = session.into_parts().0;
assert_eq!(ir.model.points.len(), 1);
assert_eq!(ir.model.vertices.len(), 1);
assert_eq!(ir.model.vertices[0].point.as_str(), point);
ctx.finish_session().unwrap();
}
#[test]
fn owned_commit_session_invalidates_positions_before_document_mutation() {
let first = "test:model:point#first";
let second = "test:model:point#second";
let mut ir = CadIr::empty();
ir.model.points.push(super::point(first));
ir.model.points.push(super::point(second));
let ctx = cadmpeg_test_support::service_decode_context();
let mut session = CommitSession::new(ir, &ctx, None).unwrap();
assert!(session.contains(first).unwrap());
assert!(session.contains(second).unwrap());
session.document_mut().unwrap().model.points.swap(0, 1);
assert!(session.contains(first).unwrap());
assert!(session.contains(second).unwrap());
session.document_mut().unwrap().model.points.remove(0);
assert!(session.contains(first).unwrap());
assert!(!session.contains(second).unwrap());
let ir = session.into_parts().0;
assert_eq!(ir.model.points.len(), 1);
assert_eq!(ir.model.points[0].id.as_str(), first);
ctx.finish_session().unwrap();
}
#[test]
fn staged_unknown_cache_moves_source_facts_and_extends_only_new_slots() {
let first_id = format!("test:source:unknown#{}", "a".repeat(16384));
let second_id = format!("test:source:unknown#{}", "b".repeat(16384));
let image = vec![7; 65536];
let image_pointer = image.as_ptr();
let first = crate::unknown::UnknownRecord::retained(
first_id.as_str().try_into().unwrap(),
17,
image,
Vec::new(),
);
let second = crate::unknown::UnknownRecord::unavailable(
second_id.as_str().try_into().unwrap(),
29,
31,
"source-digest",
Vec::new(),
);
let arena = DecodeArena::new();
let mut policy = DecodePolicy::service();
policy.limits.max_retained_bytes = 4096;
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
let mut session = CommitSession::new(CadIr::empty(), &ctx, None).unwrap();
session.push_unknown(first).unwrap();
assert!(session.contains(&first_id).unwrap());
session.push_unknown(second).unwrap();
assert!(session.contains(&first_id).unwrap());
assert!(session.contains(&second_id).unwrap());
session
.unknown_links_mut(0)
.unwrap()
.unwrap()
.1
.push("test:model:point#later".into());
assert!(session.contains(&first_id).unwrap());
assert!(session.contains(&second_id).unwrap());
let (ir, records) = session.into_parts();
assert!(ir.model.points.is_empty());
assert_eq!(records.len(), 2);
assert_eq!(records[0].id().as_str(), first_id);
assert_eq!(records[0].offset(), 17);
assert_eq!(records[0].data().unwrap().as_ptr(), image_pointer);
assert_eq!(records[0].links(), ["test:model:point#later"]);
assert_eq!(records[1].id().as_str(), second_id);
assert_eq!(records[1].offset(), 29);
ctx.finish_session().unwrap();
}
#[test]
fn staged_unknown_cache_resolves_draft_references_and_rejects_identity_collisions() {
let target = "test:source:unknown#native";
let ctx = cadmpeg_test_support::service_decode_context();
let mut session = CommitSession::new(CadIr::empty(), &ctx, None).unwrap();
session
.push_unknown(crate::unknown::UnknownRecord::retained(
target.try_into().unwrap(),
0,
Vec::new(),
Vec::new(),
))
.unwrap();
assert!(session.contains(target).unwrap());
assert!(session
.commit_model(super::point_draft(target))
.unwrap()
.is_err());
session
.commit_model(super::vertex_draft("test:model:vertex#first", target))
.unwrap()
.unwrap();
session
.commit_model(super::vertex_draft("test:model:vertex#second", target))
.unwrap()
.unwrap();
let (ir, records) = session.into_parts();
assert_eq!(ir.model.vertices.len(), 2);
assert!(ir.model.points.is_empty());
assert_eq!(records.len(), 1);
assert_eq!(ir.model.vertices[0].point.as_str(), target);
assert_eq!(ir.model.vertices[1].point.as_str(), target);
ctx.finish_session().unwrap();
}
#[test]
fn owned_source_session_replaces_native_unknown_identity_floor() {
let ctx = cadmpeg_test_support::service_decode_context();
let mut ir = CadIr::empty();
let old = "test:source:unknown#old";
let other = "test:source:unknown#other";
for (format, id) in [("rhino", old), ("other", other)] {
ir.native.namespace_mut(format).arenas_mut().insert(
"unknowns".into(),
vec![crate::NativeRecord::new(id.try_into().unwrap(), serde_json::Map::new()).unwrap()],
);
}
let mut session = CommitSession::new(ir, &ctx, Some("rhino")).unwrap();
let staged = "test:source:unknown#staged";
session
.push_unknown(crate::UnknownRecord::retained(
staged.try_into().unwrap(),
0,
vec![1],
Vec::new(),
))
.unwrap();
assert!(!session.contains(old).unwrap());
assert!(session.contains(other).unwrap());
assert!(session.contains(staged).unwrap());
assert!(session
.commit_model(super::vertex_draft("test:model:vertex#old", old))
.unwrap()
.is_err());
session
.commit_model(super::vertex_draft("test:model:vertex#staged", staged))
.unwrap()
.unwrap();
assert_eq!(session.document().model.vertices.len(), 1);
assert_eq!(
session
.document()
.native
.namespace("rhino")
.unwrap()
.arenas()["unknowns"][0]
.id(),
old
);
}
#[test]
fn owned_source_session_clears_cached_source_positions() {
let ctx = cadmpeg_test_support::service_decode_context();
let mut session = CommitSession::new(CadIr::empty(), &ctx, Some("test")).unwrap();
let first = "test:source:unknown#first";
let second = "test:source:unknown#second";
session
.push_unknown(crate::UnknownRecord::retained(
first.try_into().unwrap(),
0,
vec![1],
Vec::new(),
))
.unwrap();
assert!(session.contains(first).unwrap());
session.replace_unknowns(Vec::new()).unwrap();
session
.push_unknown(crate::UnknownRecord::retained(
second.try_into().unwrap(),
1,
vec![2],
Vec::new(),
))
.unwrap();
assert!(!session.contains(first).unwrap());
assert!(session.contains(second).unwrap());
assert_eq!(session.unknown_links_mut(0).unwrap().unwrap().0, second);
assert_eq!(session.unknowns()[0].data().unwrap(), &[2]);
}
#[test]
fn speculative_session_append_reuses_source_cache_across_candidates() {
let arena = DecodeArena::new();
let policy = DecodePolicy::service();
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
let mut session = CommitSession::new(CadIr::empty(), &ctx, Some("test")).unwrap();
let source = "test:source:unknown#owner";
session
.push_unknown(crate::UnknownRecord::retained(
source.try_into().unwrap(),
0,
vec![7; 65536],
Vec::new(),
))
.unwrap();
let source_pointer = session.unknowns()[0].data().unwrap().as_ptr();
assert!(session.contains(source).unwrap());
for identity in ["test:model:point#first", "test:model:point#second"] {
let model = crate::document::Model {
points: vec![super::point(identity)],
..Default::default()
};
session
.try_append(
model,
crate::native::Native::default(),
|combined, unknowns| {
assert_eq!(unknowns[0].data().unwrap().as_ptr(), source_pointer);
assert_eq!(combined.model.points.last().unwrap().id.as_str(), identity);
Ok(Ok::<_, ()>(()))
},
)
.unwrap()
.unwrap();
assert!(session.contains(source).unwrap());
assert!(session.contains(identity).unwrap());
}
assert!(session.contains("test:model:point#first").unwrap());
assert_eq!(session.document().model.points.len(), 2);
let (_, unknowns) = session.into_parts();
assert_eq!(unknowns[0].data().unwrap().as_ptr(), source_pointer);
ctx.finish_session().unwrap();
}
#[test]
fn speculative_session_append_extends_native_cache_and_preserves_rejected_positions() {
let ctx = cadmpeg_test_support::service_decode_context();
let mut session = CommitSession::new(CadIr::empty(), &ctx, None).unwrap();
for (format, arena, identity) in [
("middle", "middle", "test:native:record#first"),
("middle", "middle", "test:native:record#second"),
("alpha", "alpha", "test:native:record#third"),
] {
let mut native = crate::native::Native::default();
native.namespace_mut(format).arenas_mut().insert(
arena.into(),
vec![
crate::NativeRecord::new(identity.try_into().unwrap(), serde_json::Map::new())
.unwrap(),
],
);
session
.try_append(crate::document::Model::default(), native, |_, _| {
Ok(Ok::<_, ()>(()))
})
.unwrap()
.unwrap();
assert!(session.contains(identity).unwrap());
assert!(session.contains("test:native:record#first").unwrap());
}
let rejected = "test:model:point#rejected";
let before = session.document().clone();
let model = crate::document::Model {
points: vec![super::point(rejected)],
..Default::default()
};
assert_eq!(
session
.try_append(model, crate::native::Native::default(), |_, _| Ok(Err::<
(),
_,
>(
"candidate rejection"
)))
.unwrap(),
Err("candidate rejection")
);
assert_eq!(session.document(), &before);
assert!(!session.contains(rejected).unwrap());
assert!(session.contains("test:native:record#first").unwrap());
assert!(session.contains("test:native:record#second").unwrap());
}
#[test]
fn speculative_session_append_keeps_original_resource_refusal_and_model() {
use cadmpeg_core::CodecError;
let ctx = cadmpeg_test_support::service_decode_context();
let mut session = CommitSession::new(CadIr::empty(), &ctx, None).unwrap();
session
.commit_model(super::point_draft("test:model:point#first"))
.unwrap()
.unwrap();
let before = session.document().clone();
let model = crate::document::Model {
points: vec![super::point("test:model:point#refused")],
..Default::default()
};
let Err(CodecError::ResourceLimit(limit)) = session.try_append(
model,
crate::native::Native::default(),
|_, _| -> Result<Result<(), ()>, CodecError> {
let mut frames = Vec::new();
loop {
frames.push(ctx.enter_nested("test session callback refusal")?);
}
},
) else {
panic!("session callback refusal stays outer");
};
assert_eq!(limit.operation, "test session callback refusal");
assert_eq!(session.document(), &before);
assert!(session.contains("test:model:point#first").unwrap());
assert!(!session.contains("test:model:point#refused").unwrap());
assert_eq!(session.into_parts().0, before);
assert!(
matches!(ctx.finish_session(), Err(CodecError::ResourceLimit(sticky)) if sticky == limit)
);
}