//! 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::>() }; 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::>(), compiled.bundle.files.keys().collect::>() ); 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}" ); } } }