use std::error::Error;
use std::path::Path;
use super::{Config, Credential};
const FIELDS: [&str; 8] = [
"server",
"channel",
"token_file",
"frame_bytes",
"record_bytes",
"reply_timeout_ms",
"flow_steps",
"request_bytes",
];
fn written(token: &Path, without: Option<&str>, zero: Option<&str>) -> String {
let mut top = String::new();
let mut limits = String::new();
for field in FIELDS {
if without == Some(field) {
continue;
}
let value = match field {
"server" => "\"127.0.0.1:4100\"".to_owned(),
"channel" => "\"events\"".to_owned(),
"token_file" => format!("{:?}", token.display().to_string()),
_ if zero == Some(field) => "0".to_owned(),
_ => "16".to_owned(),
};
if matches!(field, "server" | "channel" | "token_file") {
top.extend([field, " = ", value.as_str(), "\n"]);
} else {
limits.extend([field, " = ", value.as_str(), "\n"]);
}
}
format!("{top}\n[limits]\n{limits}")
}
#[test]
fn config_refuses_each_missing_or_zero_field_by_name() -> Result<(), Box<dyn Error>> {
let dir = tempfile::tempdir()?;
let token = dir.path().join("token");
std::fs::write(&token, "pass-bytes\n")?;
let path = dir.path().join("ticket.toml");
std::fs::write(&path, written(&token, Some("server"), None))?;
assert_eq!(Config::load(&path)?.server, crate::seat::CENTRAL_SERVER);
for field in FIELDS[1..].iter().filter(|field| **field != "token_file") {
std::fs::write(&path, written(&token, Some(field), None))?;
let refusal = Config::load(&path).err().map(|e| e.to_string());
let refusal = refusal.ok_or("a missing field was accepted")?;
assert!(refusal.contains(field), "missing {field}: {refusal}");
}
for field in &FIELDS[3..] {
std::fs::write(&path, written(&token, None, Some(field)))?;
let refusal = Config::load(&path).err().map(|e| e.to_string());
let refusal = refusal.ok_or("a zero field was accepted")?;
assert!(refusal.contains(field), "zero {field}: {refusal}");
}
std::fs::write(&path, written(&token, None, None))?;
let config = Config::load(&path)?;
assert_eq!(config.limits.flow_steps, 16);
let Credential::Token(token) = &config.credential else {
return Err("a token_file configuration did not hold a token".into());
};
assert_eq!(token.as_str(), "pass-bytes");
Ok(())
}
#[test]
fn pass_bytes_appear_in_no_debug_and_no_error() -> Result<(), Box<dyn Error>> {
let dir = tempfile::tempdir()?;
let token = dir.path().join("token");
std::fs::write(&token, "distinct-pass-7f3a\n")?;
let path = dir.path().join("ticket.toml");
std::fs::write(&path, written(&token, None, None))?;
let config = Config::load(&path)?;
let debug = format!("{config:?}");
assert!(!debug.contains("distinct-pass-7f3a"), "{debug}");
assert!(debug.contains("Token(18 bytes)"), "{debug}");
std::fs::write(&token, "")?;
let refusal = Config::load(&path).err().map(|e| e.to_string());
let refusal = refusal.ok_or("an empty token was accepted")?;
assert!(refusal.contains("token file is empty"), "{refusal}");
Ok(())
}
#[test]
fn agt001_listen_defaults_and_every_zero_bound_are_named() -> Result<(), Box<dyn Error>> {
let directory = tempfile::tempdir()?;
let token = directory.path().join("token");
std::fs::write(&token, "fixture-pass")?;
let path = directory.path().join("seat.toml");
std::fs::write(&path, written(&token, None, None))?;
let config = Config::load(&path)?;
assert_eq!(config.listen.listen_bound, 16);
assert_eq!(config.listen.content_bytes, 65536);
for field in [
"listen_bound",
"content_bytes",
"join_bound",
"join_bytes",
"batch_messages",
"subject_bytes",
"key_bytes",
"agent_bytes",
"issuer_response_bytes",
"run_pass_bytes",
] {
let text = format!("{}\n[listen]\n{field} = 0\n", written(&token, None, None));
std::fs::write(&path, text)?;
let error = Config::load(&path)
.err()
.ok_or("a zero listen bound was accepted")?;
assert!(
error.to_string().contains(field),
"the listen refusal did not name its bound"
);
}
Ok(())
}
#[test]
fn agt001_listen_refuses_unknown_fields_and_non_subject_text() -> Result<(), Box<dyn Error>> {
let directory = tempfile::tempdir()?;
let token = directory.path().join("token");
std::fs::write(&token, "fixture-pass")?;
let path = directory.path().join("seat.toml");
for table in [
"[listen]\nsubjects = [\"a..b\"]\n",
"[listen]\nunknown = true\n",
] {
std::fs::write(&path, format!("{}\n{table}", written(&token, None, None)))?;
assert!(
Config::load(&path).is_err(),
"an invalid listen table was accepted"
);
}
Ok(())
}
#[test]
fn access010_an_open_seat_carries_no_credential_and_reaches_loopback_only()
-> Result<(), Box<dyn Error>> {
let dir = tempfile::tempdir()?;
let token = dir.path().join("token");
let path = dir.path().join("seat.toml");
for server in ["127.0.0.1:4100", "localhost:7900", "ws://[::1]:7902/ws"] {
let text = written(&token, Some("token_file"), None).replace("127.0.0.1:4100", server);
std::fs::write(&path, text)?;
let config = Config::load(&path)?;
assert!(
matches!(config.credential, Credential::Open { .. }),
"{config:?}"
);
assert_eq!(config.server, server);
}
assert!(!token.exists(), "an open seat reads no token file");
for server in ["192.168.50.40:7900", "wss://liminal.ablative.com.au/ws"] {
let text = written(&token, Some("token_file"), None).replace("127.0.0.1:4100", server);
std::fs::write(&path, text)?;
let refusal = Config::load(&path).err().map(|e| e.to_string());
let refusal = refusal.ok_or("an open seat off loopback was accepted")?;
assert!(
refusal.contains(&format!(
"an open seat (no token_file, no [lys]) reaches a loopback exchange only: server {server}"
)),
"{refusal}"
);
}
std::fs::write(&token, "pass-bytes\n")?;
let both = format!(
"{}\n[lys]\nissuer = \"http://127.0.0.1:8490\"\nagent = \"agent-x\"\n",
written(&token, None, None)
);
std::fs::write(&path, both)?;
let refusal = Config::load(&path).err().map(|e| e.to_string());
let refusal = refusal.ok_or("two credentials were accepted")?;
assert!(
refusal.contains("give exactly one of token_file and [lys]"),
"{refusal}"
);
Ok(())
}
#[test]
fn access011_an_open_seat_reaching_the_network_loads_off_loopback() -> Result<(), Box<dyn Error>> {
let dir = tempfile::tempdir()?;
let token = dir.path().join("token");
let path = dir.path().join("seat.toml");
for server in ["192.168.50.40:7900", "ws://liminal.lan:7902/ws"] {
let text = format!(
"{}\n[open]\nreach = \"network\"\n",
written(&token, Some("token_file"), None).replace("127.0.0.1:4100", server)
);
std::fs::write(&path, text)?;
let config = Config::load(&path)?;
assert!(
matches!(config.credential, Credential::Open { .. }),
"{config:?}"
);
assert_eq!(config.server, server);
}
let text = format!(
"{}\n[open]\nreach = \"loopback\"\n",
written(&token, Some("token_file"), None).replace("127.0.0.1:4100", "192.168.50.40:7900")
);
std::fs::write(&path, text)?;
let refusal = Config::load(&path).err().map(|e| e.to_string());
let refusal = refusal.ok_or("reach = \"loopback\" off loopback was accepted")?;
assert!(
refusal.contains("reaches a loopback exchange only: server 192.168.50.40:7900"),
"{refusal}"
);
Ok(())
}
#[test]
fn access011_open_beside_a_credential_and_an_unknown_reach_are_refused_by_name()
-> Result<(), Box<dyn Error>> {
let dir = tempfile::tempdir()?;
let token = dir.path().join("token");
let path = dir.path().join("seat.toml");
std::fs::write(&token, "pass-bytes\n")?;
let text = format!(
"{}\n[open]\nreach = \"network\"\n",
written(&token, None, None)
);
std::fs::write(&path, text)?;
let refusal = Config::load(&path).err().map(|e| e.to_string());
let refusal = refusal.ok_or("[open] beside token_file was accepted")?;
assert!(
refusal.contains(
"[open] names a seat with no credential; this seat has token_file or [lys], remove one"
),
"{refusal}"
);
let text = format!(
"{}\n[open]\nreach = \"anywhere\"\n",
written(&token, Some("token_file"), None)
);
std::fs::write(&path, text)?;
let refusal = Config::load(&path).err().map(|e| e.to_string());
let refusal = refusal.ok_or("reach = \"anywhere\" was accepted")?;
for word in ["anywhere", "loopback", "network"] {
assert!(refusal.contains(word), "{word}: {refusal}");
}
Ok(())
}
#[test]
fn access012_an_open_seat_declares_its_name() -> Result<(), Box<dyn Error>> {
let dir = tempfile::tempdir()?;
let path = dir.path().join("seat.toml");
std::fs::write(
&path,
format!(
"{}\n[open]\nname = \"hermes\"\n",
written(&dir.path().join("token"), Some("token_file"), None)
.replace("127.0.0.1:4100", "127.0.0.1:7900")
),
)?;
let config = Config::load(&path)?;
assert!(
matches!(&config.credential, Credential::Open { name: Some(name) } if name == "hermes"),
"{config:?}"
);
assert_eq!(config.credential.holder(), "open:hermes");
assert_eq!(config.credential.open_record(), b"open:hermes".to_vec());
let bare = dir.path().join("bare.toml");
std::fs::write(
&bare,
written(&dir.path().join("token"), Some("token_file"), None)
.replace("127.0.0.1:4100", "127.0.0.1:7900"),
)?;
let config = Config::load(&bare)?;
assert!(
matches!(&config.credential, Credential::Open { name: None }),
"{config:?}"
);
assert_eq!(config.credential.holder(), "open:anonymous");
assert!(config.credential.open_record().is_empty());
Ok(())
}
#[test]
fn access012_an_invalid_open_name_is_refused_naming_the_member() -> Result<(), Box<dyn Error>> {
let dir = tempfile::tempdir()?;
for bad in ["Hermes", "her mes", "", "a/b", &"x".repeat(65)] {
let path = dir.path().join("seat.toml");
std::fs::write(
&path,
format!(
"{}\n[open]\nname = \"{bad}\"\n",
written(&dir.path().join("token"), Some("token_file"), None)
.replace("127.0.0.1:4100", "127.0.0.1:7900")
),
)?;
let refusal = Config::load(&path)
.err()
.ok_or_else(|| format!("name {bad:?} was accepted"))?;
assert!(
refusal
.to_string()
.contains("[open] name must be 1 to 64 of a-z, 0-9, '.', '_' or '-'"),
"{bad:?}: {refusal}"
);
}
Ok(())
}