yog 0.0.65

yog: the standalone server for litany loops — the world, the balls and the conversations, behind one wire
Documentation
//! The trail row's two directions (bl-4d81): the derived readings really cross,
//! `standing` is read back strictly, and the round trip is exact.

use serde_json::json;

use super::{decode, op_row};
use crate::opslog::{OpRow, OpView, Origin, Standing};

fn view(exit: i32, standing: Standing) -> OpView {
    OpView {
        row: OpRow {
            ts: "1700".to_owned(),
            argv: "litany prompt a".to_owned(),
            cwd: "/p".to_owned(),
            exit,
            stdout: String::new(),
            stderr: "boom".to_owned(),
            origin: Origin::Conversation,
            client: crate::registry::Client::default(),
        },
        standing,
    }
}

/// The three §7.3 readings ride the row, in the words their single homes give
/// them — a seat paints `exit_label`, never the sentinel behind it.
#[test]
fn the_derived_readings_cross_beside_the_durable_line() {
    let v = op_row(&view(crate::opslog::SYNTHETIC_EXIT, Standing::Live));
    assert_eq!(v["exit"], crate::opslog::SYNTHETIC_EXIT);
    assert_eq!(v["failed"], true);
    assert_eq!(v["exit_label"], "failed to spawn — never started");
    assert_eq!(v["standing"], "live");
    assert_eq!(v["origin"], "conversation");
}

/// Every standing round-trips, and the frame is returned exactly — the two
/// recomputed fields included, which is what lets them have one home.
#[test]
fn every_standing_round_trips_exactly() {
    for standing in [
        Standing::Clean,
        Standing::Detached,
        Standing::Live,
        Standing::Retired,
        Standing::Acked,
    ] {
        let before = view(1, standing);
        let frame = op_row(&before);
        let after = decode(&frame).expect("a frame this codec wrote reads back");
        assert_eq!(after, before);
        assert_eq!(op_row(&after), frame, "and re-encodes byte for byte");
    }
}

/// Strict, like every other decoder here: an unknown word and a missing field
/// each refuse, naming the offender.
#[test]
fn an_unreadable_standing_refuses_by_name() {
    let mut frame = op_row(&view(1, Standing::Live));
    frame["standing"] = json!("shrugging");
    let err = decode(&frame).expect_err("an unknown token is refused");
    assert!(
        err.contains("standing") && err.contains("shrugging"),
        "{err}"
    );

    let missing = json!({ "ts": "1", "argv": "a", "cwd": "/", "exit": 0,
                          "stdout": "", "stderr": "", "origin": "world" });
    assert!(decode(&missing).is_err(), "and so is an absent standing");
    assert!(
        decode(&json!("row")).is_err(),
        "and a row that is not an object"
    );
}

/// `exit` is an `i32` on the row and a JSON number on the wire, so a number no
/// row could have carried is refused rather than truncated.
#[test]
fn an_out_of_range_exit_refuses() {
    let mut frame = op_row(&view(1, Standing::Clean));
    frame["exit"] = json!(i64::from(i32::MAX) + 1);
    let err = decode(&frame).expect_err("out of range");
    assert!(err.contains("out of range"), "{err}");
}

/// **Who said what crosses** (bl-e59e). `client` is the second field a reader
/// cannot recompute from the line beside it, so it is written and read back
/// exactly — and a token that names no identity reads as the in-world caller,
/// which is what an engine writing its own rows is.
#[test]
fn the_author_crosses_beside_the_surface_that_asked() {
    let seat = crate::registry::Client::parse("seat2").expect("a leaf name");
    let mut asked = view(0, Standing::Clean);
    asked.row.client = seat.clone();
    let v = op_row(&asked);
    assert_eq!(v["client"], "seat2");
    assert_eq!(decode(&v).expect("decodes").row.client, seat);

    let local = op_row(&view(0, Standing::Clean));
    assert_eq!(local["client"], "local", "and the engine's own rows say so");
    assert_eq!(
        decode(&local).expect("decodes").row.client,
        crate::registry::Client::local()
    );
}