|
|
|
@@ -289,7 +289,10 @@ impl StructuredState {
|
|
|
|
|
Some((nonce, decision))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn approval_failed(&mut self, error: String) {
|
|
|
|
|
pub fn approval_failed(&mut self, nonce: &str, error: String) {
|
|
|
|
|
if self.pending_nonce() != Some(nonce) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
self.approval_submitting = false;
|
|
|
|
|
self.error = Some(error);
|
|
|
|
|
}
|
|
|
|
@@ -554,6 +557,81 @@ mod tests {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn resolved_approval_ignores_its_late_failure() {
|
|
|
|
|
let mut state = StructuredState::default();
|
|
|
|
|
state.rows.push(row(
|
|
|
|
|
"approval:1",
|
|
|
|
|
TranscriptRowKind::Approval,
|
|
|
|
|
json!({"options":[{"kind":"allow_once"}]}),
|
|
|
|
|
));
|
|
|
|
|
state.selected = Some("approval:1".into());
|
|
|
|
|
state.reduced.pending_approval = Some(json!({"nonce":"old"}));
|
|
|
|
|
assert!(state.approval_decision('a').is_some());
|
|
|
|
|
|
|
|
|
|
state.apply(StructuredEvent::ProjectionUpdate {
|
|
|
|
|
transcript: None,
|
|
|
|
|
state: ReducedState::default(),
|
|
|
|
|
});
|
|
|
|
|
state.approval_failed("old", "response lost".into());
|
|
|
|
|
|
|
|
|
|
assert_eq!(state.error, None);
|
|
|
|
|
assert!(!state.approval_submitting);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn superseded_approval_ignores_its_late_failure() {
|
|
|
|
|
let mut state = StructuredState::default();
|
|
|
|
|
state.rows.push(row(
|
|
|
|
|
"approval:1",
|
|
|
|
|
TranscriptRowKind::Approval,
|
|
|
|
|
json!({"options":[{"kind":"allow_once"}]}),
|
|
|
|
|
));
|
|
|
|
|
state.selected = Some("approval:1".into());
|
|
|
|
|
state.reduced.pending_approval = Some(json!({"nonce":"old"}));
|
|
|
|
|
assert!(state.approval_decision('a').is_some());
|
|
|
|
|
|
|
|
|
|
state.rows.push(row(
|
|
|
|
|
"approval:2",
|
|
|
|
|
TranscriptRowKind::Approval,
|
|
|
|
|
json!({"options":[{"kind":"allow_once"}]}),
|
|
|
|
|
));
|
|
|
|
|
state.apply(StructuredEvent::ProjectionUpdate {
|
|
|
|
|
transcript: None,
|
|
|
|
|
state: ReducedState {
|
|
|
|
|
pending_approval: Some(json!({"nonce":"new"})),
|
|
|
|
|
..ReducedState::default()
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
assert_eq!(
|
|
|
|
|
state.approval_decision('a'),
|
|
|
|
|
Some(("new".into(), StructuredApprovalDecision::AllowOnce))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
state.approval_failed("old", "response lost".into());
|
|
|
|
|
|
|
|
|
|
assert_eq!(state.error, None);
|
|
|
|
|
assert!(state.approval_submitting);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn matching_approval_surfaces_its_failure() {
|
|
|
|
|
let mut state = StructuredState::default();
|
|
|
|
|
state.rows.push(row(
|
|
|
|
|
"approval:1",
|
|
|
|
|
TranscriptRowKind::Approval,
|
|
|
|
|
json!({"options":[{"kind":"allow_once"}]}),
|
|
|
|
|
));
|
|
|
|
|
state.selected = Some("approval:1".into());
|
|
|
|
|
state.reduced.pending_approval = Some(json!({"nonce":"current"}));
|
|
|
|
|
assert!(state.approval_decision('a').is_some());
|
|
|
|
|
|
|
|
|
|
state.approval_failed("current", "request failed".into());
|
|
|
|
|
|
|
|
|
|
assert_eq!(state.error.as_deref(), Some("request failed"));
|
|
|
|
|
assert!(!state.approval_submitting);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn composer_is_gated_by_the_projected_turn_lifecycle() {
|
|
|
|
|
let mut state = StructuredState::default();
|
|
|
|
|