297 lines
9.3 KiB
Rust
297 lines
9.3 KiB
Rust
use oa_core::*;
|
|
use std::collections::{BTreeMap, BTreeSet};
|
|
|
|
fn pair() -> (Project, Id, Id) {
|
|
let mut project = Project::empty();
|
|
let first = project
|
|
.add_element(Kind::Activity, Layer::Oa, "First", None)
|
|
.unwrap();
|
|
let second = project
|
|
.add_element(Kind::Activity, Layer::Oa, "Second", None)
|
|
.unwrap();
|
|
(project, first, second)
|
|
}
|
|
|
|
fn identities(project: &Project) -> BTreeSet<Id> {
|
|
project.identities().into_iter().map(|(id, _)| id).collect()
|
|
}
|
|
|
|
fn valid_map(original: &Project, prepared: &PreparedCommand) {
|
|
let origins = identities(original);
|
|
let present = identities(&prepared.project);
|
|
let targets: BTreeSet<_> = prepared.identity_map.values().copied().collect();
|
|
assert_eq!(
|
|
targets.len(),
|
|
prepared.identity_map.len(),
|
|
"correspondence is injective"
|
|
);
|
|
assert!(targets.is_subset(&present));
|
|
assert!(prepared.identity_map.keys().all(|id| origins.contains(id)));
|
|
}
|
|
|
|
#[test]
|
|
fn explicit_chains_and_nested_swaps_compose_original_identity() {
|
|
let (project, first, second) = pair();
|
|
let temporary = Id::new_v4();
|
|
let final_id = Id::new_v4();
|
|
let chained = prepare_command(
|
|
&project,
|
|
&Command::Batch(vec![
|
|
Command::Reidentify(first, temporary),
|
|
Command::Batch(vec![Command::Reidentify(temporary, final_id)]),
|
|
]),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
chained.identity_map,
|
|
BTreeMap::from([
|
|
(project.id, project.id),
|
|
(first, final_id),
|
|
(second, second)
|
|
])
|
|
);
|
|
assert_eq!(chained.selected, Some(final_id));
|
|
valid_map(&project, &chained);
|
|
|
|
let swapped = prepare_command(
|
|
&project,
|
|
&Command::Batch(vec![
|
|
Command::Reidentify(first, temporary),
|
|
Command::Batch(vec![
|
|
Command::Reidentify(second, first),
|
|
Command::Reidentify(temporary, second),
|
|
]),
|
|
]),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
swapped.identity_map,
|
|
BTreeMap::from([(project.id, project.id), (first, second), (second, first)])
|
|
);
|
|
assert_eq!(swapped.project.elements[&second].alias, "first");
|
|
assert_eq!(swapped.project.elements[&first].alias, "second");
|
|
valid_map(&project, &swapped);
|
|
}
|
|
|
|
#[test]
|
|
fn all_edit_commands_record_their_implicit_identity_change() {
|
|
let project = Project::demo();
|
|
let new = Id::new_v4();
|
|
let mut element = project.elements.values().next().unwrap().clone();
|
|
let element_id = element.id;
|
|
element.id = new;
|
|
element.alias = "edited_identity_and_alias".into();
|
|
let mut relation = project.relations.values().next().unwrap().clone();
|
|
let relation_id = relation.id;
|
|
relation.id = new;
|
|
let mut diagram = project.diagrams.values().next().unwrap().clone();
|
|
let diagram_id = diagram.id;
|
|
let mut node = diagram.nodes.values().next().unwrap().clone();
|
|
let node_id = node.id;
|
|
node.id = new;
|
|
diagram.id = new;
|
|
let mut artifact = project.artifact_links.values().next().unwrap().clone();
|
|
let artifact_id = artifact.id;
|
|
artifact.id = new;
|
|
let mut trace = project.traces.values().next().unwrap().clone();
|
|
let trace_id = trace.id;
|
|
trace.id = new;
|
|
let cases = [
|
|
(element_id, Command::EditElement(element_id, element)),
|
|
(relation_id, Command::EditRelation(relation_id, relation)),
|
|
(diagram_id, Command::EditDiagram(diagram_id, diagram)),
|
|
(
|
|
node_id,
|
|
Command::EditNode {
|
|
diagram: diagram_id,
|
|
original: node_id,
|
|
node,
|
|
},
|
|
),
|
|
(artifact_id, Command::EditArtifact(artifact_id, artifact)),
|
|
(trace_id, Command::EditTrace(trace_id, trace)),
|
|
(
|
|
project.id,
|
|
Command::EditProject {
|
|
id: new,
|
|
alias: project.alias.clone(),
|
|
name: project.name.clone(),
|
|
description: project.description.clone(),
|
|
},
|
|
),
|
|
];
|
|
for (old, command) in cases {
|
|
let prepared = prepare_command(&project, &command).unwrap();
|
|
let expected: BTreeMap<_, _> = identities(&project)
|
|
.into_iter()
|
|
.map(|id| (id, if id == old { new } else { id }))
|
|
.collect();
|
|
assert_eq!(prepared.identity_map, expected, "{command:?}");
|
|
valid_map(&project, &prepared);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn deletion_and_cascade_discard_origins_before_uuid_reuse() {
|
|
let (mut project, first, second) = pair();
|
|
let child = project
|
|
.add_element(Kind::Activity, Layer::Oa, "Nested work", Some(first))
|
|
.unwrap();
|
|
let mut fresh = project.elements[&child].clone();
|
|
fresh.parent = None;
|
|
fresh.alias = "replacement_work".into();
|
|
let prepared = prepare_command(
|
|
&project,
|
|
&Command::Batch(vec![Command::Delete(first), Command::CreateElement(fresh)]),
|
|
)
|
|
.unwrap();
|
|
assert!(prepared.project.contains(child));
|
|
assert_eq!(
|
|
prepared.identity_map,
|
|
BTreeMap::from([(project.id, project.id), (second, second)])
|
|
);
|
|
valid_map(&project, &prepared);
|
|
}
|
|
|
|
#[test]
|
|
fn embedded_diagram_replacement_cannot_revive_deleted_node_origin() {
|
|
let mut project = Project::empty();
|
|
let element = project.add(Kind::Activity, "Work", 0., 0.);
|
|
let mut diagram = project.diagrams.values().next().unwrap().clone();
|
|
let node = diagram.nodes.values().next().unwrap().clone();
|
|
diagram.nodes.clear();
|
|
let prepared = prepare_command(
|
|
&project,
|
|
&Command::Batch(vec![
|
|
Command::EditDiagram(diagram.id, diagram.clone()),
|
|
Command::CreateNode {
|
|
diagram: diagram.id,
|
|
node: node.clone(),
|
|
},
|
|
]),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(prepared.project, project);
|
|
assert_eq!(
|
|
prepared.identity_map,
|
|
BTreeMap::from([
|
|
(project.id, project.id),
|
|
(element, element),
|
|
(diagram.id, diagram.id)
|
|
])
|
|
);
|
|
assert!(!prepared.identity_map.contains_key(&node.id));
|
|
valid_map(&project, &prepared);
|
|
}
|
|
|
|
#[test]
|
|
fn fresh_creation_has_no_origin_even_when_subsequently_reidentified() {
|
|
let (project, _, _) = pair();
|
|
let mut fresh = project
|
|
.make_element(Kind::Activity, Layer::Oa, "New work", None)
|
|
.unwrap();
|
|
let created = fresh.id;
|
|
let renamed = Id::new_v4();
|
|
fresh.alias = "fresh_identity".into();
|
|
let prepared = prepare_command(
|
|
&project,
|
|
&Command::Batch(vec![
|
|
Command::CreateElement(fresh),
|
|
Command::Reidentify(created, renamed),
|
|
]),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(prepared.selected, Some(renamed));
|
|
assert!(prepared.project.contains(renamed));
|
|
assert_eq!(
|
|
prepared.identity_map,
|
|
identities(&project)
|
|
.into_iter()
|
|
.map(|id| (id, id))
|
|
.collect()
|
|
);
|
|
valid_map(&project, &prepared);
|
|
let generated = prepare_command(
|
|
&project,
|
|
&Command::AddElement {
|
|
kind: Kind::Activity,
|
|
layer: Layer::Oa,
|
|
name: "Generated once".into(),
|
|
parent: None,
|
|
},
|
|
)
|
|
.unwrap();
|
|
let selected = generated.selected.unwrap();
|
|
assert!(generated.project.contains(selected));
|
|
assert!(!generated.identity_map.values().any(|id| *id == selected));
|
|
assert_eq!(generated.project.elements.len(), project.elements.len() + 1);
|
|
}
|
|
|
|
#[test]
|
|
fn replacement_resets_correspondence_against_the_original_input() {
|
|
let (project, first, second) = pair();
|
|
let temporary = Id::new_v4();
|
|
let mut replacement = project.clone();
|
|
replacement.elements.remove(&second);
|
|
let new = replacement
|
|
.add_element(Kind::Activity, Layer::Oa, "Replacement addition", None)
|
|
.unwrap();
|
|
let final_id = Id::new_v4();
|
|
let prepared = prepare_command(
|
|
&project,
|
|
&Command::Batch(vec![
|
|
Command::Reidentify(first, temporary),
|
|
Command::Delete(second),
|
|
Command::Replace(replacement),
|
|
Command::Reidentify(first, final_id),
|
|
]),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
prepared.identity_map,
|
|
BTreeMap::from([(project.id, project.id), (first, final_id)])
|
|
);
|
|
assert!(!prepared.identity_map.values().any(|id| *id == new));
|
|
valid_map(&project, &prepared);
|
|
let restored = prepare_command(
|
|
&project,
|
|
&Command::Batch(vec![
|
|
Command::Delete(first),
|
|
Command::Replace(project.clone()),
|
|
]),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
restored.identity_map,
|
|
identities(&project)
|
|
.into_iter()
|
|
.map(|id| (id, id))
|
|
.collect()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn failed_preparation_leaves_input_unchanged_after_intermediate_identity_work() {
|
|
let (project, first, second) = pair();
|
|
let before = project.clone();
|
|
let temporary = Id::new_v4();
|
|
let failed = prepare_command(
|
|
&project,
|
|
&Command::Batch(vec![
|
|
Command::Reidentify(first, temporary),
|
|
Command::Reidentify(second, temporary),
|
|
]),
|
|
);
|
|
assert!(failed.is_err());
|
|
assert_eq!(project, before);
|
|
let failed_validation = prepare_command(
|
|
&project,
|
|
&Command::Batch(vec![
|
|
Command::Reidentify(first, temporary),
|
|
Command::SetAlias(temporary, "second".into()),
|
|
]),
|
|
);
|
|
assert!(failed_validation.is_err());
|
|
assert_eq!(project, before);
|
|
}
|