Complete source 6 engineering workflows and preserve release validation evidence
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
//! 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}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user