envvault/cli/commands/
env_delete.rs1use std::fs;
4
5use dialoguer::Confirm;
6
7use crate::cli::output;
8use crate::cli::{validate_env_name, Cli};
9use crate::errors::{EnvVaultError, Result};
10
11pub 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 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 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 fs::remove_file(&vault_path).unwrap();
77 assert!(!vault_path.exists());
78
79 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 let name = "dev";
101 let active_env = "dev";
102
103 assert!(name == active_env, "should detect active environment");
104
105 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}