use super::*;
fn sample_websocket() -> Result<crate::config::types::WebSocketConfig, Box<dyn std::error::Error>> {
Ok(crate::config::types::WebSocketConfig {
listen_address: socket("127.0.0.1:8082")?,
path: "/liminal".to_owned(),
allowed_origins: vec!["https://app.example.com".to_owned()],
allowed_origin_ports: Vec::new(),
ping_interval_ms: Some(30_000),
})
}
#[test]
fn valid_websocket_section_passes_validation() -> Result<(), Box<dyn std::error::Error>> {
let mut config = sample_config()?;
config.websocket = Some(sample_websocket()?);
validate(&mut config, None)?;
Ok(())
}
#[test]
fn websocket_empty_origin_list_is_valid_fail_closed_configuration()
-> Result<(), Box<dyn std::error::Error>> {
let mut config = sample_config()?;
let mut websocket = sample_websocket()?;
websocket.allowed_origins = Vec::new();
config.websocket = Some(websocket);
validate(&mut config, None)?;
Ok(())
}
#[test]
fn websocket_zero_port_is_a_typed_config_error() -> Result<(), Box<dyn std::error::Error>> {
let mut config = sample_config()?;
let mut websocket = sample_websocket()?;
websocket.listen_address = socket("127.0.0.1:0")?;
config.websocket = Some(websocket);
let message = config_validation_message(validate(&mut config, None));
assert!(
message.contains("websocket.listen_address"),
"expected websocket.listen_address error, got: {message}"
);
Ok(())
}
#[test]
fn websocket_address_conflicts_are_typed_config_errors() -> Result<(), Box<dyn std::error::Error>> {
for conflict in ["127.0.0.1:8080", "127.0.0.1:8081", "127.0.0.1:9000"] {
let mut config = sample_config()?;
let mut websocket = sample_websocket()?;
websocket.listen_address = socket(conflict)?;
config.websocket = Some(websocket);
let message = config_validation_message(validate(&mut config, None));
assert!(
message.contains("websocket.listen_address: must differ from"),
"expected an address-conflict error for {conflict}, got: {message}"
);
}
Ok(())
}
#[test]
fn websocket_path_rules_are_typed_config_errors() -> Result<(), Box<dyn std::error::Error>> {
for (bad_path, expectation) in [
("", "must not be empty"),
("liminal", "must start with '/'"),
("/limi nal", "whitespace"),
("/liminal?x=1", "query"),
("/liminal#frag", "query"),
] {
let mut config = sample_config()?;
let mut websocket = sample_websocket()?;
websocket.path = bad_path.to_owned();
config.websocket = Some(websocket);
let message = config_validation_message(validate(&mut config, None));
assert!(
message.contains("websocket.path") && message.contains(expectation),
"expected websocket.path '{bad_path}' to report '{expectation}', got: {message}"
);
}
Ok(())
}
#[test]
fn websocket_malformed_origin_entries_are_typed_config_errors()
-> Result<(), Box<dyn std::error::Error>> {
for (bad_origin, expectation) in [
("", "must not be empty"),
("app.example.com", "serialized origin"),
("https://app.example.com/", "path or trailing slash"),
("https://app.example.com/app", "path or trailing slash"),
("https:// app.example.com", "whitespace"),
] {
let mut config = sample_config()?;
let mut websocket = sample_websocket()?;
websocket.allowed_origins = vec![bad_origin.to_owned()];
config.websocket = Some(websocket);
let message = config_validation_message(validate(&mut config, None));
assert!(
message.contains("websocket.allowed_origins") && message.contains(expectation),
"expected origin '{bad_origin}' to report '{expectation}', got: {message}"
);
}
Ok(())
}
#[test]
fn websocket_duplicate_origin_is_a_typed_config_error() -> Result<(), Box<dyn std::error::Error>> {
let mut config = sample_config()?;
let mut websocket = sample_websocket()?;
websocket.allowed_origins = vec![
"https://app.example.com".to_owned(),
"https://app.example.com".to_owned(),
];
config.websocket = Some(websocket);
let message = config_validation_message(validate(&mut config, None));
assert!(
message.contains("duplicate origin"),
"expected a duplicate-origin error, got: {message}"
);
Ok(())
}
#[test]
fn websocket_zero_ping_interval_is_a_typed_config_error() -> Result<(), Box<dyn std::error::Error>>
{
let mut config = sample_config()?;
let mut websocket = sample_websocket()?;
websocket.ping_interval_ms = Some(0);
config.websocket = Some(websocket);
let message = config_validation_message(validate(&mut config, None));
assert!(
message.contains("websocket.ping_interval_ms"),
"expected a zero-interval error, got: {message}"
);
Ok(())
}
#[test]
fn websocket_absent_ping_interval_means_disabled_and_valid()
-> Result<(), Box<dyn std::error::Error>> {
let mut config = sample_config()?;
let mut websocket = sample_websocket()?;
websocket.ping_interval_ms = None;
config.websocket = Some(websocket);
validate(&mut config, None)?;
Ok(())
}
#[test]
fn con014_a_zero_or_duplicate_origin_port_is_refused_by_name()
-> Result<(), Box<dyn std::error::Error>> {
for (ports, expectation) in [(vec![0u16], "port 0"), (vec![5178, 5178], "duplicate port")] {
let mut config = sample_config()?;
let mut websocket = sample_websocket()?;
websocket.allowed_origin_ports = ports;
config.websocket = Some(websocket);
let message = config_validation_message(validate(&mut config, None));
assert!(
message.contains("websocket.allowed_origin_ports") && message.contains(expectation),
"expected '{expectation}', got: {message}"
);
}
let mut config = sample_config()?;
let mut websocket = sample_websocket()?;
websocket.allowed_origin_ports = vec![5178, 5188];
config.websocket = Some(websocket);
validate(&mut config, None)?;
Ok(())
}