Initial commit: env-guard CLI tool with CI/CD
This commit is contained in:
66
tests/cli_test.rs
Normal file
66
tests/cli_test.rs
Normal 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
96
tests/env_parser_test.rs
Normal 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
112
tests/secrets_test.rs
Normal 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
139
tests/validation_test.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user