use envkit::{EnvLoader, error::EnvError}; use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; fn unique_key(name: &str) -> String { let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("system time should be after the Unix epoch") .as_nanos(); format!("ENVKIT_TEST_{name}_{nanos}") } #[test] fn get_reads_required_typed_values() { let env = EnvLoader::new(); let name_key = unique_key("NAME"); let port_key = unique_key("PORT"); let debug_key = unique_key("DEBUG"); let list_key = unique_key("PORTS"); unsafe { std::env::set_var(&name_key, "envkit"); std::env::set_var(&port_key, "3000"); std::env::set_var(&debug_key, "yes"); std::env::set_var(&list_key, "3000, 3001, 3002"); } assert_eq!(env.get::(&name_key), Ok("envkit".to_string())); assert_eq!(env.get::(&port_key), Ok(3000)); assert_eq!(env.get::(&debug_key), Ok(true)); assert_eq!(env.get::>(&list_key), Ok(vec![3000, 3001, 3002])); unsafe { std::env::remove_var(name_key); std::env::remove_var(port_key); std::env::remove_var(debug_key); std::env::remove_var(list_key); } } #[test] fn get_reports_missing_and_invalid_values() { let env = EnvLoader::new(); let missing_key = unique_key("MISSING"); let invalid_key = unique_key("INVALID"); unsafe { std::env::remove_var(&missing_key); std::env::set_var(&invalid_key, "not-a-port"); } assert_eq!( env.get::(&missing_key), Err(EnvError::MissingVar(missing_key.clone())) ); assert!(matches!( env.get::(&invalid_key), Err(EnvError::Invalid { key, value, .. }) if key == "u16" && value == "not-a-port" )); unsafe { std::env::remove_var(invalid_key); } } #[test] fn get_or_and_get_opt_handle_fallbacks() { let env = EnvLoader::new(); let present_key = unique_key("PRESENT"); let missing_key = unique_key("DEFAULT"); let invalid_key = unique_key("BAD_BOOL"); unsafe { std::env::set_var(&present_key, "42"); std::env::remove_var(&missing_key); std::env::set_var(&invalid_key, "maybe"); } assert_eq!(env.get_or::(&present_key, 7), 42); assert_eq!(env.get_or::(&missing_key, 7), 7); assert_eq!(env.get_or::(&invalid_key, false), false); assert_eq!(env.get_opt::(&present_key), Some(42)); assert_eq!(env.get_opt::(&missing_key), None); assert_eq!(env.get_opt::(&invalid_key), None); unsafe { std::env::remove_var(present_key); std::env::remove_var(invalid_key); } } #[test] fn prefixed_loader_reads_namespaced_variables() { let env = EnvLoader::new(); let prefix = unique_key("APP_"); let host_key = format!("{prefix}HOST"); let enabled_key = format!("{prefix}ENABLED"); unsafe { std::env::set_var(&host_key, "127.0.0.1"); std::env::set_var(&enabled_key, "1"); } let app = env.with_prefix(prefix); assert_eq!(app.get::("HOST"), Ok("127.0.0.1".to_string())); assert_eq!(app.get::("ENABLED"), Ok(true)); assert_eq!(app.get_or::("PORT", 8080), 8080); unsafe { std::env::remove_var(host_key); std::env::remove_var(enabled_key); } } #[test] fn load_file_sets_environment_variables() { let env = EnvLoader::new(); let prefix = unique_key("FILE_"); let port_key = format!("{prefix}PORT"); let debug_key = format!("{prefix}DEBUG"); let names_key = format!("{prefix}NAMES"); let path = temp_env_file(&prefix, &port_key, &debug_key, &names_key); env.load_file(path.to_str().expect("test path should be UTF-8")) .expect("env file should load"); assert_eq!(env.get::(&port_key), Ok(8080)); assert_eq!(env.get::(&debug_key), Ok(true)); assert_eq!( env.get::>(&names_key), Ok(vec!["api".to_string(), "worker".to_string()]) ); std::fs::remove_file(path).expect("test env file should be removable"); unsafe { std::env::remove_var(port_key); std::env::remove_var(debug_key); std::env::remove_var(names_key); } } fn temp_env_file(prefix: &str, port_key: &str, debug_key: &str, names_key: &str) -> PathBuf { let path = std::env::temp_dir().join(format!("{prefix}.env")); let contents = format!( r#" # comments and blank lines are ignored {port_key} = 8080 {debug_key}= yes {names_key}= api, worker "# ); std::fs::write(&path, contents).expect("test env file should be writable"); path }