Compare commits
No commits in common. "main" and "0.1.1" have entirely different histories.
2 changed files with 3 additions and 106 deletions
38
src/args.rs
38
src/args.rs
|
|
@ -41,6 +41,9 @@ pub struct AddPass {
|
||||||
pub struct GetPass {
|
pub struct GetPass {
|
||||||
/// The name of the password to get
|
/// The name of the password to get
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// Copy the password to the clipboard instead of printing it
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub copy: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
|
@ -48,38 +51,3 @@ pub struct DeletePass {
|
||||||
/// The name of the password to delete
|
/// The name of the password to delete
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parses_add_command() {
|
|
||||||
let cli = Cli::try_parse_from(["pm", "add", "github"]).expect("parse add");
|
|
||||||
match cli.command {
|
|
||||||
Some(Commands::Add(add)) => assert_eq!(add.name, "github"),
|
|
||||||
_ => panic!("expected add command"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parses_get_command() {
|
|
||||||
let cli = Cli::try_parse_from(["pm", "get", "email"]).expect("parse get");
|
|
||||||
match cli.command {
|
|
||||||
Some(Commands::Get(get)) => assert_eq!(get.name, "email"),
|
|
||||||
_ => panic!("expected get command"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rejects_removed_copy_flag() {
|
|
||||||
let cli = Cli::try_parse_from(["pm", "get", "email", "--copy"]);
|
|
||||||
assert!(cli.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parses_list_command() {
|
|
||||||
let cli = Cli::try_parse_from(["pm", "list"]).expect("parse list");
|
|
||||||
assert!(matches!(cli.command, Some(Commands::List)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -118,74 +118,3 @@ pub fn verify_master_password(password: &str, recorded_hash: &str) -> bool {
|
||||||
.verify_password(password.as_bytes(), &parsed_hash)
|
.verify_password(password.as_bytes(), &parsed_hash)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
|
|
||||||
fn temp_file_path(prefix: &str) -> String {
|
|
||||||
let nanos = SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.expect("time went backwards")
|
|
||||||
.as_nanos();
|
|
||||||
std::env::temp_dir()
|
|
||||||
.join(format!("pm_test_{}_{}_{}", prefix, std::process::id(), nanos))
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn key_derivation_is_deterministic_for_same_input() {
|
|
||||||
let mut p1 = String::from("master-password");
|
|
||||||
let mut p2 = String::from("master-password");
|
|
||||||
let salt = "abc123fixedsalt";
|
|
||||||
|
|
||||||
let k1 = derive_key(&mut p1, salt);
|
|
||||||
let k2 = derive_key(&mut p2, salt);
|
|
||||||
|
|
||||||
assert_eq!(k1, k2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hash_and_verify_master_password_work() {
|
|
||||||
let password = "correct-horse-battery-staple";
|
|
||||||
let hash = hash_master_password(password);
|
|
||||||
|
|
||||||
assert!(verify_master_password(password, &hash));
|
|
||||||
assert!(!verify_master_password("wrong-password", &hash));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn save_load_and_delete_roundtrip() {
|
|
||||||
let path = temp_file_path("vault");
|
|
||||||
let key = [7u8; 32];
|
|
||||||
let mut data = HashMap::new();
|
|
||||||
data.insert("email".to_string(), "pw1".to_string());
|
|
||||||
data.insert("bank".to_string(), "pw2".to_string());
|
|
||||||
|
|
||||||
save_passwords(&path, &data, &key);
|
|
||||||
let loaded = load_passwords(&path, &key);
|
|
||||||
assert_eq!(loaded, data);
|
|
||||||
|
|
||||||
delete_password(&path, "email", &key);
|
|
||||||
let after_delete = load_passwords(&path, &key);
|
|
||||||
assert!(!after_delete.contains_key("email"));
|
|
||||||
assert_eq!(after_delete.get("bank"), Some(&"pw2".to_string()));
|
|
||||||
|
|
||||||
let _ = std::fs::remove_file(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn get_or_create_salt_reuses_existing_value() {
|
|
||||||
let path = temp_file_path("salt");
|
|
||||||
|
|
||||||
let first = get_or_create_salt(&path);
|
|
||||||
let second = get_or_create_salt(&path);
|
|
||||||
|
|
||||||
assert_eq!(first, second);
|
|
||||||
assert_eq!(first.len(), 32);
|
|
||||||
|
|
||||||
let _ = std::fs::remove_file(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue