Initial commit: env-guard CLI tool with CI/CD
Some checks failed
CI / test (push) Failing after 9s
CI / binary (push) Has been skipped
CI / release (push) Has been skipped

This commit is contained in:
CI Bot
2026-02-06 10:01:25 +00:00
commit fc90e05ebb
18 changed files with 2670 additions and 0 deletions

66
tests/cli_test.rs Normal file
View File

@@ -0,0 +1,66 @@
#[cfg(test)]
mod cli_tests {
use assert_cmd::Command;
use std::fs;
#[test]
fn test_cli_help() {
let mut cmd = Command::cargo_bin("env-guard").unwrap();
cmd.arg("--help").assert().success();
}
#[test]
fn test_cli_version() {
let mut cmd = Command::cargo_bin("env-guard").unwrap();
cmd.arg("--version").assert().success();
}
#[test]
fn test_cli_validate_missing_file() {
let mut cmd = Command::cargo_bin("env-guard").unwrap();
cmd.arg("validate").arg("--path").arg("nonexistent.env").assert().failure();
}
#[test]
fn test_cli_generate_missing_file() {
let mut cmd = Command::cargo_bin("env-guard").unwrap();
cmd.arg("generate").arg("--path").arg("nonexistent.env").assert().failure();
}
#[test]
fn test_cli_check_missing_file() {
let mut cmd = Command::cargo_bin("env-guard").unwrap();
cmd.arg("check").arg("--path").arg("nonexistent.env").assert().failure();
}
#[test]
fn test_cli_validate_command() {
let test_dir = "test_validate_dir";
fs::create_dir_all(test_dir).unwrap();
fs::write(format!("{}/.env", test_dir), "DATABASE_URL=postgres://localhost/db\nSECRET_KEY=secret").unwrap();
let mut cmd = Command::cargo_bin("env-guard").unwrap();
cmd.arg("validate").arg("--path").arg(format!("{}/.env", test_dir)).assert().success();
fs::remove_dir_all(test_dir).ok();
}
#[test]
fn test_cli_generate_command() {
let test_dir = "test_generate_dir";
fs::create_dir_all(test_dir).unwrap();
fs::write(format!("{}/.env", test_dir), "DATABASE_URL=postgres://localhost/db\nSECRET_KEY=secret").unwrap();
let mut cmd = Command::cargo_bin("env-guard").unwrap();
cmd.arg("generate")
.arg("--path")
.arg(format!("{}/.env", test_dir))
.arg("--output")
.arg(format!("{}/.env.example", test_dir))
.assert().success();
assert!(fs::read_to_string(format!("{}/.env.example", test_dir)).unwrap().contains("DATABASE_URL"));
fs::remove_dir_all(test_dir).ok();
}
}

96
tests/env_parser_test.rs Normal file
View File

@@ -0,0 +1,96 @@
#[cfg(test)]
mod env_parser_tests {
use env_guard::env_parser::{EnvFile, EnvEntry, parse_dotenv, extract_key_value};
#[test]
fn test_parse_simple_env() {
let content = r#"
DATABASE_URL=postgresql://user:pass@localhost:5432/db
SECRET_KEY=mysecret123
DEBUG=true
"#;
let env_file = EnvFile::parse(content).unwrap();
assert_eq!(env_file.len(), 3);
assert!(env_file.entries.contains_key("DATABASE_URL"));
assert!(env_file.entries.contains_key("SECRET_KEY"));
assert!(env_file.entries.contains_key("DEBUG"));
}
#[test]
fn test_parse_quoted_values() {
let content = r#"
API_KEY="my-secret-key-with-spaces"
DATABASE_URL='postgresql://user:pass@localhost:5432/db'
"#;
let env_file = EnvFile::parse(content).unwrap();
assert_eq!(env_file.len(), 2);
let api_key = env_file.entries.get("API_KEY").unwrap();
assert_eq!(api_key.value, "\"my-secret-key-with-spaces\"");
assert!(api_key.is_quoted);
}
#[test]
fn test_parse_comments() {
let content = r#"
# This is a comment
DATABASE_URL=postgresql://localhost:5432/db
# Another comment
SECRET_KEY=secret
"#;
let env_file = EnvFile::parse(content).unwrap();
assert_eq!(env_file.len(), 2);
assert_eq!(env_file.comments.len(), 2);
}
#[test]
fn test_parse_empty_lines() {
let content = r#"
DATABASE_URL=postgres://localhost/db
SECRET_KEY=secret
DEBUG=true
"#;
let env_file = EnvFile::parse(content).unwrap();
assert_eq!(env_file.len(), 3);
}
#[test]
fn test_extract_key_value() {
let line = "DATABASE_URL=postgresql://localhost:5432/db";
let (key, value) = extract_key_value(line).unwrap();
assert_eq!(key, "DATABASE_URL");
assert_eq!(value, "postgresql://localhost:5432/db");
}
#[test]
fn test_unquoted_value() {
let entry = EnvEntry::new("TEST".to_string(), "\"quoted value\"".to_string(), 1);
assert_eq!(entry.unquoted_value(), "quoted value");
let unquoted = EnvEntry::new("TEST2".to_string(), "plain value".to_string(), 2);
assert_eq!(unquoted.unquoted_value(), "plain value");
}
#[test]
fn test_special_characters_in_value() {
let content = r#"SPECIAL=value_with_underscores-and-hyphens"#;
let env_file = EnvFile::parse(content).unwrap();
assert!(env_file.entries.contains_key("SPECIAL"));
}
#[test]
fn test_numeric_values() {
let content = r#"
PORT=3000
TIMEOUT=60
RATIO=3.14
"#;
let env_file = EnvFile::parse(content).unwrap();
assert_eq!(env_file.len(), 3);
assert_eq!(env_file.entries.get("PORT").unwrap().value, "3000");
}
}

