use serde_json::json;
use super::p;
use crate::boundary::codec::decode;
use crate::boundary::{Action, Gesture};
use crate::fan::{Obligation, Verb};
use crate::opslog::Origin;
use crate::start::Prepared;
fn prepared(binding: Option<&str>) -> Prepared {
Prepared {
workspace: "ws".into(),
binding: binding.map(p),
goal: "Ball bl-1f2a: do it".into(),
origin: Origin::Balls,
lineage: None,
role: None,
}
}
fn obligation(ball: Option<&str>) -> Obligation {
Obligation {
project: "dev/proj".into(),
ball: ball.map(str::to_owned),
}
}
pub(super) fn surface() -> Vec<Gesture> {
let mut out = Vec::new();
for ball in [Some("bl-1f2a"), None] {
out.push(Gesture::Act(Action::Fan(Verb::Spread {
prepared: prepared(Some("/claim")),
obligation: obligation(ball),
n: 3,
})));
out.push(Gesture::Act(Action::Fan(Verb::Retire {
obligation: obligation(ball),
handle: "at-0badcafe".into(),
})));
out.push(Gesture::Act(Action::Fan(Verb::Deliver {
obligation: obligation(ball),
handle: "at-0badcafe".into(),
summary: "take the winner — spaces stay".into(),
})));
}
for n in [0, 1] {
out.push(Gesture::Act(Action::Fan(Verb::Spread {
prepared: prepared(None),
obligation: obligation(Some("bl-1f2a")),
n,
})));
}
out
}
#[test]
fn a_fan_without_a_ball_field_is_the_bare_obligation() {
let envelope = json!({"op": "fan", "project": "dev/proj", "n": 2,
"prepared": {"workspace": "ws",
"binding": null, "goal": "g", "origin": "balls"}});
assert_eq!(
decode(&envelope),
Ok(Gesture::Act(Action::Fan(Verb::Spread {
prepared: Prepared {
goal: "g".into(),
..prepared(None)
},
obligation: obligation(None),
n: 2,
}))),
);
assert!(
!encoded(&envelope).contains("\"ball\""),
"and it re-encodes without the field it never had"
);
}
fn encoded(envelope: &serde_json::Value) -> String {
let Ok(gesture) = decode(envelope) else {
return String::new();
};
crate::boundary::codec::encode(&gesture).to_string()
}
#[test]
fn a_malformed_fan_retirement_or_delivery_refuses_with_a_reason() {
let prepared = json!({"name": "n", "workspace": "/ws", "binding": null,
"goal": "g", "origin": "balls"});
let cases: Vec<(serde_json::Value, &str)> = vec![
(
json!({"op": "fan", "project": "/dev/proj", "prepared": prepared}),
"field \"n\"",
),
(
json!({"op": "fan", "project": "/dev/proj", "n": 2}),
"fan: missing prepared",
),
(
json!({"op": "fan", "n": 2, "prepared": prepared}),
"field \"project\"",
),
(
json!({"op": "fan", "project": "/p", "n": 2, "ball": 7, "prepared": prepared}),
"field \"ball\"",
),
(
json!({"op": "fan", "project": "/p", "n": -1, "prepared": prepared}),
"field \"n\"",
),
(
json!({"op": "retire", "project": "/dev/proj"}),
"field \"handle\"",
),
(
json!({"op": "deliver", "project": "/dev/proj", "summary": "s"}),
"field \"handle\"",
),
(
json!({"op": "deliver", "project": "/dev/proj", "handle": "at-1"}),
"field \"summary\"",
),
];
for (envelope, needle) in cases {
let refusal = decode(&envelope).expect_err("must refuse");
assert!(refusal.contains(needle), "{envelope} refused {refusal:?}");
}
}