use zyris::enroll::protocol::{classify_refresh_error, ErrorResponse};
use zyris::ConnectError;
use zyris::{EnrollError, RegisterError, RotateError};
fn refusing_server(status_line: &'static str) -> String {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("no loopback port");
let address = listener.local_addr().expect("no local address");
std::thread::spawn(move || {
for stream in listener.incoming() {
use std::io::{Read, Write};
let Ok(mut stream) = stream else { return };
let _ = stream.read(&mut [0u8; 4096]);
let response =
format!("HTTP/1.1 {status_line}\r\ncontent-length: 0\r\nconnection: close\r\n\r\n");
let _ = stream.write_all(response.as_bytes());
let _ = stream.flush();
}
});
format!("ws://{address}/zyris/v1/ws")
}
#[tokio::test]
async fn a_token_the_server_will_not_take_is_not_a_server_that_could_not_be_reached() {
let url = refusing_server("401 Unauthorized");
let Err(refused) = zyris::transport::ws::connect(&url, "znt_wrong").await else {
panic!("the server answered 401; that upgrade cannot have succeeded")
};
assert!(
matches!(ConnectError::from(refused), ConnectError::Unauthorized),
"a 401 says the token was judged and refused"
);
}
#[tokio::test]
async fn a_deployment_this_build_cannot_speak_to_says_so_in_the_error() {
let url = refusing_server("426 Upgrade Required");
let Err(refused) = zyris::transport::ws::connect(&url, "znt_fine").await else {
panic!("the server answered 426; that upgrade cannot have succeeded")
};
let error = ConnectError::from(refused);
let ConnectError::VersionMismatch { ours, .. } = &error else {
panic!("expected a version mismatch, got {error}")
};
assert_eq!(ours, "1", "the version this build speaks has to be in the message");
assert!(error.to_string().contains("protocol mismatch"), "got {error}");
}
#[tokio::test]
async fn a_socket_that_never_opened_is_reported_as_unreachable() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("no loopback port");
let address = listener.local_addr().expect("no local address");
drop(listener);
let url = format!("ws://{address}/zyris/v1/ws");
let Err(failed) = zyris::transport::ws::connect(&url, "znt_fine").await else {
panic!("nothing is listening on that port")
};
assert!(
matches!(ConnectError::from(failed), ConnectError::Unreachable(_)),
"a closed port says nothing about the credential"
);
}
fn refusal(error: &str, status: u16) -> ErrorResponse {
ErrorResponse {
error: error.to_string(),
error_description: Some("because".to_string()),
interval: None,
status: Some(status),
}
}
#[test]
fn a_five_hundred_during_a_deploy_is_not_reported_as_a_revoked_credential() {
let outage = ConnectError::from(classify_refresh_error(&refusal("invalid_grant", 500)));
assert!(
matches!(outage, ConnectError::Unreachable(_)),
"a server that could not answer has not told us the grant is dead, got {outage}"
);
let disowned = ConnectError::from(classify_refresh_error(&refusal("invalid_grant", 400)));
assert!(
matches!(disowned, ConnectError::Revoked),
"the one answer that needs a person, got {disowned}"
);
assert!(
disowned.to_string().contains("authorize this node again"),
"the message has to say what the person must do, got {disowned}"
);
}
#[test]
fn an_unknown_scope_comes_back_by_name() {
let refused = EnrollError::ScopeUnknown { scope: "nodes:write".to_string() };
assert!(refused.to_string().contains("nodes:write"), "got {refused}");
}
#[test]
fn a_clamped_registration_says_which_scopes_it_could_not_grant() {
let refused = RegisterError::ScopeExceeded {
requested: vec!["agents:read".to_string(), "nodes:write".to_string()],
granted: vec!["agents:read".to_string()],
};
let RegisterError::ScopeExceeded { requested, granted } = &refused else {
panic!("expected a clamp, got {refused}")
};
let refused_scopes: Vec<&str> =
requested.iter().filter(|s| !granted.contains(s)).map(String::as_str).collect();
assert_eq!(refused_scopes, ["nodes:write"]);
}
#[test]
fn a_store_that_refused_a_rotation_keeps_its_own_words() {
let failed = RotateError("the keychain is locked".to_string());
assert!(failed.to_string().contains("the keychain is locked"), "got {failed}");
}