257 lines
9.8 KiB
Rust
257 lines
9.8 KiB
Rust
//! Engineering oracles for the distributable current-profile lesson.
|
|
#[path = "../../../examples/release1/model.rs"]
|
|
mod teaching;
|
|
use oa_core::{projection, source_project, transitions::*, validation, *};
|
|
use teaching::id;
|
|
|
|
#[test]
|
|
fn release1_teaching_regeneration_and_shipped_source_are_exact_with_one_intentional_warning() {
|
|
let first = teaching::build();
|
|
let second = teaching::build();
|
|
assert_eq!(
|
|
first.project, second.project,
|
|
"fresh planner UUIDs must not churn the teaching source"
|
|
);
|
|
assert_eq!(first.before_transition, second.before_transition);
|
|
assert_eq!(first.request, second.request);
|
|
let bundle = teaching::checked_bundle(&first.project);
|
|
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../examples/release1");
|
|
for (name, bytes) in bundle.files {
|
|
assert_eq!(
|
|
std::fs::read_to_string(root.join(&name)).unwrap(),
|
|
bytes,
|
|
"regenerate {name}"
|
|
);
|
|
}
|
|
assert_eq!(
|
|
persistence::decode(&std::fs::read(root.join("model.json")).unwrap()).unwrap(),
|
|
first.project
|
|
);
|
|
for (path, expected) in [
|
|
(
|
|
"transition-before.json",
|
|
serde_json::to_value(&first.before_transition).unwrap(),
|
|
),
|
|
(
|
|
"transition-request.json",
|
|
serde_json::to_value(&first.request).unwrap(),
|
|
),
|
|
(
|
|
"expected-diagnostics.json",
|
|
serde_json::to_value(validation::validate(&first.project)).unwrap(),
|
|
),
|
|
] {
|
|
let shipped: serde_json::Value =
|
|
serde_json::from_slice(&std::fs::read(root.join(path)).unwrap()).unwrap();
|
|
assert_eq!(shipped, expected, "regenerate {path}");
|
|
}
|
|
let issues = validation::validate(&first.project);
|
|
assert_eq!(issues.len(), 1, "{issues:?}");
|
|
assert_eq!(issues[0].severity, validation::Severity::Warning);
|
|
assert_eq!(issues[0].code, "work_without_behavior_context");
|
|
assert_eq!(issues[0].element, Some(id("sa_audit_thresholds")));
|
|
assert!(
|
|
first
|
|
.project
|
|
.elements
|
|
.values()
|
|
.filter(|e| e.layer() == Layer::Oa)
|
|
.all(|e| e.kind() != Kind::Port)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn release1_teaching_preserves_explicit_transport_direction_reuse_and_view_identity() {
|
|
let lesson = teaching::build();
|
|
let p = &lesson.project;
|
|
for prefix in ["oa", "sa"] {
|
|
assert!(
|
|
matches!(p.elements[&id(&format!("{prefix}_assess_step"))].shared(), Some(SharedBody::Occurrence { body: OccurrenceBody::SequenceReference { sequence: Some(target) }, .. }) if *target == id(&format!("{prefix}_assessment_procedure")))
|
|
);
|
|
assert!(
|
|
matches!(p.elements[&id(&format!("{prefix}_assessment_action"))].shared(), Some(SharedBody::Occurrence { body: OccurrenceBody::Action { action: Some(target), .. }, .. }) if *target == id(&format!("{prefix}_assess")))
|
|
);
|
|
assert!(
|
|
p.relations
|
|
.values()
|
|
.any(|relation| relation.kind() == RelationKind::ChainInvolvement
|
|
&& relation.source() == id(&format!("{prefix}_assessment_procedure"))
|
|
&& relation.target() == id(&format!("{prefix}_triage")))
|
|
);
|
|
assert!(
|
|
matches!(p.elements[&id(&format!("{prefix}_conversation"))].shared(), Some(SharedBody::Scenario { context: Some(context), .. }) if *context == id(&format!("{prefix}_restore")))
|
|
);
|
|
assert!(p.relations.values().any(|relation| relation.kind()
|
|
== RelationKind::CapabilityInvolvement
|
|
&& relation.source() == id(&format!("{prefix}_assess"))
|
|
&& relation.target() == id(&format!("{prefix}_triage"))));
|
|
let call = &p.elements[&id(&format!("{prefix}_call"))];
|
|
let reply = &p.elements[&id(&format!("{prefix}_reply"))];
|
|
let body = |e: &Element| {
|
|
if let Some(SharedBody::Occurrence { body, .. }) = e.shared() {
|
|
body.clone()
|
|
} else {
|
|
panic!()
|
|
}
|
|
};
|
|
let validation::MessageEndpointResolution::Resolved { source, target } =
|
|
validation::resolve_message_endpoints(p, &body(call))
|
|
else {
|
|
panic!("call not resolved")
|
|
};
|
|
assert_eq!(
|
|
validation::resolve_message_endpoints(p, &body(reply)),
|
|
validation::MessageEndpointResolution::Resolved {
|
|
source: target,
|
|
target: source
|
|
}
|
|
);
|
|
assert!(
|
|
matches!(p.elements[&id(&format!("{prefix}_use_field"))].shared(), Some(SharedBody::Occurrence { body: OccurrenceBody::SequenceReference { sequence: Some(target) }, .. }) if *target == id(&format!("{prefix}_field_procedure")))
|
|
);
|
|
assert!(
|
|
matches!(p.elements[&id(&format!("{prefix}_use_conversation"))].shared(), Some(SharedBody::Occurrence { body: OccurrenceBody::ScenarioUse { scenario: Some(target), bindings }, .. }) if *target == id(&format!("{prefix}_conversation")) && bindings.len() == 2)
|
|
);
|
|
}
|
|
let full = projection::project_view(p, id("sa_allocation_view"));
|
|
let focused = projection::project_view(p, id("sa_service_view"));
|
|
let edges: std::collections::BTreeSet<_> = full.edges.iter().map(|e| e.id).collect();
|
|
assert_eq!(
|
|
edges,
|
|
[
|
|
id("sa_contact"),
|
|
id("sa_repair_request"),
|
|
id("sa_repair_report")
|
|
]
|
|
.into()
|
|
);
|
|
assert_eq!(focused.edges.len(), 1);
|
|
assert_eq!(focused.edges[0].id, id("sa_contact"));
|
|
assert!(
|
|
p.diagrams[&id("sa_service_view")]
|
|
.view_options
|
|
.exchange_labels
|
|
.label(p, focused.edges[0].id)
|
|
.contains("Valve repair")
|
|
);
|
|
let ports = |view: &projection::ProjectedView| {
|
|
view.ports
|
|
.iter()
|
|
.map(|v| (v.element, v.position))
|
|
.collect::<std::collections::BTreeMap<_, _>>()
|
|
};
|
|
assert_eq!(
|
|
ports(&full),
|
|
ports(&focused),
|
|
"filtering must not move the shared technical port geometry"
|
|
);
|
|
assert!(
|
|
p.traces
|
|
.values()
|
|
.any(|t| t.oa == id("oa_contact") && t.sa == id("sa_contact"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn release1_teaching_rejects_guessed_ports_and_wrong_performer_without_changing_source() {
|
|
let lesson = teaching::build();
|
|
let mut missing_decisions = lesson.request.clone();
|
|
missing_decisions.communication_endpoints.clear();
|
|
let plan = plan_transition(&lesson.before_transition, &missing_decisions).unwrap();
|
|
assert!(
|
|
!plan.conflicts.is_empty(),
|
|
"an operational channel does not determine technical ports"
|
|
);
|
|
let compiled = source_project::compile_bundle(
|
|
&teaching::checked_bundle(&lesson.project),
|
|
source_project::ProjectLimits::default(),
|
|
)
|
|
.unwrap();
|
|
let mut wrong = lesson.project.relations[&id("oa_repair_request")].clone();
|
|
wrong.body.set_endpoints(id("oa_assess"), id("oa_reset"));
|
|
assert!(
|
|
execute_batch(
|
|
&lesson.project,
|
|
&[Command::EditRelation(wrong.id, wrong.clone())]
|
|
)
|
|
.is_err()
|
|
);
|
|
let mut candidate = lesson.project.clone();
|
|
candidate.relations.insert(wrong.id, wrong);
|
|
assert!(
|
|
validation::validate(&candidate)
|
|
.iter()
|
|
.any(|i| i.severity == validation::Severity::Error
|
|
&& i.code == "exchange_allocation_performer")
|
|
);
|
|
assert!(
|
|
source_project::update_bundle(
|
|
&compiled,
|
|
&candidate,
|
|
source_project::ProjectLimits::default()
|
|
)
|
|
.is_err()
|
|
);
|
|
assert_eq!(compiled.decoded.project, lesson.project);
|
|
}
|
|
|
|
#[test]
|
|
fn release1_teaching_review_changes_only_the_selected_counterpart_and_preserves_modules() {
|
|
let lesson = teaching::build();
|
|
let mut changed = lesson.project.clone();
|
|
changed.elements.get_mut(&id("oa_confirm")).unwrap().name = "Confirm stable pressure".into();
|
|
let compiled = source_project::compile_bundle(
|
|
&teaching::checked_bundle(&changed),
|
|
source_project::ProjectLimits::default(),
|
|
)
|
|
.unwrap();
|
|
let mut transition =
|
|
TransitionRequest::new(TransitionDirection::OaToSa, vec![id("oa_confirm")]);
|
|
transition.fields.clear();
|
|
transition.mappings.push(TransitionMapping {
|
|
source: id("oa_confirm"),
|
|
target: Some(id("sa_confirm")),
|
|
target_kind: None,
|
|
});
|
|
let mut request = SemanticTransitionRequest::new(transition);
|
|
request.reviews.push(SemanticReviewRequest {
|
|
source: id("oa_confirm"),
|
|
resolutions: vec![FacetResolution {
|
|
facet: FacetSelector::Header(HeaderFacet::Name),
|
|
decision: SemanticDecision::TakeSource,
|
|
}],
|
|
reset: vec![],
|
|
});
|
|
let plan = plan_semantic_transition(&changed, request).unwrap();
|
|
assert!(plan.conflicts.is_empty(), "{:?}", plan.conflicts);
|
|
let accepted = apply_semantic_transition(&changed, &plan).unwrap();
|
|
assert_eq!(
|
|
accepted.elements[&id("sa_confirm")].name,
|
|
"Confirm stable pressure"
|
|
);
|
|
assert_eq!(
|
|
accepted.elements[&id("sa_control")].name,
|
|
lesson.project.elements[&id("sa_control")].name
|
|
);
|
|
let updated = source_project::update_bundle(
|
|
&compiled,
|
|
&accepted,
|
|
source_project::ProjectLimits::default(),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(updated.decoded.project, accepted);
|
|
assert_eq!(
|
|
updated.bundle.files.keys().collect::<Vec<_>>(),
|
|
compiled.bundle.files.keys().collect::<Vec<_>>()
|
|
);
|
|
for (path, original) in &compiled.bundle.files {
|
|
if path != "sa/functions/function_sa_restore_work.arcadia" && path != "traceability.arcadia"
|
|
{
|
|
assert_eq!(
|
|
&updated.bundle.files[path], original,
|
|
"unrelated module changed: {path}"
|
|
);
|
|
}
|
|
}
|
|
}
|