use super::root_presence::LysRoot;
use super::root_presence_start::{RootQuestion, RootSlot, root_question};
use super::root_presence_tests::{
Capture, Lines, NONE_HOLDS, ONE_HOLDS, TestResult, issuer, runtime,
};
use std::net::SocketAddr;
use std::sync::Arc;
use tracing_subscriber::prelude::*;
fn lys(
api: &str,
credential: Option<&std::path::Path>,
) -> TestResult<crate::config::types::LysConfig> {
let credential = credential.map_or_else(String::new, |path| {
format!("app_credential_path = {:?}\n", path.display().to_string())
});
let text = format!(
"listen_address = \"127.0.0.1:1\"\nhealth_listen_address = \"127.0.0.1:2\"\ndrain_timeout_ms = 5000\n\
routing_rules = []\n\n[[channels]]\nname = \"a\"\ndurable = false\n\n\
[auth]\ntoken = \"t\"\n\n[auth.lys]\nissuer = \"https://lys.test\"\n\
network_id = \"ablative\"\nkeys_path = \"/k\"\napi_url = \"{api}\"\n{credential}"
);
let config: crate::config::types::ServerConfig = toml::from_str(&text)?;
Ok(config
.auth
.and_then(|auth| auth.lys)
.ok_or("no [auth.lys]")?)
}
type Reported = (&'static str, Option<&'static str>);
fn started(at: SocketAddr) -> TestResult<(Reported, Vec<tracing::Level>)> {
let dir = tempfile::tempdir()?;
let path = dir.path().join("app-credential");
std::fs::write(&path, "fixture-app-credential\n")?;
let runtime = runtime()?;
let config = lys(&format!("http://{at}/api/"), Some(&path))?;
let RootQuestion::AtStart(root) = root_question(&config, Some(runtime.handle().clone()))?
else {
return Err("a server with no grant stream and a credential asks at start".into());
};
assert_eq!(root.answer(), LysRoot::NotAsked);
let lines = Arc::new(Lines::default());
let subscriber = tracing_subscriber::registry().with(Capture(Arc::clone(&lines)));
tracing::subscriber::with_default(subscriber, || root.ask());
let levels = lines
.0
.lock()
.map_err(|_| "captured lines poisoned")?
.iter()
.map(|(level, _)| *level)
.collect();
Ok((RootSlot::Asked(root).health(), levels))
}
#[test]
fn a_server_without_a_grant_stream_reports_an_absent_root() -> TestResult {
let (at, asks) = issuer(vec![(200, NONE_HOLDS)])?;
let (health, levels) = started(at)?;
assert_eq!(health, ("absent", None));
assert!(asks.try_recv()?.starts_with("POST /api/grants/held "));
assert!(asks.try_recv().is_err(), "exactly one held ask");
assert_eq!(levels, vec![tracing::Level::ERROR]);
Ok(())
}
#[test]
fn a_server_without_a_grant_stream_reports_a_held_root_silently() -> TestResult {
let (at, asks) = issuer(vec![(200, ONE_HOLDS)])?;
let (health, levels) = started(at)?;
assert_eq!(health, ("held", None));
assert!(asks.try_recv().is_ok());
assert!(asks.try_recv().is_err(), "exactly one held ask");
assert!(levels.is_empty(), "a held root logs nothing: {levels:?}");
Ok(())
}
#[test]
fn a_server_with_a_grant_stream_installs_one_root_question() -> TestResult {
let template = crate::link::certificate::setup::DEVICE_TEMPLATE
.replace("LYS_HOST", "issuer.example")
.replace("BROKER_HOST", "broker.example")
.replace("BROKER_IP", "127.0.0.1")
.replace("RELAY_HOST", "relay.example")
.replace("OFFICE_ED25519_PUBLIC_KEY_64_HEX", &"01".repeat(32))
.replace("ADMISSION_RESOURCE_KIND", "liminal.network")
.replace("ADMISSION_RESOURCE_ID", "office")
.replace("ADMISSION_ACTION", "call");
let config: crate::config::types::ServerConfig = toml::from_str(&template)?;
let lys = config
.auth
.and_then(|auth| auth.lys)
.ok_or("no [auth.lys]")?;
assert!(lys.grant_stream.is_some());
let runtime = runtime()?;
assert!(matches!(
root_question(&lys, Some(runtime.handle().clone()))?,
RootQuestion::Owner
));
Ok(())
}
#[test]
fn health_names_an_unasked_root_with_its_cause() -> TestResult {
let runtime = runtime()?;
let question = root_question(
&lys("https://lys.invalid/api/", None)?,
Some(runtime.handle().clone()),
)?;
let RootQuestion::Unasked(cause) = question else {
return Err("a server with no credential cannot ask".into());
};
assert_eq!(cause, "no_asking_credential");
assert_eq!(
RootSlot::Unasked { cause }.health(),
("not_asked", Some("no_asking_credential"))
);
Ok(())
}