112
tests/secrets_test.rs Normal file
View File

@@ -0,0 +1,112 @@
#[cfg(test)]
mod secrets_tests {
use env_guard::secrets::{
scan_file, redact_secret, format_secret_match,
get_builtin_patterns, SecretSeverity
};
use std::fs;
#[test]
fn test_redact_secret_short() {
assert_eq!(redact_secret("abc"), "***");
}
#[test]
fn test_redact_secret_long() {
let result = redact_secret("my-secret-api-key-12345");
assert!(result.starts_with("my-s"));
assert!(result.contains('*'));
assert!(result.len() < 30);
}
#[test]
fn test_redact_secret_exact_8_chars() {
let result = redact_secret("12345678");
assert_eq!(result, "********");
}
#[test]
fn test_get_builtin_patterns() {
let patterns = get_builtin_patterns();
assert!(!patterns.is_empty());
let has_aws = patterns.iter().any(|p| p.name.contains("AWS"));
let has_github = patterns.iter().any(|p| p.name.contains("GitHub"));
let has_jwt = patterns.iter().any(|p| p.name.contains("JWT"));
assert!(has_aws);
assert!(has_github);
assert!(has_jwt);
}
#[test]
fn test_scan_file_with_secrets() {
let content = r#"
const apiKey = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const password = "super_secret_password";
const awsKey = "AKIAIOSFODNN7EXAMPLE";
"#;
let test_file = "test_secrets_temp.txt";
fs::write(test_file, content).unwrap();
let matches = scan_file(test_file, false).unwrap();
assert!(!matches.is_empty());
let has_api_key = matches.iter().any(|m| m.secret_type.contains("API") || m.secret_type.contains("OpenAI"));
assert!(has_api_key);
fs::remove_file(test_file).ok();
}
#[test]
fn test_scan_file_without_secrets() {
let content = r#"
const apiUrl = "https://api.example.com";
const port = 3000;
const debug = true;
"#;
let test_file = "test_no_secrets_temp.txt";
fs::write(test_file, content).unwrap();
let matches = scan_file(test_file, false).unwrap();
assert!(matches.is_empty());
fs::remove_file(test_file).ok();
}
#[test]
fn test_secret_severity_levels() {
assert_eq!(SecretSeverity::Critical.as_str(), "CRITICAL");
assert_eq!(SecretSeverity::High.as_str(), "HIGH");
assert_eq!(SecretSeverity::Medium.as_str(), "MEDIUM");
assert_eq!(SecretSeverity::Low.as_str(), "LOW");
}
#[test]
fn test_github_token_pattern() {
let content = r#"const token = "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";"#;
let test_file = "test_github_temp.txt";
fs::write(test_file, content).unwrap();
let matches = scan_file(test_file, false).unwrap();
let has_github = matches.iter().any(|m| m.secret_type.contains("GitHub"));
assert!(has_github);
fs::remove_file(test_file).ok();
}
#[test]
fn test_jwt_pattern() {
let content = r#"const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U";"#;
let test_file = "test_jwt_temp.txt";
fs::write(test_file, content).unwrap();
let matches = scan_file(test_file, false).unwrap();
let has_jwt = matches.iter().any(|m| m.secret_type.contains("JWT"));
assert!(has_jwt);
fs::remove_file(test_file).ok();
}
}

