Verify projection API and preserve same-object explicit navigation

This commit is contained in:
2026-09-24 23:00:05 -07:00
parent 0341bca846
commit 603a59c178
4 changed files with 182 additions and 8 deletions
@@ -372,3 +372,150 @@ async fn source6_http_explicit_communication_ports_preview_apply_and_restart_pre
assert_pair(&reopened);
server.stop().await;
}
#[tokio::test]
async fn mixed_traceability_projection_roundtrip_preserves_dormant_http_state() {
let f = fixture();
let mut model = compile_bundle(&f.bundle, ProjectLimits::default())
.unwrap()
.decoded
.project;
let view = model.diagrams.get_mut(&f.diagram).unwrap();
view.layer = Layer::Sa;
view.projection = ProjectionKind::Traceability;
// The OA placements and category are intentionally opposite the view's layer.
view.view_options.hidden_edges = vec![f.mean];
for (index, element) in f.sa.into_iter().enumerate() {
model.place(
f.diagram,
element,
Position {
x: index as f32 * 400.,
y: 300.,
},
);
}
let mut bundle = f.bundle.clone();
bundle.files.insert(
bundle.entry.clone(),
format!(
"// Authored source-6 engineering rationale\n{}",
oa_core::language::format(&model).replacen(
"{\n",
"{\n include \"notes.arcadia\";\n",
1
)
),
);
let directory = tempfile::tempdir().unwrap();
let server = TestServer::start(directory.path(), None).await;
let original = import(&server, &bundle).await;
assert_pair(&original);
let resource = original.resource_id;
let mixed = oa_core::projection::project_view(&original.model, f.diagram);
assert!(
f.oa.iter()
.chain(&f.sa)
.all(|id| mixed.nodes.iter().any(|n| n.element == *id))
);
let mut ordinary = original.model.diagrams[&f.diagram].clone();
ordinary.projection = ProjectionKind::Allocation;
let response = transact(
&server,
&original,
vec![Command::EditDiagram(f.diagram, ordinary.clone())],
)
.await;
let status = response.status();
let etag = response
.headers()
.get("etag")
.map(|v| v.to_str().unwrap().to_owned());
let result: Value = response.json().await.unwrap();
assert_eq!(status, StatusCode::OK, "{result}");
assert_eq!(result["changed"], true);
assert_eq!(result["dry_run"], false);
assert_eq!(result["revision"], original.revision + 1);
let changed = server.get(resource).await;
assert_eq!(etag.as_deref(), Some(changed.etag().as_str()));
let mut expected = original.model.clone();
expected.diagrams.insert(f.diagram, ordinary.clone());
assert_eq!(
changed.model, expected,
"only the projection changes; dormant state is retained"
);
assert_pair(&changed);
assert_eq!(
changed.source_project.files["notes.arcadia"],
bundle.files["notes.arcadia"]
);
assert!(
changed.source_project.files[&bundle.entry]
.starts_with("// Authored source-6 engineering rationale\n")
);
let projected = oa_core::projection::project_view(&changed.model, f.diagram);
assert!(projected.nodes.iter().all(|n| !f.oa.contains(&n.element)));
assert!(
f.sa.iter()
.all(|id| projected.nodes.iter().any(|n| n.element == *id))
);
// A dormant option still must name an existing category. Rejection must not
// advance the ETag or rewrite either side of the persisted model/source pair.
let disk = directory.path().join(format!("{resource}.json"));
let accepted_bytes = std::fs::read(&disk).unwrap();
ordinary.view_options.participant_categories = CategorySelection::Any(vec![Id::new_v4()]);
let rejected = transact(
&server,
&changed,
vec![Command::EditDiagram(f.diagram, ordinary)],
)
.await;
assert_eq!(rejected.status(), StatusCode::UNPROCESSABLE_ENTITY);
let retained = server.get(resource).await;
assert_eq!(retained.etag(), changed.etag());
assert_eq!(
serde_json::to_value(&retained).unwrap(),
serde_json::to_value(&changed).unwrap()
);
assert_eq!(std::fs::read(&disk).unwrap(), accepted_bytes);
server.stop().await;
let server = TestServer::start(directory.path(), None).await;
let reopened = server.get(resource).await;
assert_eq!(
serde_json::to_value(&reopened).unwrap(),
serde_json::to_value(&changed).unwrap()
);
assert_pair(&reopened);
let response = transact(
&server,
&reopened,
vec![Command::EditDiagram(
f.diagram,
original.model.diagrams[&f.diagram].clone(),
)],
)
.await;
let status = response.status();
let result: Value = response.json().await.unwrap();
assert_eq!(status, StatusCode::OK, "{result}");
assert_eq!(result["changed"], true);
assert_eq!(result["revision"], changed.revision + 1);
let restored = server.get(resource).await;
assert_eq!(restored.model, original.model);
assert_pair(&restored);
assert_eq!(
restored.source_project.files["notes.arcadia"],
bundle.files["notes.arcadia"]
);
let projected = oa_core::projection::project_view(&restored.model, f.diagram);
assert!(
f.oa.iter()
.chain(&f.sa)
.all(|id| projected.nodes.iter().any(|n| n.element == *id))
);
assert!(!projected.edges.iter().any(|edge| edge.id == f.mean));
server.stop().await;
}