Files
ARCADIA-Studio/crates/oa-desktop/src/behavior/scenario_operation_tests.rs
T
Codex e2ecfea69b
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Checkpoint source 7 workflows and in-progress diagram authoring redesign
Preserve current core, source, REST, desktop, recovery, documentation and validation work, including AGENTS.md and the development summary. This is a work-in-progress checkpoint: the new compact palette has outstanding compilation errors and native acceptance; earlier passing gates retain their documented scope.
2026-09-10 10:06:55 -07:00

305 lines
9.9 KiB
Rust

use super::*;
use crate::document::Document;
use oa_core::source_project::ProjectLimits;
fn fixture() -> Project {
persistence::decode(include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../harness/fixtures/interface-operation-probe/output/boundary-only.json"
)))
.unwrap()
}
fn id(p: &Project, n: u32) -> Id {
let alias = format!("operation_probe_{n}");
p.elements
.values()
.find(|e| e.alias == alias)
.map(|e| e.id)
.or_else(|| {
p.relations
.values()
.find(|r| r.alias == alias)
.map(|r| r.id)
})
.unwrap()
}
fn choice(p: &Project, n: u32) -> Choice {
Choice {
id: id(p, n),
label: p.named(id(p, n)).to_owned(),
}
}
fn prepare(p: &Project) -> State {
let mut state = State::default();
for event in [
Event::Start(id(p, 40), Tool::Message),
Event::Style(MessageStyle::CallReply),
Event::Exchange(choice(p, 34)),
Event::Sender(choice(p, 41)),
Event::Receiver(choice(p, 42)),
Event::OperationIntent(true),
] {
state.update(p, event).unwrap();
}
state
}
#[test]
fn operation_gui_selects_two_parameterless_allocations_without_inventing_refinement_and_reply_derives_call()
{
let p = fixture();
let mut state = prepare(&p);
let choices = crate::data_values::operation_choices(
&p,
&state.draft.as_ref().unwrap().proposed_message(&p),
);
assert_eq!(
choices.iter().map(|c| c.id).collect::<BTreeSet<_>>(),
BTreeSet::from([id(&p, 30), id(&p, 31)])
);
for allocation in [30, 31] {
state
.update(&p, Event::Operation(choice(&p, allocation)))
.unwrap();
let command = state.prepare_pending(&p).unwrap();
let expected = execute_batch(&p, std::slice::from_ref(&command)).unwrap();
let added: Vec<_> = expected
.elements
.values()
.filter(|e| !p.contains(e.id))
.collect();
assert_eq!(added.len(), 2);
let call = added
.iter()
.find(|e| {
matches!(
e.shared(),
Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange {
message_kind: MessageKind::Call,
..
},
..
})
)
})
.unwrap();
let reply = added
.iter()
.find(|e| {
matches!(
e.shared(),
Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange {
message_kind: MessageKind::Reply,
..
},
..
})
)
})
.unwrap();
assert!(
matches!(call.shared(),Some(SharedBody::Occurrence {body:OccurrenceBody::Exchange {operation:Some(OperationUse {allocation:Some(selected)}),..},..}) if *selected==id(&p,allocation))
);
assert!(
matches!(reply.shared(),Some(SharedBody::Occurrence {body:OccurrenceBody::Exchange {operation:None,reply_to:Some(selected),..},..}) if *selected==call.id)
);
let label = crate::data_values::operation_label(&expected, reply).unwrap();
assert!(label.contains("Reply operation from Call"));
assert!(label.contains(if allocation == 30 {
"Reset"
} else {
"ReadStatus"
}));
assert_eq!(expected.relations, p.relations);
let mut doc = Document::from_project(p.clone(), ProjectLimits::default()).unwrap();
let before = doc.bundle().clone();
doc.execute(command).unwrap();
let after = doc.bundle().clone();
assert_eq!(doc.project(), &expected);
assert_eq!(
Document::from_bundle(after.clone(), ProjectLimits::default())
.unwrap()
.project(),
&expected
);
assert!(doc.undo());
assert_eq!(doc.bundle(), &before);
assert!(doc.redo());
assert_eq!(doc.bundle(), &after);
}
}
#[test]
fn operation_gui_unresolved_and_disabled_selection_recovery_preserves_rejected_raw_values() {
let p = fixture();
let mut state = prepare(&p);
state.update(&p, Event::Operation(choice(&p, 30))).unwrap();
state.update(&p, Event::OperationIntent(false)).unwrap();
let raw = state.recovery_snapshot().unwrap().unwrap();
let mut state = State::from_recovery(raw.clone()).unwrap();
assert_eq!(state.recovery_snapshot().unwrap(), Some(raw));
state.update(&p, Event::OperationIntent(true)).unwrap();
assert_eq!(
state
.draft
.as_ref()
.unwrap()
.operation
.as_ref()
.unwrap()
.allocation,
Some(id(&p, 30))
);
let key = crate::data_values::FieldPathKey {
path: vec![],
field: id(&p, 20),
};
state
.update(&p, Event::Value(key.clone(), " rejected raw && ".into()))
.unwrap();
state.update(&p, Event::ClearOperation).unwrap();
assert!(state.draft.as_ref().unwrap().signature(&p).is_err());
let raw = state.recovery_snapshot().unwrap().unwrap();
let mut restored = State::from_recovery(raw.clone()).unwrap();
assert_eq!(restored.recovery_snapshot().unwrap(), Some(raw.clone()));
assert!(restored.prepare_pending(&p).is_err());
assert_eq!(restored.recovery_snapshot().unwrap(), Some(raw));
restored
.update(&p, Event::Operation(choice(&p, 31)))
.unwrap();
assert_eq!(
restored.draft.as_ref().unwrap().values[&key],
" rejected raw && "
);
assert!(restored.prepare_pending(&p).is_err());
restored
.update(&p, Event::Value(key, String::new()))
.unwrap();
assert!(restored.prepare_pending(&p).is_ok());
}
#[test]
fn operation_gui_async_picker_uses_request_root_and_nested_directions_without_erasing_rejected_values()
{
let mut p = fixture();
let allocation = id(&p, 30);
if let RelationBody::InterfaceItemAllocation { transmission, .. } =
&mut p.relations.get_mut(&allocation).unwrap().body
{
*transmission = TransmissionProtocol::Asynchronous;
}
let record = p
.add_element(
Kind::DataType,
Layer::Sa,
"Operation parameter record",
None,
)
.unwrap();
*p.elements.get_mut(&record).unwrap().shared_mut().unwrap() = SharedBody::DataType {
definition: DataTypeDefinition::Record { supertype: None },
default: None,
unit: String::new(),
};
let leaf = p
.add_element(Kind::DataField, Layer::Sa, "Nested value", Some(record))
.unwrap();
let item = id(&p, 20);
let mut fields = vec![];
for (name, direction) in [
("Request input", ParameterDirection::In),
("Response output", ParameterDirection::Out),
("Return result", ParameterDirection::Return),
] {
let field = p
.add_element(Kind::DataField, Layer::Sa, name, Some(item))
.unwrap();
*p.elements.get_mut(&field).unwrap().shared_mut().unwrap() = SharedBody::DataField {
type_ref: TypeRef::Named(record),
direction,
minimum: 0,
maximum: Some(1),
unit: String::new(),
storage: FieldStorage::Value,
default: None,
};
fields.push(field);
}
validate_candidate(&p).unwrap();
let mut state = prepare(&p);
state
.update(&p, Event::Style(MessageStyle::Asynchronous))
.unwrap();
state.update(&p, Event::Operation(choice(&p, 30))).unwrap();
let signature = state
.draft
.as_ref()
.unwrap()
.signature(&p)
.unwrap()
.unwrap();
let choices = state
.draft
.as_ref()
.unwrap()
.browsed_values(&p, &signature)
.unwrap();
assert_eq!(
choices.iter().map(|c| c.key.field).collect::<Vec<_>>(),
vec![fields[0]]
);
state
.update(
&p,
Event::ValueBrowse(Some(crate::data_values::FieldPathKey {
path: vec![],
field: fields[0],
})),
)
.unwrap();
let choices = state
.draft
.as_ref()
.unwrap()
.browsed_values(&p, &signature)
.unwrap();
assert_eq!(choices.len(), 1);
assert_eq!(
choices[0].key,
crate::data_values::FieldPathKey {
path: vec![fields[0]],
field: leaf
}
);
let rejected = crate::data_values::FieldPathKey {
path: vec![fields[1]],
field: leaf,
};
state
.update(
&p,
Event::Value(rejected.clone(), " retained output expression ".into()),
)
.unwrap();
assert!(state.prepare_pending(&p).is_err());
let raw = state.recovery_snapshot().unwrap().unwrap();
let mut restored = State::from_recovery(raw.clone()).unwrap();
assert_eq!(restored.recovery_snapshot().unwrap(), Some(raw));
assert_eq!(
restored.draft.as_ref().unwrap().values[&rejected],
" retained output expression "
);
restored.update(&p, Event::OperationIntent(false)).unwrap();
restored.update(&p, Event::ValueBrowse(None)).unwrap();
let d = restored.draft.as_ref().unwrap();
let ordinary = d.signature(&p).unwrap().unwrap();
assert!(
d.browsed_values(&p, &ordinary)
.unwrap()
.iter()
.any(|c| c.key.field == fields[1]),
"Ordinary asynchronous channel behavior is preserved"
);
assert_eq!(d.values[&rejected], " retained output expression ");
}