use std::fs;
use runewarp::{
ClientPublicCertConfig, ClientRuntimeArgs, ClientSettingsResolutionDefaults, ClientTlsMode,
LogLevel, SelectedClientConfig, load_client_settings, resolve_selected_client_settings,
};
use tempfile::tempdir;
#[test]
fn client_settings_accept_exact_match_services_and_default_log_level_to_info() {
let tempdir = tempdir().unwrap();
fs::create_dir(tempdir.path().join("client-identity")).unwrap();
fs::write(
tempdir.path().join("client-identity/client.crt"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client.key"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client-identity.txt"),
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
)
.unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "Tunnel.Example.Test."
identity-dir = "client-identity"
[[client.services]]
public-hostnames = ["App.Example.Test.", "api.example.test"]
backend-address = "localhost:8443"
"#,
)
.unwrap();
let settings = load_client_settings(&tempdir.path().join("config.toml")).unwrap();
assert_eq!(settings.server_hostname, "tunnel.example.test");
assert_eq!(settings.server_port, 443);
assert_eq!(settings.log_level, LogLevel::Info);
assert_eq!(
settings.services[0].public_hostnames,
Some(vec![
"app.example.test".to_owned(),
"api.example.test".to_owned(),
])
);
}
#[test]
fn client_settings_accept_server_address_with_default_port_and_flat_identity_dir() {
let tempdir = tempdir().unwrap();
fs::create_dir(tempdir.path().join("client-identity")).unwrap();
fs::write(
tempdir.path().join("client-identity/client.crt"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client.key"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client-identity.txt"),
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
)
.unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "Tunnel.Example.Test."
identity-dir = "client-identity"
[[client.services]]
public-hostnames = ["App.Example.Test.", "api.example.test"]
backend-address = "localhost:8443"
"#,
)
.unwrap();
let settings = load_client_settings(&tempdir.path().join("config.toml")).unwrap();
assert_eq!(settings.server_hostname, "tunnel.example.test");
assert_eq!(settings.server_port, 443);
assert_eq!(
settings.identity_directory,
tempdir.path().join("client-identity")
);
assert_eq!(settings.log_level, LogLevel::Info);
assert_eq!(
settings.services[0].public_hostnames,
Some(vec![
"app.example.test".to_owned(),
"api.example.test".to_owned(),
])
);
}
#[test]
fn client_settings_accept_server_address_with_an_explicit_port() {
let tempdir = tempdir().unwrap();
fs::create_dir(tempdir.path().join("client-identity")).unwrap();
fs::write(
tempdir.path().join("client-identity/client.crt"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client.key"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client-identity.txt"),
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
)
.unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "Tunnel.Example.Test.:9443"
identity-dir = "client-identity"
[[client.services]]
backend-address = "localhost:8443"
"#,
)
.unwrap();
let settings = load_client_settings(&tempdir.path().join("config.toml")).unwrap();
assert_eq!(settings.server_hostname, "tunnel.example.test");
assert_eq!(settings.server_port, 9443);
}
#[test]
fn client_settings_accept_top_level_log_level_values() {
for (raw_level, expected_level) in [
("off", LogLevel::Off),
("error", LogLevel::Error),
("warn", LogLevel::Warn),
("info", LogLevel::Info),
("debug", LogLevel::Debug),
("trace", LogLevel::Trace),
] {
let tempdir = tempdir().unwrap();
fs::create_dir(tempdir.path().join("client-identity")).unwrap();
fs::write(
tempdir.path().join("client-identity/client.crt"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client.key"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client-identity.txt"),
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
)
.unwrap();
fs::write(
tempdir.path().join("config.toml"),
format!(
r#"
log-level = "{raw_level}"
[client]
server-address = "Tunnel.Example.Test."
identity-dir = "client-identity"
[[client.services]]
backend-address = "localhost:8443"
"#
),
)
.unwrap();
let settings = load_client_settings(&tempdir.path().join("config.toml")).unwrap();
assert_eq!(settings.log_level, expected_level);
}
}
#[test]
fn client_settings_reject_legacy_client_logs_as_an_unknown_field() {
let tempdir = tempdir().unwrap();
fs::create_dir(tempdir.path().join("client-identity")).unwrap();
fs::write(
tempdir.path().join("client-identity/client.crt"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client.key"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client-identity.txt"),
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
)
.unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "Tunnel.Example.Test."
identity-dir = "client-identity"
logs = false
[[client.services]]
backend-address = "localhost:8443"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
let message = error.to_string();
assert!(!message.contains("failed to parse [client]"));
assert!(message.contains("unknown field `logs`"));
}
#[test]
fn client_settings_reject_ip_literals_in_server_address() {
let tempdir = tempdir().unwrap();
fs::create_dir(tempdir.path().join("client-identity")).unwrap();
fs::write(
tempdir.path().join("client-identity/client.crt"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client.key"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client-identity.txt"),
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
)
.unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "127.0.0.1:443"
identity-dir = "client-identity"
[[client.services]]
backend-address = "localhost:8443"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
assert!(
error
.to_string()
.contains("client.server-address is invalid: IP literals are not supported")
);
}
#[test]
fn client_settings_reject_invalid_ports_in_server_address() {
let tempdir = tempdir().unwrap();
fs::create_dir(tempdir.path().join("client-identity")).unwrap();
fs::write(
tempdir.path().join("client-identity/client.crt"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client.key"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client-identity.txt"),
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
)
.unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test:65536"
identity-dir = "client-identity"
[[client.services]]
backend-address = "localhost:8443"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
assert!(
error
.to_string()
.contains("client.server-address is invalid: port must be a valid u16")
);
}
#[test]
fn client_settings_report_all_selected_mode_errors_together() {
let tempdir = tempdir().unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
identity-dir = "missing-client"
reconnect-interval = 0
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "caddy.local"
[[client.services]]
backend-address = "127.0.0.1:443"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
let message = error.to_string();
assert!(message.contains("client.server-address is required"));
assert!(message.contains("client.identity-dir directory not found"));
assert!(message.contains("unknown field `reconnect-interval`"));
assert!(message.contains(
"client.services[].public-hostnames may be omitted only when there is exactly one service"
));
assert!(
message
.contains("client.services[].backend-address must be a TCP address or host:port pair")
);
}
#[test]
fn client_settings_reject_the_legacy_flat_client_surface() {
let tempdir = tempdir().unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
cert-file = "client.crt"
key-file = "client.key"
[[client.services]]
local-addr = "127.0.0.1:443"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
let message = error.to_string();
assert!(!message.contains("failed to parse [client]"));
assert!(message.contains("unknown field `cert-file`"));
assert!(message.contains("unknown field `key-file`"));
assert!(message.contains("unknown field `local-addr`"));
assert!(message.contains("client.identity-dir directory not found"));
assert!(message.contains("client.services[].backend-address is required"));
}
#[test]
fn client_settings_reject_server_ca_file_without_ca_file_trust_mode() {
let tempdir = tempdir().unwrap();
fs::create_dir(tempdir.path().join("client-identity")).unwrap();
fs::write(
tempdir.path().join("client-identity/client.crt"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client.key"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client-identity.txt"),
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
)
.unwrap();
fs::write(tempdir.path().join("server-ca.pem"), "placeholder").unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
server-ca-file = "server-ca.pem"
identity-dir = "client-identity"
[[client.services]]
backend-address = "localhost:8443"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
assert!(
error.to_string().contains(
"client.server-ca-file may be set only when client.server-trust = \"ca-file\""
)
);
}
#[test]
fn client_settings_accept_ca_file_trust_with_an_explicit_server_ca_file() {
let tempdir = tempdir().unwrap();
fs::create_dir(tempdir.path().join("client-identity")).unwrap();
fs::write(
tempdir.path().join("client-identity/client.crt"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client.key"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client-identity.txt"),
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
)
.unwrap();
fs::write(tempdir.path().join("server-ca.pem"), "placeholder").unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
server-trust = "ca-file"
server-ca-file = "server-ca.pem"
identity-dir = "client-identity"
[[client.services]]
backend-address = "localhost:8443"
"#,
)
.unwrap();
let settings = load_client_settings(&tempdir.path().join("config.toml")).unwrap();
assert_eq!(
settings.server_ca_file,
Some(tempdir.path().join("server-ca.pem"))
);
}
#[test]
fn client_settings_reject_duplicate_public_hostnames_after_normalization() {
let tempdir = tempdir().unwrap();
fs::create_dir(tempdir.path().join("client-identity")).unwrap();
fs::write(
tempdir.path().join("client-identity/client.crt"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client.key"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client-identity.txt"),
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
)
.unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
[[client.services]]
public-hostnames = ["App.Example.Test."]
backend-address = "localhost:8443"
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "nginx.local:443"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
assert!(error.to_string().contains(
"client.services[].public-hostnames must be unique after normalization: app.example.test"
));
}
#[test]
fn client_settings_reject_empty_public_hostname_lists() {
let tempdir = tempdir().unwrap();
fs::create_dir(tempdir.path().join("client-identity")).unwrap();
fs::write(
tempdir.path().join("client-identity/client.crt"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client.key"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client-identity.txt"),
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
)
.unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
[[client.services]]
public-hostnames = []
backend-address = "localhost:8443"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
assert!(
error
.to_string()
.contains("client.services[].public-hostnames must not be empty")
);
}
#[test]
fn client_settings_report_duplicate_hostnames_even_when_another_hostname_is_invalid() {
let tempdir = tempdir().unwrap();
fs::create_dir(tempdir.path().join("client-identity")).unwrap();
fs::write(
tempdir.path().join("client-identity/client.crt"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client.key"),
"placeholder",
)
.unwrap();
fs::write(
tempdir.path().join("client-identity/client-identity.txt"),
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
)
.unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
[[client.services]]
public-hostnames = ["App.Example.Test."]
backend-address = "localhost:8443"
[[client.services]]
public-hostnames = ["app.example.test", "*.bad.example.test"]
backend-address = "nginx.local:443"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
let message = error.to_string();
assert!(message.contains(
"client.services[].public-hostnames contains invalid hostname `*.bad.example.test`"
));
assert!(message.contains(
"client.services[].public-hostnames must be unique after normalization: app.example.test"
));
}
fn write_base_client_identity(tempdir: &std::path::Path) {
fs::create_dir(tempdir.join("client-identity")).unwrap();
fs::write(tempdir.join("client-identity/client.crt"), "placeholder").unwrap();
fs::write(tempdir.join("client-identity/client.key"), "placeholder").unwrap();
fs::write(
tempdir.join("client-identity/client-identity.txt"),
"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
)
.unwrap();
}
#[test]
fn client_settings_default_tls_mode_is_passthrough() {
let tempdir = tempdir().unwrap();
write_base_client_identity(tempdir.path());
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "localhost:8443"
"#,
)
.unwrap();
let settings = load_client_settings(&tempdir.path().join("config.toml")).unwrap();
assert_eq!(settings.services[0].tls_mode, ClientTlsMode::Passthrough);
}
#[test]
fn client_settings_accept_explicit_passthrough_tls_mode() {
let tempdir = tempdir().unwrap();
write_base_client_identity(tempdir.path());
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "localhost:8443"
tls-mode = "passthrough"
"#,
)
.unwrap();
let settings = load_client_settings(&tempdir.path().join("config.toml")).unwrap();
assert_eq!(settings.services[0].tls_mode, ClientTlsMode::Passthrough);
}
#[test]
fn client_settings_reject_unknown_tls_mode_value() {
let tempdir = tempdir().unwrap();
write_base_client_identity(tempdir.path());
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "localhost:8443"
tls-mode = "proxy"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
assert!(
error
.to_string()
.contains("client.services[].tls-mode must be \"passthrough\" or \"terminate\""),
"unexpected error: {error}"
);
}
#[test]
fn client_settings_reject_terminate_on_catch_all_service() {
let tempdir = tempdir().unwrap();
write_base_client_identity(tempdir.path());
fs::create_dir(tempdir.path().join("certs")).unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
public-cert-dir = "certs"
[[client.services]]
backend-address = "localhost:8443"
tls-mode = "terminate"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
assert!(
error.to_string().contains(
"client.services[].tls-mode = \"terminate\" requires explicit public-hostnames"
),
"unexpected error: {error}"
);
}
#[test]
fn client_settings_use_the_default_public_cert_dir_for_terminate_mode_when_acme_is_absent() {
let tempdir = tempdir().unwrap();
write_base_client_identity(tempdir.path());
let default_public_cert_directory = tempdir.path().join("xdg-data/client/public-cert");
fs::create_dir_all(&default_public_cert_directory).unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "localhost:8443"
tls-mode = "terminate"
"#,
)
.unwrap();
let settings = resolve_selected_client_settings(
SelectedClientConfig::Explicit(tempdir.path().join("config.toml")),
&ClientRuntimeArgs::default(),
&ClientSettingsResolutionDefaults {
identity_directory: tempdir.path().join("client-identity"),
public_cert_directory: default_public_cert_directory.clone(),
},
)
.unwrap();
assert!(
matches!(
settings.public_cert_config,
Some(ClientPublicCertConfig::Manual { ref directory }) if *directory == default_public_cert_directory
),
"unexpected public cert config: {:?}",
settings.public_cert_config
);
}
#[test]
fn client_settings_accept_terminate_with_public_cert_dir() {
let tempdir = tempdir().unwrap();
write_base_client_identity(tempdir.path());
fs::create_dir(tempdir.path().join("certs")).unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
public-cert-dir = "certs"
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "localhost:8443"
tls-mode = "terminate"
"#,
)
.unwrap();
let settings = load_client_settings(&tempdir.path().join("config.toml")).unwrap();
assert_eq!(settings.services[0].tls_mode, ClientTlsMode::Terminate);
assert!(
matches!(
&settings.public_cert_config,
Some(runewarp::ClientPublicCertConfig::Manual { directory }) if *directory == tempdir.path().join("certs")
),
"expected Manual cert config, got {:?}",
settings.public_cert_config
);
}
#[test]
fn client_settings_reject_public_cert_dir_not_found() {
let tempdir = tempdir().unwrap();
write_base_client_identity(tempdir.path());
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
public-cert-dir = "missing-certs"
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "localhost:8443"
tls-mode = "terminate"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
assert!(
error
.to_string()
.contains("client.public-cert-dir directory not found"),
"unexpected error: {error}"
);
}
#[test]
fn client_settings_reject_public_cert_dir_without_any_terminating_service() {
let tempdir = tempdir().unwrap();
write_base_client_identity(tempdir.path());
fs::create_dir(tempdir.path().join("certs")).unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
public-cert-dir = "certs"
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "localhost:8443"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
assert!(
error.to_string().contains(
"client.public-cert-dir and [client.acme] require at least one service with tls-mode = \"terminate\""
),
"unexpected error: {error}"
);
}
#[test]
fn client_settings_reject_acme_without_any_terminating_service() {
let tempdir = tempdir().unwrap();
write_base_client_identity(tempdir.path());
fs::create_dir(tempdir.path().join("acme-state")).unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
[client.acme]
email = "admin@example.test"
state-dir = "acme-state"
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "localhost:8443"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
assert!(
error.to_string().contains(
"client.public-cert-dir and [client.acme] require at least one service with tls-mode = \"terminate\""
),
"unexpected error: {error}"
);
}
#[test]
fn client_settings_accept_terminate_with_acme_and_explicit_state_dir() {
let tempdir = tempdir().unwrap();
write_base_client_identity(tempdir.path());
fs::create_dir(tempdir.path().join("acme-state")).unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
[client.acme]
email = "admin@example.test"
state-dir = "acme-state"
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "localhost:8443"
tls-mode = "terminate"
"#,
)
.unwrap();
let settings = load_client_settings(&tempdir.path().join("config.toml")).unwrap();
assert_eq!(settings.services[0].tls_mode, ClientTlsMode::Terminate);
assert!(
matches!(
&settings.public_cert_config,
Some(runewarp::ClientPublicCertConfig::Acme { email, .. }) if email == "admin@example.test"
),
"expected ACME cert config, got {:?}",
settings.public_cert_config
);
}
#[test]
fn client_settings_reject_acme_without_email() {
let tempdir = tempdir().unwrap();
write_base_client_identity(tempdir.path());
fs::create_dir(tempdir.path().join("acme-state")).unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
[client.acme]
state-dir = "acme-state"
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "localhost:8443"
tls-mode = "terminate"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
assert!(
error.to_string().contains("client.acme.email is required"),
"unexpected error: {error}"
);
}
#[test]
fn client_settings_reject_acme_state_dir_not_found() {
let tempdir = tempdir().unwrap();
write_base_client_identity(tempdir.path());
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
[client.acme]
email = "admin@example.test"
state-dir = "missing-acme-state"
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "localhost:8443"
tls-mode = "terminate"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
assert!(
error
.to_string()
.contains("client.acme.state-dir directory not found"),
"unexpected error: {error}"
);
}
#[test]
fn client_settings_reject_both_public_cert_dir_and_acme() {
let tempdir = tempdir().unwrap();
write_base_client_identity(tempdir.path());
fs::create_dir(tempdir.path().join("certs")).unwrap();
fs::create_dir(tempdir.path().join("acme-state")).unwrap();
fs::write(
tempdir.path().join("config.toml"),
r#"
[client]
server-address = "tunnel.example.test"
identity-dir = "client-identity"
public-cert-dir = "certs"
[client.acme]
email = "admin@example.test"
state-dir = "acme-state"
[[client.services]]
public-hostnames = ["app.example.test"]
backend-address = "localhost:8443"
"#,
)
.unwrap();
let error = load_client_settings(&tempdir.path().join("config.toml")).unwrap_err();
assert!(
error
.to_string()
.contains("[client.acme] and client.public-cert-dir are mutually exclusive"),
"unexpected error: {error}"
);
}