Files
ARCADIA-Studio/crates/oa-desktop/src/semantic_review/compose_diagnostic_tests.rs
T

303 lines
9.6 KiB
Rust

use super::*;
use serde_json::json;
fn fixture() -> (Project, RowAddress, Id, Id, KeyPair<Id>) {
let mut p = Project::empty();
let source = p
.add_element(Kind::Activity, Layer::Oa, "Inspect equipment", None)
.unwrap();
let target = p
.add_element(Kind::Function, Layer::Sa, "Monitor equipment", None)
.unwrap();
let other = p
.add_element(Kind::Function, Layer::Sa, "Report condition", None)
.unwrap();
let trace = super::super::tests::add_trace(&mut p, source, target, "Evidence rationale");
let mut ids = Vec::new();
for (owner, alias) in [
(source, "operational_evidence"),
(target, "system_evidence"),
] {
let id = Id::new_v4();
p.artifact_links.insert(
id,
ArtifactLink {
id,
alias: alias.into(),
name: "Evidence".into(),
description: String::new(),
owner,
relation: ArtifactRelation::Verifies,
uri: "requirements/interlock".into(),
revision: None,
fragment: None,
},
);
ids.push(id);
}
(
p,
RowAddress {
source,
target,
trace,
},
ids[1],
other,
KeyPair {
oa: Some(ids[0]),
sa: Some(ids[1]),
},
)
}
fn send(state: &mut State, p: &Project, event: RowEvent) {
let addressed = state.address_event(event);
state.update(p, addressed);
}
#[test]
fn same_review_presence_and_owner_conflict_names_both_facets_and_owners_and_retains_drafts() {
let (p, row, id, other, keys) = fixture();
let accepted = p.clone();
let mut state = State::new(&p, row.trace).unwrap();
for (layer, key) in [(Layer::Oa, keys.oa), (Layer::Sa, keys.sa)] {
send(
&mut state,
&p,
RowEvent::Artifact(artifacts::Event::Pick(
layer,
key.map(|id| view::choice(&p, id)),
)),
);
}
send(&mut state, &p, RowEvent::Artifact(artifacts::Event::Stage));
let presence = artifacts::selector(&keys, ArtifactFacet::Presence);
let owner = artifacts::selector(&keys, ArtifactFacet::Owner);
send(
&mut state,
&p,
RowEvent::Decide(owner.clone(), Some(SemanticDecision::Candidate)),
);
send(
&mut state,
&p,
RowEvent::Artifact(artifacts::Event::Input(
owner.clone(),
artifacts::Raw::Owner(other.to_string()),
)),
);
assert!(state.can_apply(), "{:?}", state.error);
send(
&mut state,
&p,
RowEvent::Decide(presence.clone(), Some(SemanticDecision::KeepTarget)),
);
assert!(!state.can_apply());
assert!(state.command(&p).is_err());
let error = state.error.as_deref().unwrap();
assert!(
error.starts_with("Decisions in this review disagree"),
"{error}"
);
for expected in [
"Artifact Presence",
"Artifact Owner",
"Monitor equipment",
"Report condition",
"Evidence rationale",
"@system_evidence",
"@operational_evidence",
"Both drafts are retained",
] {
assert!(error.contains(expected), "missing {expected}: {error}");
}
for misleading in ["Independent row", "Absent", "{\"Value\"", "exclude a row"] {
assert!(!error.contains(misleading), "{error}");
}
assert!(
!error.contains(&row.target.to_string()),
"named owner should not be raw UUID: {error}"
);
assert_eq!(state.decisions[&presence], SemanticDecision::KeepTarget);
assert_eq!(
state.artifacts.drafts[&owner],
artifacts::Raw::Owner(other.to_string())
);
send(&mut state, &p, RowEvent::Decide(presence, None));
let command = state.command(&p).unwrap();
let prepared = prepare_command(&p, &command).unwrap().project;
assert_eq!(prepared.artifact_links[&id].owner, other);
assert_eq!(p, accepted);
}
#[test]
fn distinct_review_conflict_keeps_trace_aliases_even_when_names_are_equal() {
let (mut p, first, id, other, keys) = fixture();
let trace =
super::super::tests::add_trace(&mut p, first.source, first.target, "Evidence rationale");
let second = RowAddress { trace, ..first };
let slot = artifacts::slot(id, ArtifactFacet::Owner);
let presence = artifacts::selector(&keys, ArtifactFacet::Presence);
let owner = artifacts::selector(&keys, ArtifactFacet::Owner);
let mut slots = BTreeMap::new();
let state = State::new(&p, first.trace).unwrap();
agree(
&state,
&mut slots,
slot.clone(),
json!({"Value": first.target}),
first,
&presence,
)
.unwrap();
let error = agree(
&state,
&mut slots,
slot,
json!({"Value": other}),
second,
&owner,
)
.unwrap_err();
assert!(
error.starts_with("Independent row proposals disagree"),
"{error}"
);
assert!(error.contains(&p.traces[&first.trace].alias), "{error}");
assert!(error.contains(&p.traces[&second.trace].alias), "{error}");
assert!(
error.contains("Artifact Presence") && error.contains("Artifact Owner"),
"{error}"
);
assert!(error.contains("exclude a row"), "{error}");
assert_eq!(slots.len(), 1);
assert_eq!(
slots.values().next().unwrap().value,
json!({"Value": first.target})
);
}
#[test]
fn reserved_creation_conflict_uses_its_rows_seed_instead_of_a_deleted_reference() {
for reverse in [false, true] {
let (p, row, _, _, existing) = fixture();
let accepted = p.clone();
let mut state = State::new(&p, row.trace).unwrap();
if reverse {
state.update(&p, Event::Direction(TransitionDirection::SaToOa));
}
let source_layer = if reverse { Layer::Sa } else { Layer::Oa };
let source = if reverse {
existing.sa.unwrap()
} else {
existing.oa.unwrap()
};
send(
&mut state,
&p,
RowEvent::Artifact(artifacts::Event::Pick(
source_layer,
Some(view::choice(&p, source)),
)),
);
send(
&mut state,
&p,
RowEvent::Artifact(artifacts::Event::Reserve),
);
send(&mut state, &p, RowEvent::Artifact(artifacts::Event::Stage));
let keys = state.artifacts.pair();
let destination = if reverse {
keys.oa.unwrap()
} else {
keys.sa.unwrap()
};
let seed = state.artifacts.seeds[&destination].clone();
let presence = artifacts::selector(&keys, ArtifactFacet::Presence);
let owner = artifacts::selector(&keys, ArtifactFacet::Owner);
send(
&mut state,
&p,
RowEvent::Decide(presence, Some(SemanticDecision::TakeSource)),
);
send(
&mut state,
&p,
RowEvent::Decide(owner, Some(SemanticDecision::KeepTarget)),
);
assert!(!state.can_apply());
assert!(state.command(&p).is_err());
let saved = state.recovery_snapshot().unwrap();
let restored = State::from_recovery(&p, saved.clone()).unwrap();
assert_eq!(restored.recovery_snapshot().unwrap(), saved);
for review in [&state, &restored] {
let error = review.error.as_deref().unwrap();
for expected in [
"Decisions in this review disagree",
"Artifact Presence",
"Artifact Owner",
"artifact absent",
"Evidence · reserved @",
] {
assert!(error.contains(expected), "missing {expected}: {error}");
}
assert!(error.contains(&seed.alias), "{error}");
for misleading in ["Missing identity", "Deleted reference", "{\"Value\""] {
assert!(!error.contains(misleading), "{error}");
}
let actual = review.active_address.unwrap();
assert!(review_identity(review, actual, destination).contains(&seed.alias));
// A resolver for a different row must not borrow the active row's seed.
let unrelated = RowAddress {
trace: Id::new_v4(),
..actual
};
assert_eq!(
review_identity(review, unrelated, destination),
format!("Missing identity {destination}")
);
}
assert_eq!(p, accepted);
assert!(!p.contains(destination));
}
}
#[test]
fn typed_owner_display_does_not_reinterpret_uuid_shaped_artifact_text() {
let (p, row, id, _, _) = fixture();
let value = json!({"Value": row.target.to_string()});
assert_eq!(
describe(&p, &artifacts::slot(id, ArtifactFacet::Uri), &value),
json!(row.target.to_string()).to_string()
);
assert!(
describe(&p, &artifacts::slot(id, ArtifactFacet::Owner), &value)
.contains("owner Monitor equipment")
);
assert_eq!(
describe(
&p,
&artifacts::slot(id, ArtifactFacet::Owner),
&json!("NotApplicable")
),
"artifact absent"
);
assert_eq!(
describe(
&p,
&artifacts::slot(id, ArtifactFacet::Revision),
&json!({"Value": null})
),
"null"
);
assert_eq!(
describe(
&p,
&artifacts::slot(id, ArtifactFacet::Revision),
&json!({"Value": ""})
),
"\"\""
);
}