Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c8bee36e2 |
2 changed files with 106 additions and 3 deletions
38
src/args.rs
38
src/args.rs
|
|
@ -41,9 +41,6 @@ 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)]
|
||||||
|
|
@ -51,3 +48,38 @@ 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,3 +118,74 @@ 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