#![deny(clippy::all)]
#![warn(clippy::pedantic)]
#![warn(clippy::nursery)]
#![warn(clippy::cargo)]
mod common;
use anyhow::Error;
use common::identity::{Identity, SubCommand, default, prompt};
use common::{Interaction, InteractionType, SecondAttempt};
fn default_id_name() -> Interaction {
Interaction::new(
prompt::ID_NAME,
InteractionType::Input(default::ID_NAME.to_string()),
)
}
#[test]
fn new_minimal() -> Result<(), Error> {
let identity = Identity::new(
"new_minimal",
default_id_name(),
None,
None,
None,
None,
None,
None,
None,
)?;
identity.run(&SubCommand::New, Vec::new())?;
Ok(())
}
#[test]
fn new_full() -> Result<(), Error> {
let identity = Identity::new(
"new_full",
default_id_name(),
Some(default::FULL_NAME.to_string()),
Some(Interaction::new(
prompt::EMAIL,
InteractionType::Input(default::EMAIL.to_string()),
)),
Some(Interaction::new(
prompt::EXPIRY_DATE,
InteractionType::Input(default::EXPIRY.to_string()),
)),
Some(Interaction::new(
prompt::LOGIN,
InteractionType::Input(default::LOGIN.to_string()),
)),
Some(Interaction::new(
prompt::ORIGIN,
InteractionType::Input(default::ORIGIN.to_string()),
)),
Some(Interaction::new(
prompt::PASSWORD,
InteractionType::Password {
input: default::PASSWORD.to_string(),
confirm: Some(prompt::PASSWORD_REPROMPT.to_string()),
},
)),
Some(Interaction::new(
prompt::SELECT_KEY,
InteractionType::Input(default::SSH.to_string()),
)),
)?;
identity.run(&SubCommand::New, Vec::new())?;
Ok(())
}
#[test]
fn new_email() -> Result<(), Error> {
let identity = Identity::new(
"new_email",
default_id_name(),
None,
Some(
Interaction::new(
prompt::EMAIL,
InteractionType::Input(String::from("BAD-EMAIL")),
)
.with_second_attempt(SecondAttempt::new(
InteractionType::Input(default::EMAIL.to_string()),
"Invalid email address",
)?)?,
),
None,
None,
None,
None,
None,
)?;
identity.run(&SubCommand::New, Vec::new())?;
Ok(())
}
#[test]
fn new_expiry() -> Result<(), Error> {
let identity = Identity::new(
"new_expiry",
default_id_name(),
None,
None,
Some(
Interaction::new(
prompt::EXPIRY_DATE,
InteractionType::Input(String::from("BAD-EXPIRY")),
)
.with_second_attempt(SecondAttempt::new(
InteractionType::Input(default::EXPIRY.to_string()),
"Invalid date",
)?)?,
),
None,
None,
None,
None,
)?;
identity.run(&SubCommand::New, Vec::new())?;
Ok(())
}
#[test]
fn new_login() -> Result<(), Error> {
let identity = Identity::new(
"new_login",
default_id_name(),
None,
None,
None,
Some(Interaction::new(
prompt::LOGIN,
InteractionType::Input(default::LOGIN.to_string()),
)),
None,
None,
None,
)?;
identity.run(&SubCommand::New, Vec::new())?;
Ok(())
}
#[test]
fn new_origin() -> Result<(), Error> {
let identity = Identity::new(
"new_origin",
default_id_name(),
None,
None,
None,
None,
Some(Interaction::new(
prompt::ORIGIN,
InteractionType::Input(default::ORIGIN.to_string()),
)),
None,
None,
)?;
identity.run(&SubCommand::New, Vec::new())?;
Ok(())
}
#[test]
fn new_password() -> Result<(), Error> {
let identity = Identity::new(
"new_password",
default_id_name(),
None,
None,
None,
None,
None,
Some(
Interaction::new(
prompt::PASSWORD,
InteractionType::Password {
input: default::PASSWORD.to_string(),
confirm: Some(prompt::PASSWORD_REPROMPT.to_string()),
},
)
.with_second_attempt(SecondAttempt::new(
InteractionType::Password {
input: "Good-Password".to_string(),
confirm: Some(prompt::PASSWORD_REPROMPT.to_string()),
},
"Password mismatch",
)?)?,
),
None,
)?;
identity.run(&SubCommand::New, Vec::new())?;
Ok(())
}
#[test]
fn edit_preserves_existing_expiry() -> Result<(), Error> {
use std::{fs, process::Command};
let config_dir = tempfile::tempdir()?;
fs::write(
config_dir.path().join("config.toml"),
"colors = 'never'\n[author]\nlogin = ''",
)?;
let new_status = Command::new(env!("CARGO_BIN_EXE_pijul"))
.env("PIJUL_CONFIG_DIR", config_dir.path())
.args([
"identity",
"new",
"--no-link",
"--no-prompt",
"my_identity",
"--display-name",
default::FULL_NAME,
"--expiry",
default::EXPIRY,
])
.status()?;
assert!(new_status.success());
let edit_status = Command::new(env!("CARGO_BIN_EXE_pijul"))
.env("PIJUL_CONFIG_DIR", config_dir.path())
.args([
"identity",
"edit",
"--no-link",
"--no-prompt",
"my_identity",
"--email",
default::EMAIL,
])
.status()?;
assert!(edit_status.success());
let identity_path = config_dir
.path()
.join("identities")
.join("my_identity")
.join("identity.toml");
let identity_data = fs::read_to_string(identity_path)?;
let toml_data = identity_data.parse::<toml::Value>().unwrap();
assert_eq!(
toml_data.get("email").and_then(toml::Value::as_str),
Some(default::EMAIL)
);
let public_expiry = toml_data
.get("public_key")
.and_then(|value| value.get("expires"))
.and_then(toml::Value::as_str)
.expect("expected public key expiry to be preserved")
.parse::<jiff::Timestamp>()?;
assert_eq!(
public_expiry.strftime("%Y-%m-%d").to_string(),
default::EXPIRY
);
let secret_key = fs::read_to_string(
config_dir
.path()
.join("identities")
.join("my_identity")
.join("secret_key.json"),
)?;
let secret_key: pijul_core::key::SecretKey = serde_json::from_str(&secret_key)?;
assert_eq!(
secret_key
.expires
.expect("expected secret key expiry to be preserved")
.strftime("%Y-%m-%d")
.to_string(),
default::EXPIRY
);
Ok(())
}
#[test]
fn edit_full() -> Result<(), Error> {
let old_identity = Identity::new(
"edit_full",
default_id_name(),
None,
None,
None,
None,
None,
None,
None,
)?;
let new_identity = Identity::new(
"edit_full",
Interaction::new(
prompt::ID_NAME,
InteractionType::Input(String::from("new_id_name")),
),
Some(default::FULL_NAME.to_string()),
Some(
Interaction::new(
prompt::EMAIL,
InteractionType::Input(String::from("BAD_EMAIL")),
)
.with_second_attempt(SecondAttempt::new(
InteractionType::Input(default::EMAIL.to_string()),
"Invalid email address",
)?)?,
),
Some(
Interaction::new(
prompt::EXPIRY_DATE,
InteractionType::Input(String::from("BAD-EXPIRY")),
)
.with_second_attempt(SecondAttempt::new(
InteractionType::Input(default::EXPIRY.to_string()),
"Invalid date",
)?)?,
),
Some(Interaction::new(
prompt::LOGIN,
InteractionType::Input(default::LOGIN.to_string()),
)),
Some(Interaction::new(
prompt::ORIGIN,
InteractionType::Input(default::ORIGIN.to_string()),
)),
Some(
Interaction::new(
prompt::PASSWORD,
InteractionType::Password {
input: default::PASSWORD.to_string(),
confirm: Some(prompt::PASSWORD_REPROMPT.to_string()),
},
)
.with_second_attempt(SecondAttempt::new(
InteractionType::Password {
input: "Good-Password".to_string(),
confirm: Some(prompt::PASSWORD_REPROMPT.to_string()),
},
"Password mismatch",
)?)?,
),
Some(Interaction::new(
prompt::SELECT_KEY,
InteractionType::Input(default::SSH.to_string()),
)),
)?;
new_identity.run(
&SubCommand::Edit(old_identity.id_name.valid_input().as_string()),
vec![old_identity],
)?;
Ok(())
}
#[test]
fn edit_id_name() -> Result<(), Error> {
let old_identity = Identity::new(
"edit_id_name",
default_id_name(),
None,
None,
None,
None,
None,
None,
None,
)?;
let new_identity = Identity::new(
"edit_id_name",
Interaction::new(
prompt::ID_NAME,
InteractionType::Input(String::from("new_id_name")),
),
None,
None,
None,
None,
None,
None,
None,
)?;
new_identity.run(
&SubCommand::Edit(old_identity.id_name.valid_input().as_string()),
vec![old_identity],
)?;
Ok(())
}
#[test]
fn edit_email() -> Result<(), Error> {
let old_identity = Identity::new(
"edit_email",
default_id_name(),
None,
None,
None,
None,
None,
None,
None,
)?;
let new_identity = Identity::new(
"edit_email",
default_id_name(),
None,
Some(
Interaction::new(
prompt::EMAIL,
InteractionType::Input(String::from("BAD_EMAIL")),
)
.with_second_attempt(SecondAttempt::new(
InteractionType::Input(default::EMAIL.to_string()),
"Invalid email address",
)?)?,
),
None,
None,
None,
None,
None,
)?;
new_identity.run(
&SubCommand::Edit(old_identity.id_name.valid_input().as_string()),
vec![old_identity],
)?;
Ok(())
}
#[test]
fn edit_expiry() -> Result<(), Error> {
let old_identity = Identity::new(
"edit_expiry",
default_id_name(),
None,
None,
None,
None,
None,
None,
None,
)?;
let new_identity = Identity::new(
"edit_expiry",
default_id_name(),
None,
None,
Some(
Interaction::new(
prompt::EXPIRY_DATE,
InteractionType::Input(String::from("BAD-EXPIRY")),
)
.with_second_attempt(SecondAttempt::new(
InteractionType::Input(default::EXPIRY.to_string()),
"Invalid date",
)?)?,
),
None,
None,
None,
None,
)?;
new_identity.run(
&SubCommand::Edit(old_identity.id_name.valid_input().as_string()),
vec![old_identity],
)?;
Ok(())
}
#[test]
fn edit_password() -> Result<(), Error> {
let old_identity = Identity::new(
"edit_password",
default_id_name(),
None,
None,
None,
None,
None,
None,
None,
)?;
let new_identity = Identity::new(
"edit_password",
default_id_name(),
None,
None,
None,
None,
None,
Some(
Interaction::new(
prompt::PASSWORD,
InteractionType::Password {
input: default::PASSWORD.to_string(),
confirm: Some(prompt::PASSWORD_REPROMPT.to_string()),
},
)
.with_second_attempt(SecondAttempt::new(
InteractionType::Password {
input: "Good-Password".to_string(),
confirm: Some(prompt::PASSWORD_REPROMPT.to_string()),
},
"Password mismatch",
)?)?,
),
None,
)?;
new_identity.run(
&SubCommand::Edit(old_identity.id_name.valid_input().as_string()),
vec![old_identity],
)?;
Ok(())
}
#[test]
fn remove() -> Result<(), Error> {
let identity = Identity::new(
"remove",
default_id_name(),
None,
None,
None,
None,
None,
None,
None,
)?;
identity.run(&SubCommand::Remove, vec![identity.clone()])?;
Ok(())
}