139
tests/validation_test.rs Normal file
View File

@@ -0,0 +1,139 @@
#[cfg(test)]
mod validation_tests {
use env_guard::validation::{
validate_url, validate_email, validate_uuid, validate_api_key,
validate_boolean, validate_integer, validate_database_url, validate_jwt,
validate_aws_key, validate_github_token, validate_slack_webhook,
validate_value, ValidationError, ValidationType, Validator
};
#[test]
fn test_valid_urls() {
assert!(validate_url("https://example.com"));
assert!(validate_url("http://localhost:3000"));
assert!(validate_url("https://api.example.com/v1/users"));
}
#[test]
fn test_invalid_urls() {
assert!(!validate_url("not-a-url"));
assert!(!validate_url("ftp://invalid.com"));
assert!(!validate_url(""));
}
#[test]
fn test_valid_emails() {
assert!(validate_email("user@example.com"));
assert!(validate_email("test.user+tag@domain.co.uk"));
assert!(validate_email("admin@sub.domain.com"));
}
#[test]
fn test_invalid_emails() {
assert!(!validate_email("not-an-email"));
assert!(!validate_email("@nodomain.com"));
assert!(!validate_email(""));
}
#[test]
fn test_valid_uuids() {
assert!(validate_uuid("550e8400-e29b-41d4-a716-446655440000"));
assert!(validate_uuid("f47ac10b-58cc-4372-a567-0e02b2c3d479"));
}
#[test]
fn test_invalid_uuids() {
assert!(!validate_uuid("not-a-uuid"));
assert!(!validate_uuid(""));
}
#[test]
fn test_valid_api_keys() {
assert!(validate_api_key("sk-test123456789012345678901234"));
assert!(validate_api_key("pk_live_abcdefghijklmnopqrstuvwx"));
}
#[test]
fn test_invalid_api_keys() {
assert!(!validate_api_key("too-short"));
assert!(!validate_api_key(""));
}
#[test]
fn test_valid_booleans() {
assert!(validate_boolean("true"));
assert!(validate_boolean("false"));
assert!(validate_boolean("1"));
assert!(validate_boolean("0"));
}
#[test]
fn test_invalid_booleans() {
assert!(!validate_boolean("maybe"));
assert!(!validate_boolean("2"));
assert!(!validate_boolean(""));
}
#[test]
fn test_valid_integers() {
assert!(validate_integer("123"));
assert!(validate_integer("-456"));
assert!(validate_integer("0"));
}
#[test]
fn test_invalid_integers() {
assert!(!validate_integer("12.34"));
assert!(!validate_integer("abc"));
assert!(!validate_integer(""));
}
#[test]
fn test_valid_database_urls() {
assert!(validate_database_url("postgresql://user:pass@localhost:5432/db"));
assert!(validate_database_url("mysql://user:pass@localhost:3306/db"));
assert!(validate_database_url("mongodb://localhost:27017/db"));
assert!(validate_database_url("redis://localhost:6379"));
}
#[test]
fn test_invalid_database_urls() {
assert!(!validate_database_url("not-a-db-url"));
assert!(!validate_database_url(""));
}
#[test]
fn test_valid_jwts() {
assert!(validate_jwt("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"));
}
#[test]
fn test_invalid_jwts() {
assert!(!validate_jwt("not-a-jwt"));
assert!(!validate_jwt(""));
}
#[test]
fn test_valid_github_tokens() {
assert!(validate_github_token("ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"));
}
#[test]
fn test_valid_slack_webhooks() {
assert!(validate_slack_webhook("https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"));
}
#[test]
fn test_validate_value() {
assert!(validate_value("TEST", "value", None).is_ok());
assert!(validate_value("TEST", "", None).is_err());
}
#[test]
fn test_validator_with_builtin_rules() {
let validator = Validator::with_builtin_rules();
let result = validator.validate_all(&std::collections::HashMap::new());
assert!(result.failed.is_empty());
assert!(result.passed.is_empty());
}
}