liminal-server 0.24.1

Standalone server for the liminal messaging bus
Documentation
use std::net::SocketAddr;

use super::bootstrap::start_cluster;
use super::{SharedReadinessState, StartStages};
use crate::ServerError;
use crate::config::types::ClusterConfig;
use crate::health::{ClusterReadiness, ReadinessCondition, ReadinessState, readiness_check};
use crate::server::connection::services::ChannelCluster;
use crate::server::connection::services::LiminalConnectionServices;

/// The started line names each stage with its own length and where it
/// ended, so a slow start says which stage took the time.
#[test]
fn start_stages_read_as_lengths_and_ends() {
    let stages = StartStages {
        began: std::time::Instant::now(),
        marks: vec![("config", 3), ("health", 4), ("services", 2_104)],
    };
    assert_eq!(
        stages.to_string(),
        "config 3 (3), health 1 (4), services 2100 (2104)"
    );
}

/// A channel cluster with NO distribution resolver — the shape produced when a
/// server was built without a `[cluster]` section. `start_cluster` must reject
/// it before touching `cluster::start`, so its `on_established` hook never runs.
fn unclustered_channel_cluster() -> Result<ChannelCluster, ServerError> {
    Ok(LiminalConnectionServices::empty()?
        .channel_cluster()
        .clone())
}

fn clustered_but_unmet_readiness() -> SharedReadinessState {
    SharedReadinessState::new(ReadinessState::new(
        true,
        true,
        ClusterReadiness::Configured {
            membership_established: false,
        },
    ))
}

fn sample_cluster_config() -> Result<ClusterConfig, Box<dyn std::error::Error>> {
    let listen_address: SocketAddr = "127.0.0.1:0".parse()?;
    Ok(ClusterConfig {
        node_name: "node-under-test@127.0.0.1".to_owned(),
        listen_address,
        seed_nodes: Vec::new(),
        cookie: "runtime-test-cookie".to_owned(),
    })
}

#[test]
fn failed_cluster_start_leaves_membership_unestablished() -> Result<(), Box<dyn std::error::Error>>
{
    let readiness = clustered_but_unmet_readiness();
    let channel_cluster = unclustered_channel_cluster()?;
    let config = sample_cluster_config()?;

    // Missing-resolver failure path: start_cluster returns Err before the
    // established hook can fire.
    let result = start_cluster(&channel_cluster, &config, &readiness);
    assert!(
        result.is_err(),
        "start_cluster must fail without a distribution resolver"
    );

    // The readiness flag stays unset, so /ready still lists the unmet gate.
    let status = readiness_check(&readiness.snapshot());
    assert!(
        !status.ready,
        "readiness must remain not-ready after a failed start"
    );
    assert!(
        status
            .unmet_conditions
            .contains(&ReadinessCondition::ClusterMembershipEstablished),
        "cluster membership gate must stay unmet after a failed start"
    );

    Ok(())
}