fuzzel-pass 0.1.2

A password-store frontend for auto-typing passwords
use super::Result;

shell!(Pass, "/usr/bin/pass");

impl Pass {
    /// Lists passwords in store and return it as a [`String`]
    /// ready to be consumed by [`Fuzzel::pick`] function.
    ///
    /// [`String`]: std::string::String
    /// [`Fuzzel::pick`]: crate::shell::fuzzel::Fuzzel::pick
    pub fn list(&self) -> Result<String> {
        let lines: Vec<String> = self
            .inner
            .exec(vec!["git".into(), "ls-files".into()])?
            .lines()
            .filter(|line| {
                std::path::Path::new(line)
                    .extension()
                    .is_some_and(|ext| ext.eq_ignore_ascii_case("gpg"))
            })
            .map(|line| line.strip_suffix(".gpg").unwrap_or(line))
            .map(std::string::ToString::to_string)
            .collect();

        Ok(lines.join("\n"))
    }

    /// Shows a password file's content and return it as a [`String`]
    /// which should be processed using [`Password`].
    ///
    /// * `entry` - The password file name under `$HOME/.password-store/`
    ///
    /// [`String`]: std::string::String
    /// [`Password`]: crate::password::Password
    pub fn show(&self, entry: String) -> Result<String> {
        self.inner.exec(vec!["show".into(), entry])
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_default() {
        let store = Pass::default();

        assert_eq!(Pass::new(String::from("/usr/bin/pass")), store);
    }

    #[test]
    fn test_list() {
        let store = mock_password_store();
        let expected = "Bank/bank.example.com\nStore/store.example.com";

        check_result(expected, store.list());
    }

    #[test]
    fn test_show() {
        let store = mock_password_store();
        for item in [
            (
                "Bank/bank.example.com",
                "Very Secret Password\n---\nuser: me@examble.com\nurl: https://bank.example.com/login\nautotype: user :tab :password :enter",
            ),
            (
                "Bank/bank.example_integer.com",
                "Very Secret Password\n---\nuser: 123456789\nurl: https://bank.example.com/login\nautotype: user :tab :password :enter",
            ),
        ] {
            let (entry, expected) = item;
            check_result(expected, store.show(entry.into()));
        }
    }

    fn check_result(expected: &str, result: Result<String>) {
        assert!(result.is_ok());

        if let Ok(actual) = result {
            assert_eq!(expected, actual);
        }
    }

    fn mock_password_store() -> Pass {
        Pass::new("test/scripts/mock-pass".into())
    }
}