liminal-server 0.24.5

Standalone server for the liminal messaging bus
Documentation
//! The held topology: one revision per real change, nothing for a repeat,
//! never a console line, and a document in the console's names.

use std::time::{Duration, Instant};

use super::error::ConsoleError;
use super::model::{FlowRole, ThisDevice, TopologyChange, TopologyModel};
use super::records::{
    Carrier, LineLife, LineState, LinkCounters, LinkState, Mount, canonical_bytes,
};
use super::wall::WallBase;

type TestResult = Result<(), Box<dyn std::error::Error>>;

fn model(started: Instant) -> TopologyModel {
    TopologyModel::new(
        ThisDevice {
            id: "local".to_owned(),
            liminal: "1.2.3".to_owned(),
            started,
            depot: false,
        },
        ["orders.reply".to_owned()],
        [
            ("orders".to_owned(), LineLife::Kept, LineState::Live),
            (
                "console.topology".to_owned(),
                LineLife::Passing,
                LineState::Live,
            ),
            (
                "console.tap.orders".to_owned(),
                LineLife::Passing,
                LineState::Live,
            ),
        ],
        [(7, Mount::Websocket, Some("alice".to_owned()))],
        [],
    )
}

/// The wall clock the tests read: [`WALL_MS`] at the instant a document is
/// built.
const WALL_MS: u64 = 1_791_272_000_000;

fn document(
    model: &TopologyModel,
    built: Instant,
) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
    Ok(serde_json::from_slice(&canonical_bytes(
        &model.document(&WallBase::fixed(WALL_MS, built)).0,
        "topology",
    )?)?)
}

/// R7: with console.topology and two console.tap channels live, no line id
/// starts with console., at start or after a change names one.
#[test]
fn the_topology_has_no_console_line() -> TestResult {
    let started = Instant::now();
    let mut model = model(started);
    assert!(!model.apply(TopologyChange::Channel {
        name: "console.tap.audit".to_owned(),
        life: LineLife::Passing,
        state: LineState::Live,
    }));
    let value = document(&model, started)?;
    let lines = value["lines"].as_array().ok_or("lines is an array")?;
    assert_eq!(lines.len(), 2);
    assert_eq!(lines[0]["id"], "orders");
    assert_eq!(lines[1]["id"], "orders.reply");
    assert_eq!(lines[1]["kind"], "call");
    assert!(lines.iter().all(|line| {
        !line["id"]
            .as_str()
            .is_some_and(|id| id.starts_with("console."))
    }));
    Ok(())
}

/// Each real change moves the revision on by exactly one; a repeat moves it
/// not at all, so it publishes nothing.
#[test]
fn one_revision_per_real_change() {
    let started = Instant::now();
    let mut model = model(started);
    assert_eq!(model.rev(), 1);
    let changes = [
        TopologyChange::Service {
            subject: "billing".to_owned(),
            present: true,
        },
        TopologyChange::Channel {
            name: "orders".to_owned(),
            life: LineLife::Kept,
            state: LineState::Quiesced,
        },
        TopologyChange::ConnectionOpened {
            id: 8,
            mount: Mount::Tcp,
            participant: None,
        },
        TopologyChange::Peer {
            name: "edge".to_owned(),
            present: true,
        },
    ];
    for change in changes.clone() {
        assert!(model.apply(change));
    }
    assert_eq!(model.rev(), 5);
    for change in changes {
        assert!(!model.apply(change), "a repeat is not a change");
    }
    assert_eq!(model.rev(), 5);
    assert!(model.apply(TopologyChange::ConnectionClosed { id: 8 }));
    assert!(!model.apply(TopologyChange::ConnectionClosed { id: 8 }));
    assert_eq!(model.rev(), 6);
}

#[path = "model_tests/state.rs"]
mod state;
#[path = "model_tests/stations.rs"]
mod stations;