use serde_json::{Value, json};
use crate::boundary::corpus::ledger::{Ledger, advance};
use crate::boundary::corpus::{Shape, canonical};
fn ack(frame: Value) -> Shape {
Shape {
direction: "request",
name: "ack".to_owned(),
frames: vec![frame],
}
}
fn recorded(protocol: u32, floor: u32, stamps: &[(&str, u32)]) -> Ledger {
let signature: serde_json::Map<String, Value> = stamps
.iter()
.map(|(path, at)| ((*path).to_owned(), json!(at)))
.collect();
Ledger::read(&canonical(&json!({
"protocol": protocol,
"floor": floor,
"shapes": { "request/ack": { "signature": signature } },
})))
}
const ACK: &[(&str, u32)] = &[("/op:string", 1), (":object", 1)];
#[test]
fn a_gained_field_is_stamped_the_next_edition_and_needs_no_bump() {
let previous = recorded(
19,
18,
&[("/op:string", 1), (":object", 1), ("/late:bool", 18)],
);
let next = advance(
&[ack(json!({ "op": "ack", "late": true, "reason": "why" }))],
&previous,
19,
19,
&[],
)
.expect("additive");
let stamps = &next.shapes["request/ack"].signature;
assert_eq!(stamps["/reason:string"], 19, "the gain");
assert_eq!(stamps["/late:bool"], 18, "what stood");
assert_eq!(stamps["/op:string"], 1);
assert_eq!((next.protocol, next.floor), (19, 18));
assert_eq!(next.edition(), 19);
}
#[test]
fn a_new_shape_is_stamped_the_next_edition() {
let fresh = Shape {
direction: "request",
name: "novel".to_owned(),
frames: vec![json!({ "op": "novel" })],
};
let next = advance(
&[ack(json!({ "op": "ack" })), fresh],
&recorded(19, 18, ACK),
19,
19,
&[],
)
.expect("additive");
assert!(
next.shapes["request/novel"]
.signature
.values()
.all(|&at| at == 2)
);
assert_eq!(
Ledger::read("").edition(),
0,
"an empty record is edition 0"
);
}
#[test]
fn a_vanished_field_demands_the_major_and_a_deprecation() {
let previous = recorded(
19,
18,
&[("/op:string", 1), (":object", 1), ("/gone:bool", 2)],
);
let shapes = [ack(json!({ "op": "ack" }))];
let held = advance(&shapes, &previous, 19, 19, &["request/ack/gone"])
.expect_err("refused under the major");
assert!(held.contains("request/ack/gone:bool vanished"), "{held}");
assert!(held.contains("DEPRECATED"), "{held}");
assert!(held.contains("PROTOCOL file"), "{held}");
assert!(held.contains("make corpus"), "{held}");
let bumping = advance(&shapes, &previous, 20, 19, &[]).expect_err("refused undeprecated");
assert!(bumping.contains("vanished undeprecated"), "{bumping}");
let next = advance(&shapes, &previous, 20, 19, &["request/ack/gone"]).expect("lawful");
assert!(next.deprecated.contains("request/ack/gone"));
assert!(
!next.shapes["request/ack"]
.signature
.contains_key("/gone:bool")
);
assert_eq!(
next.render(),
Ledger::read(&next.render()).render(),
"round trip"
);
}
#[test]
fn a_vanished_shape_is_named_too() {
let refusal = advance(&[], &recorded(19, 18, ACK), 19, 19, &[]).expect_err("refused");
assert!(refusal.contains("request/ack:object vanished"), "{refusal}");
let gone = advance(&[], &recorded(19, 18, ACK), 20, 19, &["request/ack"]).expect("lawful");
assert!(gone.shapes.is_empty());
}
#[test]
fn a_second_type_on_a_key_in_use_is_breaking() {
let previous = recorded(19, 18, ACK);
let shapes = [ack(json!({ "op": 7 }))];
let refusal = advance(&shapes, &previous, 19, 19, &[]).expect_err("re-typed");
assert!(
refusal.contains("request/ack/op:number re-typed"),
"{refusal}"
);
let widened = [Shape {
direction: "request",
name: "ack".to_owned(),
frames: vec![json!({ "op": "ack" }), json!({ "op": Value::Null })],
}];
let next = advance(&widened, &previous, 20, 19, &[]).expect("lawful across a bump");
assert_eq!(next.shapes["request/ack"].signature["/op:null"], 2);
}
#[test]
fn the_floor_is_set_by_the_bump_and_left_by_everything_else() {
let bumped = advance(
&[ack(json!({ "op": "ack" }))],
&recorded(18, 0, ACK),
19,
17,
&[],
)
.expect("the bump");
assert_eq!((bumped.protocol, bumped.floor), (19, 1));
let later = advance(
&[ack(json!({ "op": "ack", "more": 1 }))],
&bumped,
19,
19,
&[],
)
.expect("additive");
assert_eq!(later.floor, 1, "unmoved");
assert_eq!(
later.shapes["request/ack"].signature["/more:number"], 2,
"post-floor"
);
}