crates/monetize-proto/tests/terminate_wire.rs
//! ★ **A32-27: the terminate wire carries no backup** ("ta ingen backup alls", owner 2026-10-22).
//! `EstateOrderTerminateRequest.backup` (4) and `EstateOrderTerminateResponse.backup_offered`
//! (2) / `backups` (4) are deleted and their numbers and names RESERVED, so no later field reuses
//! them. Wire compatibility both ways: an older page's request that still says `backup: skip`
//! decodes in today's message (the field is skipped), and today's answer decodes in an older
//! client (its `backup_offered` reads false, its `backups` empty).
use monetize_proto as pb;
use prost::Message;
/// The request as a client from before A32-27 encoded it.
#[derive(Clone, PartialEq, prost::Message)]
struct OldTerminateRequest {
#[prost(string, tag = "1")]
company: String,
#[prost(bytes = "vec", tag = "2")]
actor_ticket: Vec<u8>,
#[prost(string, tag = "3")]
confirm: String,
#[prost(string, tag = "4")]
backup: String,
}
/// The answer as a client from before A32-27 decodes it.
#[derive(Clone, PartialEq, prost::Message)]
struct OldTerminateResponse {
#[prost(string, tag = "1")]
words: String,
#[prost(bool, tag = "2")]
backup_offered: bool,
#[prost(bool, tag = "3")]
done: bool,
#[prost(string, repeated, tag = "4")]
backups: Vec<String>,
}
fn message_block<'a>(proto: &'a str, name: &str) -> &'a str {
let head = format!("message {name} {{");
let from = proto.find(&head).unwrap_or_else(|| panic!("{name} is declared"));
let rest = &proto[from..];
&rest[..rest.find("\n}").expect("the message ends")]
}
#[test]
fn the_terminate_messages_reserve_the_backup_fields_and_carry_none() {
let proto = include_str!("../proto/monetize/v1/estate_order.proto");
let req = message_block(proto, "EstateOrderTerminateRequest");
assert!(!req.contains("string backup"), "the request carries no backup: {req}");
assert!(req.contains("reserved 4;") && req.contains("reserved \"backup\";"), "its number and name are reserved: {req}");
let resp = message_block(proto, "EstateOrderTerminateResponse");
assert!(!resp.contains("backup_offered =") && !resp.contains("backups ="), "the answer carries no backup: {resp}");
assert!(resp.contains("reserved 2, 4;") && resp.contains("reserved \"backup_offered\", \"backups\";"), "its numbers and names are reserved: {resp}");
}
#[test]
fn an_older_page_s_request_and_an_older_client_s_answer_still_decode() {
let old = OldTerminateRequest { company: "acme".into(), actor_ticket: vec![1, 2, 3], confirm: "acme".into(), backup: "skip".into() };
let now = pb::EstateOrderTerminateRequest::decode(old.encode_to_vec().as_slice()).expect("an older request decodes");
assert_eq!((now.company.as_str(), now.actor_ticket.as_slice(), now.confirm.as_str()), ("acme", &[1u8, 2, 3][..], "acme"));
assert!(now.encode_to_vec().len() < old.encode_to_vec().len(), "the backup word is not carried on");
let answer = pb::EstateOrderTerminateResponse { words: "acme is being ended now".into(), done: true };
let read = OldTerminateResponse::decode(answer.encode_to_vec().as_slice()).expect("an older client reads the answer");
assert_eq!(read, OldTerminateResponse { words: "acme is being ended now".into(), backup_offered: false, done: true, backups: Vec::new() });
}