Skip to main content

envvault/cli/commands/
env_delete.rs

1//! `envvault env delete` — delete a vault environment.
2
3use std::fs;
4
5use dialoguer::Confirm;
6
7use crate::cli::output;
8use crate::cli::{validate_env_name, Cli};
9use crate::errors::{EnvVaultError, Result};
10
11/// Execute `envvault env delete <name>`.
12pub fn execute(cli: &Cli, name: &str, force: bool) -> Result<()> {
13    validate_env_name(name)?;
14
15    let cwd = std::env::current_dir()?;
16    let vault_dir = cwd.join(&cli.vault_dir);
17    let vault_path = vault_dir.join(format!("{name}.vault"));
18
19    if !vault_path.exists() {
20        return Err(EnvVaultError::EnvironmentNotFound(name.to_string()));
21    }
22
23    // Prevent deleting the active environment unless --force is used.
24    if name == cli.env && !force {
25        output::warning(&format!(
26            "'{name}' is the currently active environment. Use --force to confirm."
27        ));
28        return Ok(());
29    }
30
31    if !force {
32        let confirmed = Confirm::new()
33            .with_prompt(format!(
34                "Delete environment '{name}'? This cannot be undone"
35            ))
36            .default(false)
37            .interact()
38            .map_err(|e| EnvVaultError::CommandFailed(format!("confirm prompt: {e}")))?;
39
40        if !confirmed {
41            output::info("Cancelled.");
42            return Ok(());
43        }
44    }
45
46    fs::remove_file(&vault_path)?;
47
48    crate::audit::log_audit(cli, "env-delete", None, Some(&format!("deleted {name}")));
49
50    output::success(&format!(
51        "Deleted environment '{name}' ({} removed)",
52        vault_path.display()
53    ));
54
55    Ok(())
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use crate::vault::VaultStore;
62
63    #[test]
64    fn delete_removes_real_vault() {
65        let dir = tempfile::TempDir::new().unwrap();
66        let vault_path = dir.path().join("staging.vault");
67
68        // Create a real vault with secrets.
69        let mut store =
70            VaultStore::create(&vault_path, b"testpassword1", "staging", None, None).unwrap();
71        store.set_secret("KEY", "value").unwrap();
72        store.save().unwrap();
73        assert!(vault_path.exists());
74
75        // Delete the vault file (simulates `execute` with --force).
76        fs::remove_file(&vault_path).unwrap();
77        assert!(!vault_path.exists());
78
79        // Verify it can no longer be opened.
80        assert!(VaultStore::open(&vault_path, b"testpassword1", None).is_err());
81    }
82
83    #[test]
84    fn delete_nonexistent_env_produces_correct_error() {
85        let err = EnvVaultError::EnvironmentNotFound("ghost".to_string());
86        let msg = err.to_string();
87        assert!(
88            msg.contains("ghost"),
89            "error should name the environment: {msg}"
90        );
91        assert!(
92            msg.contains("not found"),
93            "error should indicate not found: {msg}"
94        );
95    }
96
97    #[test]
98    fn active_env_protection_blocks_without_force() {
99        // Mirrors the condition in execute(): name == cli.env && !force
100        let name = "dev";
101        let active_env = "dev";
102
103        assert!(name == active_env, "should detect active environment");
104
105        // With --force, deletion proceeds even for the active env.
106        let force = true;
107        assert!(
108            name != active_env || force,
109            "force should bypass active env protection"
110        );
111    }
112
113    #[test]
114    fn validates_env_name_on_delete() {
115        assert!(validate_env_name("INVALID").is_err());
116        assert!(validate_env_name("valid-name").is_ok());
117        assert!(validate_env_name("").is_err());
118        assert!(validate_env_name("-leading").is_err());
119    }
120}