liminal-server 0.25.4

Standalone server for the liminal messaging bus
Documentation
use super::*;

/// SRV-012 R4: a console channel configured durable is refused by name;
/// the same channel ephemeral, and an ordinary durable one, pass.
#[test]
fn a_durable_console_channel_is_refused_in_config() -> Result<(), Box<dyn std::error::Error>> {
    let console = |durable| ChannelDef {
        name: "console.topology".to_owned(),
        schema_ref: None,
        durable,
        loaded_schema: None,
    };
    let mut config = sample_config()?;
    config.channels.push(console(true));
    let message = config_validation_message(validate(&mut config, None));
    assert!(message.contains("console channel 'console.topology' must not be durable"));

    let mut config = sample_config()?;
    config.channels.push(console(false));
    validate(&mut config, None)?;
    Ok(())
}

#[test]
fn valid_config_passes_validation() -> Result<(), Box<dyn std::error::Error>> {
    let mut config = sample_config()?;

    validate(&mut config, None)?;

    Ok(())
}

#[test]
fn invalid_listen_address_reports_field_name() -> Result<(), Box<dyn std::error::Error>> {
    let mut config = sample_config()?;
    config.listen_address = socket("127.0.0.1:0")?;

    let message = config_validation_message(validate(&mut config, None));

    assert!(message.contains("listen_address"));
    assert!(message.contains("port"));

    Ok(())
}

#[test]
fn invalid_health_listen_address_reports_field_name() -> Result<(), Box<dyn std::error::Error>> {
    let mut config = sample_config()?;
    config.health_listen_address = socket("127.0.0.1:0")?;

    let message = config_validation_message(validate(&mut config, None));

    assert!(message.contains("health_listen_address"));
    assert!(message.contains("port"));

    Ok(())
}

#[test]
fn matching_health_and_main_listen_addresses_are_rejected() -> Result<(), Box<dyn std::error::Error>>
{
    let mut config = sample_config()?;
    config.health_listen_address = config.listen_address;

    let message = config_validation_message(validate(&mut config, None));

    assert!(message.contains("health_listen_address"));
    assert!(message.contains("listen_address"));

    Ok(())
}

#[test]
fn matching_health_and_main_listen_ports_are_rejected() -> Result<(), Box<dyn std::error::Error>> {
    let mut config = sample_config()?;
    config.health_listen_address = socket("0.0.0.0:8080")?;

    let message = config_validation_message(validate(&mut config, None));

    assert!(message.contains("health_listen_address"));
    assert!(message.contains("port"));

    Ok(())
}

/// `drain_timeout_ms` has been ignored since 0.14.3, so it can no longer
/// refuse a startup — a field that changes nothing must not be able to stop
/// a server coming up. Zero loads exactly like any other value.
#[test]
fn zero_drain_timeout_is_accepted_because_the_field_is_ignored()
-> Result<(), Box<dyn std::error::Error>> {
    let mut config = sample_config()?;
    config.drain_timeout_ms = 0;

    validate(&mut config, None)?;

    assert_eq!(config.drain_timeout_ms, 0);

    Ok(())
}

#[test]
fn duplicate_channel_names_are_listed() -> Result<(), Box<dyn std::error::Error>> {
    let mut config = sample_config()?;
    config.channels.push(ChannelDef {
        name: "orders".to_owned(),
        schema_ref: None,
        durable: false,
        loaded_schema: None,
    });

    let message = config_validation_message(validate(&mut config, None));

    assert!(message.contains("duplicate"));
    assert!(message.contains("orders"));

    Ok(())
}

#[test]
fn unreachable_persistence_path_reports_path() -> Result<(), Box<dyn std::error::Error>> {
    let mut config = sample_config()?;
    let path = unique_temp_dir("missing");
    config.persistence_path = Some(path.clone());

    let message = config_validation_message(validate(&mut config, None));

    assert!(message.contains("persistence_path"));
    assert!(message.contains(&path.display().to_string()));

    Ok(())
}

#[test]
fn file_persistence_path_is_rejected() -> Result<(), Box<dyn std::error::Error>> {
    let mut config = sample_config()?;
    let path = unique_temp_dir("file");
    fs::write(&path, "not a directory")?;
    config.persistence_path = Some(path.clone());

    let message = config_validation_message(validate(&mut config, None));
    fs::remove_file(&path)?;

    assert!(message.contains("persistence_path"));
    assert!(message.contains("directory"));

    Ok(())
}

#[test]
fn multiple_validation_errors_are_reported_together() -> Result<(), Box<dyn std::error::Error>> {
    let mut config = sample_config()?;
    let missing_path = unique_temp_dir("multi-missing");
    config.listen_address = socket("127.0.0.1:0")?;
    config.channels.push(ChannelDef {
        name: "orders".to_owned(),
        schema_ref: None,
        durable: false,
        loaded_schema: None,
    });
    config.persistence_path = Some(missing_path.clone());

    let message = config_validation_message(validate(&mut config, None));

    assert!(message.contains("listen_address"));
    assert!(message.contains("duplicate channel names: orders"));
    assert!(message.contains(&missing_path.display().to_string()));

    Ok(())
}

#[test]
fn routing_rules_reference_configured_channels() -> Result<(), Box<dyn std::error::Error>> {
    let mut config = sample_config()?;
    config.routing_rules[0].target_channel = "unknown".to_owned();

    let message = config_validation_message(validate(&mut config, None));

    assert!(message.contains("routing_rules[0].target_channel"));
    assert!(message.contains("unknown"));

    Ok(())
}