dotenvpp 0.0.2

From-scratch .env parsing and loading toolkit in Rust.
Documentation
//! Integration tests for the `dotenvpp` facade crate.

#![allow(clippy::unwrap_used)]

use std::io::Cursor;

#[test]
fn test_from_read_basic() {
    let input = b"A=hello\nB=world";
    let pairs = dotenvpp::from_read(Cursor::new(input)).unwrap();
    assert_eq!(pairs.len(), 2);
    assert_eq!(pairs[0].key, "A");
    assert_eq!(pairs[0].value, "hello");
    assert_eq!(pairs[1].key, "B");
    assert_eq!(pairs[1].value, "world");
}

#[test]
fn test_from_read_with_comments() {
    let input = b"# comment\nKEY=value\n\n# another\nNAME=\"test\"";
    let pairs = dotenvpp::from_read(Cursor::new(input)).unwrap();
    assert_eq!(pairs.len(), 2);
    assert_eq!(pairs[0].key, "KEY");
    assert_eq!(pairs[0].value, "value");
    assert_eq!(pairs[1].key, "NAME");
    assert_eq!(pairs[1].value, "test");
}

#[test]
fn test_from_read_empty() {
    let input = b"";
    let pairs = dotenvpp::from_read(Cursor::new(input)).unwrap();
    assert_eq!(pairs.len(), 0);
}

#[test]
fn test_from_read_complex_env() {
    let input = br#"
# App settings
APP_NAME=dotenvpp
APP_PORT=8080
DB_URL="postgres://user:pass@localhost/db"
export SECRET='my-secret'
MULTI="line1
line2"
EMPTY=
"#;
    let pairs = dotenvpp::from_read(Cursor::new(input)).unwrap();
    assert_eq!(pairs.len(), 6);
    assert_eq!(pairs[0].key, "APP_NAME");
    assert_eq!(pairs[0].value, "dotenvpp");
    assert_eq!(pairs[2].key, "DB_URL");
    assert_eq!(pairs[2].value, "postgres://user:pass@localhost/db");
    assert_eq!(pairs[5].key, "EMPTY");
    assert_eq!(pairs[5].value, "");
}

#[test]
fn test_from_path_missing_file() {
    let result = dotenvpp::from_path("nonexistent_file_that_does_not_exist.env");
    assert!(result.is_err());
}

#[test]
fn test_from_path_iter_missing_file() {
    let result = dotenvpp::from_path_iter("nonexistent_file_that_does_not_exist.env");
    assert!(result.is_err());
}

#[test]
fn test_version_format() {
    let version = dotenvpp::version();
    assert!(
        version.chars().all(|c| c.is_ascii_digit() || c == '.'),
        "version string should only contain digits and dots: {version}"
    );
}

#[test]
fn test_var_not_present() {
    let key = format!(
        "DOTENVPP_TEST_{}_{}",
        std::process::id(),
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos()
    );

    // Requesting a var that doesn't exist should give NotPresent error.
    let err = dotenvpp::var(&key);
    assert!(err.is_err());
    let msg = format!("{}", err.unwrap_err());
    assert!(msg.contains("not found"));
    assert!(msg.contains(&key));
}

#[cfg(not(windows))]
#[test]
fn test_var_not_unicode() {
    use std::ffi::OsString;
    use std::os::unix::ffi::OsStringExt;

    let key = "DOTENVPP_INVALID_UNICODE";
    let value = OsString::from_vec(vec![0x66, 0x80]);

    // SAFETY: test setup for a unique env var key.
    unsafe { std::env::set_var(key, &value) };

    let err = dotenvpp::var(key).unwrap_err();
    let msg = format!("{err}");
    assert!(msg.contains("invalid unicode"));

    // SAFETY: test cleanup for a unique env var key.
    unsafe { std::env::remove_var(key) };
}