use std::{fs::File as StdFile, io::Write};
use config::{Config, File, FileFormat};
use tempfile::tempdir;
use crate::config::{
DatabaseType, LogLevel, apply_env_overrides_from, parse_bindizr_config_with_env,
};
fn create_temp_config_file(content: &str) -> (tempfile::TempDir, String) {
let dir = tempdir().unwrap();
let config_path = dir.path().join("bindizr.conf.toml");
let mut file = StdFile::create(&config_path).unwrap();
write!(file, "{}", content).unwrap();
file.flush().unwrap();
(dir, config_path.to_str().unwrap().to_string())
}
#[test]
fn parse_bindizr_config_accepts_valid_config() {
let (dir, config_path) = create_temp_config_file(
r#"
[api]
listen_addr = "127.0.0.1"
listen_port = 3000
require_authentication = false
[database]
type = "sqlite"
[database.mysql]
server_url = ""
[database.sqlite]
file_path = "file::memory:?cache=shared"
[database.postgresql]
server_url = ""
[dns]
listen_addr = "127.0.0.1"
listen_port = 53
secondary_addrs = "127.0.0.1:53"
notify_after_update = false
notify_on_startup = true
notify_retries = 4
notify_timeout_secs = 9
nsupdate_tsig_key_name = "nsupdate-key"
nsupdate_tsig_key = ""
[logging]
log_level = "debug"
"#,
);
let config = Config::builder()
.add_source(File::new(&config_path, FileFormat::Toml))
.build()
.unwrap();
let parsed = parse_bindizr_config_with_env(config, |_| None).unwrap();
assert_eq!(parsed.api.listen_addr.to_string(), "127.0.0.1");
assert_eq!(parsed.dns.listen_addr.to_string(), "127.0.0.1");
assert!(matches!(
parsed.database.database_type,
DatabaseType::Sqlite
));
assert_eq!(parsed.api.listen_port, 3000);
assert!(!parsed.dns.notify_after_update);
assert!(parsed.dns.notify_on_startup);
assert_eq!(parsed.dns.notify_retries, 4);
assert_eq!(parsed.dns.notify_timeout_secs, 9);
assert_eq!(parsed.dns.nsupdate_tsig_key_name, "nsupdate-key");
drop(dir);
}
#[test]
fn parse_bindizr_config_defaults_missing_nsupdate_tsig_key() {
let (dir, config_path) = create_temp_config_file(
r#"
[api]
listen_addr = "127.0.0.1"
listen_port = 3000
require_authentication = false
[database]
type = "sqlite"
[database.mysql]
server_url = ""
[database.sqlite]
file_path = "file::memory:?cache=shared"
[database.postgresql]
server_url = ""
[dns]
listen_addr = "127.0.0.1"
listen_port = 53
secondary_addrs = "127.0.0.1:53"
[logging]
log_level = "debug"
"#,
);
let config = Config::builder()
.add_source(File::new(&config_path, FileFormat::Toml))
.build()
.unwrap();
let parsed = parse_bindizr_config_with_env(config, |_| None).unwrap();
assert_eq!(parsed.dns.nsupdate_tsig_key, "");
assert!(parsed.dns.notify_after_update);
assert!(!parsed.dns.notify_on_startup);
assert_eq!(parsed.dns.notify_retries, 3);
assert_eq!(parsed.dns.notify_timeout_secs, 5);
assert_eq!(parsed.dns.nsupdate_tsig_key_name, "");
drop(dir);
}
#[test]
fn parse_bindizr_config_defaults_unselected_database_sections() {
let (dir, config_path) = create_temp_config_file(
r#"
[api]
listen_addr = "127.0.0.1"
listen_port = 3000
require_authentication = false
[database]
type = "sqlite"
[database.sqlite]
file_path = "file::memory:?cache=shared"
[dns]
listen_addr = "127.0.0.1"
listen_port = 53
secondary_addrs = "127.0.0.1:53"
nsupdate_tsig_key = ""
[logging]
log_level = "debug"
"#,
);
let config = Config::builder()
.add_source(File::new(&config_path, FileFormat::Toml))
.build()
.unwrap();
let parsed = parse_bindizr_config_with_env(config, |_| None).unwrap();
assert_eq!(
parsed.database.sqlite.file_path,
"file::memory:?cache=shared"
);
assert_eq!(parsed.database.mysql.server_url, "");
assert_eq!(parsed.database.postgresql.server_url, "");
drop(dir);
}
#[test]
fn parse_bindizr_config_rejects_invalid_listen_addr() {
let (dir, config_path) = create_temp_config_file(
r#"
[api]
listen_addr = "not-an-ip"
listen_port = 3000
require_authentication = false
[database]
type = "sqlite"
[database.mysql]
server_url = ""
[database.sqlite]
file_path = "file::memory:?cache=shared"
[database.postgresql]
server_url = ""
[dns]
listen_addr = "127.0.0.1"
listen_port = 53
secondary_addrs = ""
nsupdate_tsig_key = ""
[logging]
log_level = "debug"
"#,
);
let config = Config::builder()
.add_source(File::new(&config_path, FileFormat::Toml))
.build()
.unwrap();
let err = parse_bindizr_config_with_env(config, |_| None).unwrap_err();
assert!(err.contains("Invalid Bindizr configuration"));
drop(dir);
}
#[test]
fn parse_bindizr_config_rejects_empty_selected_database_url() {
let (dir, config_path) = create_temp_config_file(
r#"
[api]
listen_addr = "127.0.0.1"
listen_port = 3000
require_authentication = false
[database]
type = "mysql"
[database.mysql]
server_url = ""
[database.sqlite]
file_path = "file::memory:?cache=shared"
[database.postgresql]
server_url = ""
[dns]
listen_addr = "127.0.0.1"
listen_port = 53
secondary_addrs = ""
nsupdate_tsig_key = ""
[logging]
log_level = "debug"
"#,
);
let config = Config::builder()
.add_source(File::new(&config_path, FileFormat::Toml))
.build()
.unwrap();
let err = parse_bindizr_config_with_env(config, |_| None).unwrap_err();
assert!(err.contains("database.mysql.server_url must not be empty"));
drop(dir);
}
#[test]
fn apply_env_overrides_replaces_config_values_before_validation() {
let (dir, config_path) = create_temp_config_file(
r#"
[api]
listen_addr = "127.0.0.1"
listen_port = 3000
require_authentication = true
[database]
type = "sqlite"
[database.mysql]
server_url = ""
[database.sqlite]
file_path = "file::memory:?cache=shared"
[database.postgresql]
server_url = ""
[dns]
listen_addr = "127.0.0.1"
listen_port = 53
secondary_addrs = ""
nsupdate_tsig_key = ""
[logging]
log_level = "debug"
"#,
);
let config = Config::builder()
.add_source(File::new(&config_path, FileFormat::Toml))
.build()
.unwrap();
let parsed = parse_bindizr_config_with_env(config, |_| None).unwrap();
let mut overridden = parsed.clone();
apply_env_overrides_from(&mut overridden, |name| match name {
"BINDIZR_API_LISTEN_ADDR" => Some("0.0.0.0".to_string()),
"BINDIZR_API_PORT" => Some("8000".to_string()),
"BINDIZR_API_REQUIRE_AUTHENTICATION" => Some("false".to_string()),
"BINDIZR_DATABASE_TYPE" => Some("mysql".to_string()),
"BINDIZR_DATABASE_URL" => Some("mysql://user:p#ss&word@mysql:3306/bindizr".to_string()),
"BINDIZR_DNS_LISTEN_ADDR" => Some("127.0.0.2".to_string()),
"BINDIZR_DNS_PORT" => Some("5353".to_string()),
"BINDIZR_SECONDARY_ADDRS" => Some("192.0.2.10:53,192.0.2.11:53".to_string()),
"BINDIZR_NSUPDATE_TSIG_KEY" => Some("secret#with&chars".to_string()),
"BINDIZR_NSUPDATE_TSIG_KEY_NAME" => Some("nsupdate-key".to_string()),
"BINDIZR_NOTIFY_AFTER_UPDATE" => Some("false".to_string()),
"BINDIZR_NOTIFY_ON_STARTUP" => Some("true".to_string()),
"BINDIZR_NOTIFY_RETRIES" => Some("7".to_string()),
"BINDIZR_NOTIFY_TIMEOUT_SECS" => Some("11".to_string()),
"BINDIZR_LOG_LEVEL" => Some("info".to_string()),
_ => None,
})
.unwrap();
assert_eq!(overridden.api.listen_addr.to_string(), "0.0.0.0");
assert_eq!(overridden.api.listen_port, 8000);
assert!(!overridden.api.require_authentication);
assert!(matches!(
overridden.database.database_type,
DatabaseType::Mysql
));
assert_eq!(
overridden.database.mysql.server_url,
"mysql://user:p#ss&word@mysql:3306/bindizr"
);
assert_eq!(overridden.dns.listen_addr.to_string(), "127.0.0.2");
assert_eq!(overridden.dns.listen_port, 5353);
assert_eq!(
overridden.dns.secondary_addrs,
"192.0.2.10:53,192.0.2.11:53"
);
assert_eq!(overridden.dns.nsupdate_tsig_key, "secret#with&chars");
assert_eq!(overridden.dns.nsupdate_tsig_key_name, "nsupdate-key");
assert!(!overridden.dns.notify_after_update);
assert!(overridden.dns.notify_on_startup);
assert_eq!(overridden.dns.notify_retries, 7);
assert_eq!(overridden.dns.notify_timeout_secs, 11);
assert!(matches!(overridden.logging.log_level, LogLevel::Info));
drop(dir);
}
#[test]
fn apply_env_overrides_rejects_invalid_values() {
let (dir, config_path) = create_temp_config_file(
r#"
[api]
listen_addr = "127.0.0.1"
listen_port = 3000
require_authentication = false
[database]
type = "sqlite"
[database.sqlite]
file_path = "file::memory:?cache=shared"
[dns]
listen_addr = "127.0.0.1"
listen_port = 53
secondary_addrs = ""
nsupdate_tsig_key = ""
[logging]
log_level = "debug"
"#,
);
let config = Config::builder()
.add_source(File::new(&config_path, FileFormat::Toml))
.build()
.unwrap();
let parsed = parse_bindizr_config_with_env(config, |_| None).unwrap();
let mut overridden = parsed.clone();
let err = apply_env_overrides_from(&mut overridden, |name| match name {
"BINDIZR_API_PORT" => Some("not-a-port".to_string()),
_ => None,
})
.unwrap_err();
assert!(err.contains("Invalid BINDIZR_API_PORT environment variable"));
drop(dir);
}