use std::error::Error;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use serde_json::json;
use super::network::{RunningServer, lys_pass_expired, lys_pass_with};
use super::{CHANNEL, Host, SUBJECT, Ticket, answering_worker};
fn seat_with_pass(
dir: &Path,
name: &str,
address: SocketAddr,
pass: &str,
) -> Result<PathBuf, Box<dyn Error>> {
let token = dir.join(format!("{name}.token"));
std::fs::write(&token, format!("{pass}\n"))?;
let seat = dir.join(format!("{name}.toml"));
std::fs::write(
&seat,
format!(
"server = \"{address}\"\nchannel = \"{CHANNEL}\"\ntoken_file = \"{name}.token\"\n\n\
[limits]\nframe_bytes = 65536\nrecord_bytes = 1048576\nreply_timeout_ms = 5000\n\
flow_steps = 8\nrequest_bytes = 65536\n"
),
)?;
Ok(seat)
}
fn advised_total(server: &RunningServer) -> Result<u64, Box<dyn Error>> {
let metrics = server.metrics()?;
let line = metrics
.lines()
.find(|line| line.starts_with("liminal_advised_total"))
.ok_or_else(|| format!("no liminal_advised_total in:\n{metrics}"))?;
Ok(line
.rsplit(' ')
.next()
.ok_or("liminal_advised_total carries no value")?
.parse()?)
}
#[test]
fn access011_advise_lets_a_grantless_ticket_act_and_counts_every_advised_act()
-> Result<(), Box<dyn Error>> {
let dir = tempfile::tempdir()?;
let server = RunningServer::start_with(dir.path(), "decisions = \"advise\"")?;
let (_worker, _inputs) = answering_worker(server.address, 1)?;
let seat = seat_with_pass(
dir.path(),
"grantless",
server.address,
&lys_pass_with(&[])?,
)?;
let mut host = Host::open(Ticket::spawn(&seat)?, true)?;
let listed = host.tool("list", &json!({}))?;
assert_eq!(listed["isError"], false, "{listed}");
assert!(
listed["structuredContent"]["stations"]
.to_string()
.contains(SUBJECT),
"{listed}"
);
let answer = host.tool(
"call",
&json!({ "subject": SUBJECT, "input": { "text": "advised" } }),
)?;
assert_eq!(answer["isError"], false, "{answer}");
assert_eq!(
answer["structuredContent"]["echoed"]["text"], "advised",
"{answer}"
);
let published = host.tool(
"send",
&json!({ "subject": CHANNEL, "text": "{\"note\":\"advised\"}", "key": "advised-1" }),
)?;
assert_eq!(published["isError"], false, "{published}");
let (stderr, code) = host.ticket.close()?;
assert_eq!(code, 0, "{stderr}");
let advised = advised_total(&server)?;
assert!(
advised >= 2,
"the subscribe behind list and the call were advised: liminal_advised_total {advised}"
);
Ok(())
}
#[test]
fn access011_enforce_refuses_the_same_grantless_ticket_by_the_grant_it_needs()
-> Result<(), Box<dyn Error>> {
let dir = tempfile::tempdir()?;
let server = RunningServer::start(dir.path())?;
let seat = seat_with_pass(
dir.path(),
"grantless",
server.address,
&lys_pass_with(&[])?,
)?;
let mut host = Host::open(Ticket::spawn(&seat)?, true)?;
let listed = host.tool("list", &json!({}))?;
assert_eq!(listed["isError"], true, "{listed}");
let words = listed.to_string();
assert!(
words.contains("console.topology") && words.contains("subscribe"),
"{listed}"
);
let (_, code) = host.ticket.close()?;
assert_ne!(code, i32::MIN, "the ticket exited");
assert_eq!(advised_total(&server)?, 0, "enforce advises nothing");
Ok(())
}
#[test]
fn access011_an_expired_pass_is_refused_under_advise_exactly_as_under_enforce()
-> Result<(), Box<dyn Error>> {
let dir = tempfile::tempdir()?;
let (advise_dir, enforce_dir) = (dir.path().join("advise"), dir.path().join("enforce"));
std::fs::create_dir_all(&advise_dir)?;
std::fs::create_dir_all(&enforce_dir)?;
let advise = RunningServer::start_with(&advise_dir, "decisions = \"advise\"")?;
let enforce = RunningServer::start_with(&enforce_dir, "")?;
let mut refusals = Vec::new();
for (name, server) in [("advise", &advise), ("enforce", &enforce)] {
let seat = seat_with_pass(
dir.path(),
name,
server.address,
&lys_pass_expired("agent-late")?,
)?;
let mut host = Host::open(Ticket::spawn(&seat)?, true)?;
let listed = host.tool("list", &json!({}))?;
assert_eq!(listed["isError"], true, "{name}: {listed}");
let words = listed["structuredContent"]["error"]
.as_str()
.or_else(|| listed["content"][0]["text"].as_str())
.ok_or_else(|| format!("{name}: no refusal words in {listed}"))?
.to_owned();
assert!(words.contains("pass_expired"), "{name}: {words}");
refusals.push(words);
drop(host.ticket.close()?);
}
assert_eq!(
refusals[0], refusals[1],
"the words do not depend on decisions"
);
assert_eq!(
advised_total(&advise)?,
0,
"an unverifiable pass is never advised"
);
Ok(())
}