use super::*;
fn open_mode_refusal(field: &str, address: &str) -> String {
format!(
"open mode (no [auth]): {field} {address} is not loopback; an exchange with no [auth] \
listens on loopback only, give [auth] to listen elsewhere"
)
}
fn websocket_on(
address: &str,
) -> Result<crate::config::types::WebSocketConfig, Box<dyn std::error::Error>> {
Ok(crate::config::types::WebSocketConfig {
listen_address: socket(address)?,
path: "/ws".to_owned(),
allowed_origins: Vec::new(),
allowed_origin_ports: Vec::new(),
ping_interval_ms: None,
})
}
#[test]
fn an_open_exchange_off_loopback_is_refused_on_every_listener_by_name()
-> Result<(), Box<dyn std::error::Error>> {
let mut config = sample_config()?;
config.auth = None;
config.listen_address = socket("0.0.0.0:8080")?;
config.extra_listen_addresses = vec![socket("192.168.50.40:8082")?];
config.websocket = Some(websocket_on("0.0.0.0:8083")?);
let message = config_validation_message(validate(&mut config, None));
for expected in [
open_mode_refusal("listen_address", "0.0.0.0:8080"),
open_mode_refusal("extra_listen_addresses[0]", "192.168.50.40:8082"),
open_mode_refusal("websocket.listen_address", "0.0.0.0:8083"),
] {
assert!(
message.lines().any(|line| line == expected),
"missing {expected:?} in {message}"
);
}
Ok(())
}
#[test]
fn an_open_exchange_on_loopback_validates() -> Result<(), Box<dyn std::error::Error>> {
let mut config = sample_config()?;
config.auth = None;
config.listen_address = socket("127.0.0.1:8080")?;
config.extra_listen_addresses = vec![socket("[::1]:8082")?];
config.websocket = Some(websocket_on("127.0.0.1:8083")?);
validate(&mut config, None)?;
assert_eq!(config.access_mode(), crate::config::types::AccessMode::Open);
Ok(())
}
#[test]
fn a_gated_exchange_may_listen_off_loopback() -> Result<(), Box<dyn std::error::Error>> {
let mut config = sample_config()?;
config.auth = Some(AuthConfig {
token: "bearer-under-test".to_owned(),
pass: None,
lys: None,
require_verified_worker: false,
});
config.listen_address = socket("0.0.0.0:8080")?;
let message = config_validation_message(validate(&mut config, None));
assert!(!message.contains("open mode"), "{message}");
assert_eq!(
config.access_mode(),
crate::config::types::AccessMode::Gated
);
Ok(())
}
const fn open_reach(reach: crate::config::types::Reach) -> crate::config::types::OpenConfig {
crate::config::types::OpenConfig { reach }
}
#[test]
fn access011_an_open_exchange_reaching_the_network_validates_off_loopback()
-> Result<(), Box<dyn std::error::Error>> {
let mut config = sample_config()?;
config.auth = None;
config.open = Some(open_reach(crate::config::types::Reach::Network));
config.listen_address = socket("0.0.0.0:8080")?;
config.extra_listen_addresses = vec![socket("192.168.50.40:8082")?];
config.websocket = Some(websocket_on("0.0.0.0:8083")?);
validate(&mut config, None)?;
assert_eq!(config.access_mode(), crate::config::types::AccessMode::Open);
assert_eq!(config.open_reach(), crate::config::types::Reach::Network);
Ok(())
}
#[test]
fn access011_reach_loopback_written_or_absent_keeps_the_loopback_refusal()
-> Result<(), Box<dyn std::error::Error>> {
for open in [
None,
Some(open_reach(crate::config::types::Reach::Loopback)),
] {
let mut config = sample_config()?;
config.auth = None;
config.open = open;
config.listen_address = socket("0.0.0.0:8080")?;
let message = config_validation_message(validate(&mut config, None));
let expected = open_mode_refusal("listen_address", "0.0.0.0:8080");
assert!(
message.lines().any(|line| line == expected),
"missing {expected:?} in {message}"
);
assert_eq!(config.open_reach(), crate::config::types::Reach::Loopback);
}
Ok(())
}
#[test]
fn access011_an_unknown_reach_word_is_refused_naming_the_member_and_both_words()
-> Result<(), Box<dyn std::error::Error>> {
let refusal = toml::from_str::<crate::config::types::OpenConfig>("reach = \"anywhere\"")
.err()
.map(|error| error.to_string())
.ok_or("reach = \"anywhere\" was accepted")?;
assert!(refusal.contains("anywhere"), "{refusal}");
assert!(refusal.contains("loopback"), "{refusal}");
assert!(refusal.contains("network"), "{refusal}");
assert!(refusal.contains("reach"), "{refusal}");
Ok(())
}
#[test]
fn access011_open_beside_auth_is_refused_by_name() -> Result<(), Box<dyn std::error::Error>> {
let mut config = sample_config()?;
config.auth = Some(crate::config::types::AuthConfig {
pass: None,
token: "s3cr3t".to_owned(),
require_verified_worker: false,
lys: None,
});
config.open = Some(open_reach(crate::config::types::Reach::Network));
let message = config_validation_message(validate(&mut config, None));
let expected =
"[open] names an exchange with no [auth]; this configuration has [auth], remove one";
assert!(
message.lines().any(|line| line == expected),
"missing {expected:?} in {message}"
);
Ok(())
}