Checkpoint source 7 workflows and in-progress diagram authoring redesign
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
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.
This commit is contained in:
@@ -0,0 +1,304 @@
|
||||
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 ");
|
||||
}
|
||||
Reference in New Issue
Block a user