kagi-vault 0.1.0

A small Rust CLI for encrypted environment variables
use crate::domain::entity::secret::Secret;
use crate::domain::entity::service::Service;
use crate::domain::error::DomainError;
use crate::domain::repository::secret_repo::SecretRepository;

pub struct SetSecretService<R: SecretRepository> {
    repo: R,
}

impl<R: SecretRepository> SetSecretService<R> {
    pub fn new(repo: R) -> Self {
        Self { repo }
    }

    pub fn execute(
        &self,
        service_name: &str,
        key: &str,
        value: &str,
        description: Option<&str>,
    ) -> Result<(), DomainError> {
        let mut service = match self.repo.load(service_name) {
            Ok(s) => s,
            Err(DomainError::ServiceNotFound(_)) => Service::new(service_name),
            Err(e) => return Err(e),
        };
        let secret = if let Some(desc) = description {
            Secret::with_description(key, value, desc)
        } else {
            Secret::new(key, value)
        };
        service.set_secret(secret);
        self.repo.save(&service)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::domain::crypto::encryptor::mock::XorEncryptor;
    use crate::infrastructure::fs_store::FileStore;
    use tempfile::TempDir;

    fn create_service(dir: &TempDir) -> SetSecretService<FileStore> {
        let base = dir.path().join(".kagi");
        std::fs::create_dir(&base).unwrap();
        let config = serde_json::json!({"version": "2", "project_id": "kgp_test", "services": {}});
        std::fs::write(
            base.join(crate::domain::config::KAGI_CONFIG_FILE),
            serde_json::to_string(&config).unwrap(),
        )
        .unwrap();
        let store = FileStore::new(base, Box::new(XorEncryptor::new(0xAB)));
        SetSecretService::new(store)
    }

    #[test]
    fn test_set_new_secret() {
        let dir = TempDir::new().unwrap();
        let svc = create_service(&dir);
        svc.execute("api", "KEY", "val", None).unwrap();
    }

    #[test]
    fn test_set_existing_service() {
        let dir = TempDir::new().unwrap();
        let svc = create_service(&dir);
        svc.execute("api", "A", "1", None).unwrap();
        svc.execute("api", "B", "2", None).unwrap();
    }

    #[test]
    fn test_set_secret_with_description() {
        let dir = TempDir::new().unwrap();
        let svc = create_service(&dir);
        svc.execute("api", "KEY", "val", Some("a description"))
            .unwrap();
        let loaded = svc.repo.load("api").unwrap();
        let secret = loaded.get_secret("KEY").unwrap();
        assert_eq!(secret.value, "val");
        assert_eq!(secret.description, Some("a description".into()));
    }
}