mod common;
use common::EnvGuard;
use parenv::Environment;
use serial_test::serial;
use std::net::SocketAddr;
use std::path::PathBuf;
#[test]
#[serial]
fn single_required_string_field() {
#[derive(Environment)]
struct Config {
name: String,
}
let _guard = EnvGuard::set("NAME", "test_value");
let config = Config::try_parse().unwrap();
assert_eq!(config.name, "test_value");
}
#[test]
#[serial]
fn multiple_required_fields() {
#[derive(Environment)]
struct Config {
host: String,
port: String,
}
let _guard = EnvGuard::set("HOST", "localhost").and_set("PORT", "8080");
let config = Config::try_parse().unwrap();
assert_eq!(config.host, "localhost");
assert_eq!(config.port, "8080");
}
#[test]
#[serial]
fn optional_field_present() {
#[derive(Environment)]
struct Config {
name: String,
debug: Option<String>,
}
let _guard = EnvGuard::set("NAME", "app").and_set("DEBUG", "true");
let config = Config::try_parse().unwrap();
assert_eq!(config.name, "app");
assert_eq!(config.debug, Some("true".to_string()));
}
#[test]
#[serial]
fn optional_field_absent() {
#[derive(Environment)]
struct Config {
name: String,
debug: Option<String>,
}
let _guard = EnvGuard::set("NAME", "app");
let config = Config::try_parse().unwrap();
assert_eq!(config.name, "app");
assert_eq!(config.debug, None);
}
#[test]
#[serial]
fn mixed_required_and_optional() {
#[derive(Environment)]
struct Config {
required_field: String,
optional_field: Option<String>,
another_required: String,
}
let _guard = EnvGuard::set("REQUIRED_FIELD", "value1").and_set("ANOTHER_REQUIRED", "value2");
let config = Config::try_parse().unwrap();
assert_eq!(config.required_field, "value1");
assert_eq!(config.optional_field, None);
assert_eq!(config.another_required, "value2");
}
#[test]
#[serial]
fn parse_u8() {
#[derive(Environment)]
struct Config {
count: u8,
}
let _guard = EnvGuard::set("COUNT", "42");
let config = Config::try_parse().unwrap();
assert_eq!(config.count, 42);
}
#[test]
#[serial]
fn parse_u32() {
#[derive(Environment)]
struct Config {
count: u32,
}
let _guard = EnvGuard::set("COUNT", "123456");
let config = Config::try_parse().unwrap();
assert_eq!(config.count, 123456);
}
#[test]
#[serial]
fn parse_i64() {
#[derive(Environment)]
struct Config {
offset: i64,
}
let _guard = EnvGuard::set("OFFSET", "-9876543210");
let config = Config::try_parse().unwrap();
assert_eq!(config.offset, -9876543210);
}
#[test]
#[serial]
fn parse_pathbuf() {
#[derive(Environment)]
struct Config {
config_path: PathBuf,
}
let _guard = EnvGuard::set("CONFIG_PATH", "/etc/app/config.toml");
let config = Config::try_parse().unwrap();
assert_eq!(config.config_path, PathBuf::from("/etc/app/config.toml"));
}
#[test]
#[serial]
fn parse_socket_addr() {
#[derive(Environment)]
struct Config {
bind_addr: SocketAddr,
}
let _guard = EnvGuard::set("BIND_ADDR", "127.0.0.1:8080");
let config = Config::try_parse().unwrap();
assert_eq!(
config.bind_addr,
"127.0.0.1:8080".parse::<SocketAddr>().unwrap()
);
}
#[test]
#[serial]
fn parse_optional_integer() {
#[derive(Environment)]
struct Config {
timeout: Option<u32>,
}
let _guard = EnvGuard::set("TIMEOUT", "30");
let config = Config::try_parse().unwrap();
assert_eq!(config.timeout, Some(30));
}
#[test]
#[serial]
fn prefix_only() {
#[derive(Environment)]
#[parenv(prefix = "APP_")]
struct Config {
host: String,
port: u16,
}
let _guard = EnvGuard::set("APP_HOST", "localhost").and_set("APP_PORT", "3000");
let config = Config::try_parse().unwrap();
assert_eq!(config.host, "localhost");
assert_eq!(config.port, 3000);
}
#[test]
#[serial]
fn suffix_only() {
#[derive(Environment)]
#[parenv(suffix = "_CONFIG")]
struct Config {
database: String,
cache: String,
}
let _guard = EnvGuard::set("DATABASE_CONFIG", "postgres://localhost/db")
.and_set("CACHE_CONFIG", "redis://localhost");
let config = Config::try_parse().unwrap();
assert_eq!(config.database, "postgres://localhost/db");
assert_eq!(config.cache, "redis://localhost");
}
#[test]
#[serial]
fn prefix_and_suffix() {
#[derive(Environment)]
#[parenv(prefix = "MY_", suffix = "_VAR")]
struct Config {
setting: String,
}
let _guard = EnvGuard::set("MY_SETTING_VAR", "value");
let config = Config::try_parse().unwrap();
assert_eq!(config.setting, "value");
}
#[test]
#[serial]
fn missing_required_field_returns_error() {
#[allow(dead_code)]
#[derive(Debug, Environment)]
struct Config {
required_field: String,
}
let result = Config::try_parse();
assert!(result.is_err());
let errors = result.unwrap_err();
assert_eq!(errors.len(), 1);
}
#[test]
#[serial]
fn invalid_type_conversion_returns_error() {
#[allow(dead_code)]
#[derive(Debug, Environment)]
struct Config {
port: u16,
}
let _guard = EnvGuard::set("PORT", "not_a_number");
let result = Config::try_parse();
assert!(result.is_err());
let errors = result.unwrap_err();
assert_eq!(errors.len(), 1);
}
#[test]
#[serial]
fn multiple_errors_collected() {
#[allow(dead_code)]
#[derive(Debug, Environment)]
struct Config {
field1: String,
field2: u32,
field3: String,
}
let _guard = EnvGuard::set("FIELD2", "invalid");
let result = Config::try_parse();
assert!(result.is_err());
let errors = result.unwrap_err();
assert_eq!(errors.len(), 3);
}
#[test]
#[serial]
fn invalid_optional_type_conversion_returns_error() {
#[allow(dead_code)]
#[derive(Debug, Environment)]
struct Config {
port: Option<u16>,
}
let _guard = EnvGuard::set("PORT", "not_a_number");
let result = Config::try_parse();
assert!(result.is_err());
let errors = result.unwrap_err();
assert_eq!(errors.len(), 1);
}
#[test]
#[serial]
fn prefix_missing_field_returns_error() {
#[allow(dead_code)]
#[derive(Environment)]
#[parenv(prefix = "TEST_")]
struct Config {
value: String,
}
let _guard = EnvGuard::set("VALUE", "test");
let result = Config::try_parse();
assert!(result.is_err());
}
#[test]
#[serial]
fn default_value_used_when_env_missing() {
#[derive(Environment)]
struct Config {
#[parenv(default = "default_name")]
name: String,
}
let config = Config::try_parse().unwrap();
assert_eq!(config.name, "default_name");
}
#[test]
#[serial]
fn default_value_overridden_by_env() {
#[derive(Environment)]
struct Config {
#[parenv(default = "default_name")]
name: String,
}
let _guard = EnvGuard::set("NAME", "env_value");
let config = Config::try_parse().unwrap();
assert_eq!(config.name, "env_value");
}
#[test]
#[serial]
fn default_value_with_type_conversion() {
#[derive(Environment)]
struct Config {
#[parenv(default = "8080")]
port: u16,
#[parenv(default = "100")]
count: u32,
}
let config = Config::try_parse().unwrap();
assert_eq!(config.port, 8080);
assert_eq!(config.count, 100);
}
#[test]
#[serial]
fn default_value_invalid_parse_error() {
#[allow(dead_code)]
#[derive(Debug, Environment)]
struct Config {
#[parenv(default = "not_a_number")]
port: u16,
}
let result = Config::try_parse();
assert!(result.is_err());
let errors = result.unwrap_err();
assert_eq!(errors.len(), 1);
}
#[test]
#[serial]
fn default_value_with_prefix_suffix() {
#[derive(Environment)]
#[parenv(prefix = "APP_", suffix = "_CONFIG")]
struct Config {
#[parenv(default = "localhost")]
host: String,
#[parenv(default = "3000")]
port: u16,
}
let config = Config::try_parse().unwrap();
assert_eq!(config.host, "localhost");
assert_eq!(config.port, 3000);
}
#[test]
#[serial]
fn default_value_with_prefix_suffix_overridden() {
#[derive(Environment)]
#[parenv(prefix = "APP_", suffix = "_CONFIG")]
struct Config {
#[parenv(default = "localhost")]
host: String,
#[parenv(default = "3000")]
port: u16,
}
let _guard = EnvGuard::set("APP_HOST_CONFIG", "production.example.com")
.and_set("APP_PORT_CONFIG", "8080");
let config = Config::try_parse().unwrap();
assert_eq!(config.host, "production.example.com");
assert_eq!(config.port, 8080);
}
#[test]
#[serial]
fn default_value_mixed_with_required_and_optional() {
#[derive(Environment)]
struct Config {
required_field: String,
#[parenv(default = "default_value")]
default_field: String,
optional_field: Option<String>,
}
let _guard = EnvGuard::set("REQUIRED_FIELD", "required_value");
let config = Config::try_parse().unwrap();
assert_eq!(config.required_field, "required_value");
assert_eq!(config.default_field, "default_value");
assert_eq!(config.optional_field, None);
}