use std::sync::mpsc::{self, Sender};
use std::sync::Mutex;
use std::time::SystemTime;
use async_trait::async_trait;
use terminal_clipboard;
use rust_keylock::{execute_async, AllConfigurations, AsyncEditor, Entry, EntryMeta, EntryPresentationType, GeneralConfiguration, Menu, MessageSeverity, UserOption, UserSelection};
use rust_keylock::dropbox::DropboxConfiguration;
use rust_keylock::nextcloud::NextcloudConfiguration;
use std::path::PathBuf;
use std::fs::DirBuilder;
const INTEGRATION_TESTS_TMP_PATH_STR: &str = "./target/integration_tests_tmp";
#[tokio::test]
async fn execution_cases() {
DirBuilder::new()
.recursive(true)
.create(INTEGRATION_TESTS_TMP_PATH_STR).unwrap();
execute_login_success().await;
execute_exit_without_login().await;
execute_show_entry().await;
execute_add_entry().await;
execute_add_entry_with_leaked_password().await;
execute_add_entry_with_leaked_password_and_fix_it().await;
execute_edit_entry().await;
execute_edit_entry_define_leaked_password().await;
execute_edit_entry_define_leaked_password_and_fix_it().await;
execute_delete_entry().await;
execute_change_pass().await;
execute_export_entries().await;
execute_import_entries().await;
execute_add_to_clipboard().await;
execute_file_recovery().await;
execute_check_passwords().await;
execute_login_fail_and_then_sucess().await;
execute_update_configuration().await;
}
async fn execute_login_success() {
println!("===========execute_login_success");
let (tx, rx) = mpsc::channel();
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::GoTo(Menu::Save(false)),
UserSelection::UserOption(UserOption::ok()),
UserSelection::GoTo(Menu::Exit),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_exit_without_login() {
println!("===========execute_exit_without_login");
let (tx, rx) = mpsc::channel();
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("12311".to_string(), 0),
UserSelection::UserOption(UserOption::ok()),
UserSelection::GoTo(Menu::Exit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_login_fail_and_then_sucess() {
println!("===========execute_login_fail_and_then_sucess");
let (tx, rx) = mpsc::channel();
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("12311".to_string(), 0),
UserSelection::UserOption(UserOption::ok()),
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::GoTo(Menu::Save(false)),
UserSelection::UserOption(UserOption::ok()),
UserSelection::GoTo(Menu::Exit),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_show_entry() {
println!("===========execute_show_entry");
let (tx, rx) = mpsc::channel();
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::NewEntry(Entry::new("11nn".to_owned(), "11url".to_owned(), "11un".to_owned(), "11pn".to_owned(), "11sn".to_owned(), EntryMeta::default())),
UserSelection::GoTo(Menu::ShowEntry(0)),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_edit_entry() {
println!("===========execute_edit_entry");
let (tx, rx) = mpsc::channel();
let pass = rs_password_utils::dice::generate(6);
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::GoTo(Menu::EditEntry(0)),
UserSelection::ReplaceEntry(0, Entry::new("r".to_owned(), "url".to_owned(), "ru".to_owned(), pass, "rs".to_owned(), EntryMeta::default())),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_edit_entry_define_leaked_password() {
println!("===========execute_edit_entry_define_leaked_password");
let (tx, rx) = mpsc::channel();
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::GoTo(Menu::EditEntry(0)),
UserSelection::ReplaceEntry(0, Entry::new("r".to_owned(), "url".to_owned(), "ru".to_owned(), "123".to_string(), "rs".to_owned(), EntryMeta::default())),
UserSelection::UserOption(UserOption::yes()),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_edit_entry_define_leaked_password_and_fix_it() {
println!("===========execute_edit_entry_define_leaked_password_and_fix_it");
let (tx, rx) = mpsc::channel();
let pass = rs_password_utils::dice::generate(6);
let anentry = Entry::new("r".to_owned(), "url".to_owned(), "ru".to_owned(), "123".to_string(), "rs".to_owned(), EntryMeta::default());
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::GoTo(Menu::EditEntry(0)),
UserSelection::ReplaceEntry(0, anentry.clone()),
UserSelection::UserOption(UserOption::no()),
UserSelection::GeneratePassphrase(Some(0), anentry),
UserSelection::ReplaceEntry(0, Entry::new("r".to_owned(), "url".to_owned(), "ru".to_owned(), pass, "rs".to_owned(), EntryMeta::default())),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_add_entry() {
println!("===========execute_add_entry");
let (tx, rx) = mpsc::channel();
let pass = rs_password_utils::dice::generate(6);
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::GoTo(Menu::NewEntry(None)),
UserSelection::NewEntry(Entry::new("n".to_owned(), "url".to_owned(), "u".to_owned(), pass, "s".to_owned(), EntryMeta::default())),
UserSelection::GoTo(Menu::Save(false)),
UserSelection::UserOption(UserOption::ok()),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_add_entry_with_leaked_password() {
println!("===========execute_add_entry_with_leaked_password");
let (tx, rx) = mpsc::channel();
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::GoTo(Menu::NewEntry(None)),
UserSelection::NewEntry(Entry::new("n".to_owned(), "url".to_owned(), "u".to_owned(), "123".to_string(), "s".to_owned(), EntryMeta::default())),
UserSelection::UserOption(UserOption::yes()),
UserSelection::GoTo(Menu::Save(false)),
UserSelection::UserOption(UserOption::ok()),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_add_entry_with_leaked_password_and_fix_it() {
println!("===========execute_add_entry_with_leaked_password_and_fix_it");
let pass = rs_password_utils::dice::generate(6);
let (tx, rx) = mpsc::channel();
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::GoTo(Menu::NewEntry(None)),
UserSelection::NewEntry(Entry::new("n".to_owned(), "url".to_owned(), "u".to_owned(), "123".to_string(), "s".to_owned(), EntryMeta::default())),
UserSelection::UserOption(UserOption::no()),
UserSelection::NewEntry(Entry::new("n".to_owned(), "url".to_owned(), "u".to_owned(), pass, "s".to_owned(), EntryMeta::default())),
UserSelection::GoTo(Menu::Save(false)),
UserSelection::UserOption(UserOption::ok()),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_delete_entry() {
println!("===========execute_delete_entry");
let (tx, rx) = mpsc::channel();
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::NewEntry(Entry::new("11nn".to_owned(), "11url".to_owned(), "11un".to_owned(), "11pn".to_owned(), "11sn".to_owned(), EntryMeta::default())),
UserSelection::GoTo(Menu::DeleteEntry(0)),
UserSelection::DeleteEntry(0),
UserSelection::GoTo(Menu::Save(false)),
UserSelection::UserOption(UserOption::ok()),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_change_pass() {
println!("===========execute_change_pass");
let (tx, rx) = mpsc::channel();
let editor1 = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::GoTo(Menu::ChangePass),
UserSelection::new_provided_password("321".to_string(), 1),
UserSelection::GoTo(Menu::Save(false)),
UserSelection::UserOption(UserOption::ok()),
UserSelection::GoTo(Menu::ForceExit)], tx.clone()));
execute_async(editor1).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
let editor2 = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("321".to_string(), 1),
UserSelection::GoTo(Menu::ForceExit)], tx.clone()));
execute_async(editor2).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
let editor3 = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("321".to_string(), 1),
UserSelection::GoTo(Menu::ChangePass),
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::GoTo(Menu::Save(false)),
UserSelection::UserOption(UserOption::ok()),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor3).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_export_entries() {
println!("===========execute_export_entries");
let (tx, rx) = mpsc::channel();
let mut loc = PathBuf::from(INTEGRATION_TESTS_TMP_PATH_STR);
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).expect("Cannot create the duration for the execute_export_entries").as_secs();
loc.push(format!("exported{:?}", now));
let loc_str = loc.into_os_string().into_string().unwrap();
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::GoTo(Menu::ExportEntries),
UserSelection::ExportTo(loc_str.clone()),
UserSelection::UserOption(UserOption::ok()),
UserSelection::ExportTo(loc_str.clone()),
UserSelection::UserOption(UserOption::no()),
UserSelection::ExportTo(loc_str.clone()),
UserSelection::UserOption(UserOption::yes()),
UserSelection::UserOption(UserOption::ok()),
UserSelection::ExportTo(format!("/exported{:?}", now)),
UserSelection::UserOption(UserOption::ok()),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_import_entries() {
println!("===========execute_import_entries");
let (tx, rx) = mpsc::channel();
let mut loc = PathBuf::from(INTEGRATION_TESTS_TMP_PATH_STR);
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).expect("Cannot create the duration for the execute_export_entries").as_secs();
loc.push(format!("toimport{:?}", now));
let loc_str = loc.into_os_string().into_string().unwrap();
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::GoTo(Menu::ImportEntries),
UserSelection::ExportTo(loc_str.clone()),
UserSelection::UserOption(UserOption::ok()),
UserSelection::new_import_from(loc_str, "123".to_string(), 0),
UserSelection::UserOption(UserOption::ok()),
UserSelection::new_import_from("/non-existing".to_string(), "123".to_string(), 0),
UserSelection::UserOption(UserOption::ok()),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_update_configuration() {
println!("===========execute_update_configuration");
let (tx, rx) = mpsc::channel();
let nc_conf = NextcloudConfiguration::default();
let dbx_conf = DropboxConfiguration::default();
let gen_conf = GeneralConfiguration::default();
let new_conf = AllConfigurations::new(nc_conf, dbx_conf.clone(), gen_conf.clone());
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::GoTo(Menu::ShowConfiguration),
UserSelection::UpdateConfiguration(new_conf),
UserSelection::GoTo(Menu::Save(false)),
UserSelection::UserOption(UserOption::ok()),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_add_to_clipboard() {
println!("===========execute_add_to_clipboard");
let (tx, rx) = mpsc::channel();
let a_string = "1string".to_string();
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::AddToClipboard(a_string.clone()),
UserSelection::UserOption(UserOption::ok()),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let clip_res = terminal_clipboard::get_string();
assert!(clip_res.is_ok());
assert!(clip_res.unwrap() == a_string);
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_file_recovery() {
println!("===========execute_file_recovery");
let (tx, rx) = mpsc::channel();
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::GoTo(Menu::TryFileRecovery),
UserSelection::UserOption(UserOption::ok()),
UserSelection::UserOption(UserOption::ok()),
UserSelection::GoTo(Menu::Exit),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
async fn execute_check_passwords() {
println!("===========execute_check_passwords");
let (tx, rx) = mpsc::channel();
let editor = Box::new(TestEditor::new(vec![
UserSelection::new_provided_password("123".to_string(), 0),
UserSelection::CheckPasswords,
UserSelection::UserOption(UserOption::ok()),
UserSelection::GoTo(Menu::Exit),
UserSelection::GoTo(Menu::ForceExit)], tx));
execute_async(editor).await;
let res = rx.recv();
assert!(res.is_ok() && res.unwrap());
}
struct TestEditor {
selections_to_execute: Mutex<Vec<UserSelection>>,
completed_tx: Sender<bool>,
}
impl TestEditor {
pub fn new(selections_to_execute: Vec<UserSelection>, completed_tx: Sender<bool>) -> TestEditor {
let mut selections_to_execute_mut = selections_to_execute;
selections_to_execute_mut.reverse();
TestEditor { selections_to_execute: Mutex::new(selections_to_execute_mut), completed_tx }
}
fn return_first_selection(&self) -> UserSelection {
let mut available_selections_mut = self.selections_to_execute.lock().unwrap();
let to_ret = match available_selections_mut.pop() {
Some(sel) => sel,
None => panic!("Don't have more user selections to execute"),
};
if available_selections_mut.is_empty() {
self.completed_tx.send(true).expect("Cannot send to signal completion.");
}
to_ret
}
}
#[async_trait]
impl AsyncEditor for TestEditor {
async fn show_password_enter(&self) -> UserSelection {
println!("====== TestEditor::show_password_enter");
let to_ret = self.return_first_selection();
println!("====== Returning {:?}", to_ret);
to_ret
}
async fn show_change_password(&self) -> UserSelection {
println!("====== TestEditor::show_change_password");
let to_ret = self.return_first_selection();
println!("====== Returning {:?}", to_ret);
to_ret
}
async fn show_menu(&self, m: Menu) -> UserSelection {
println!("====== TestEditor::show_menu {:?}", m);
let to_ret = self.return_first_selection();
println!("====== Returning {:?}", to_ret);
to_ret
}
async fn show_entries(&self, entries: Vec<Entry>, filter: String) -> UserSelection {
println!("====== TestEditor::show_entries {:?} with filter {}", entries, filter);
let to_ret = self.return_first_selection();
println!("====== Returning {:?}", to_ret);
to_ret
}
async fn show_entry(&self, entry: Entry, index: usize, presentation_type: EntryPresentationType) -> UserSelection {
println!("====== TestEditor::show_entry {:?} with index {} and presentation_type {:?}", entry, index, presentation_type);
let to_ret = self.return_first_selection();
println!("====== Returning {:?}", to_ret);
to_ret
}
async fn exit(&self, force: bool) -> UserSelection {
println!("====== TestEditor::exit {}", force);
let to_ret = self.return_first_selection();
println!("====== Returning {:?}", to_ret);
to_ret
}
async fn show_configuration(&self, nextcloud: NextcloudConfiguration, dropbox: DropboxConfiguration, gen: GeneralConfiguration) -> UserSelection {
println!("====== TestEditor::show_configuration with {:?}, {:?} and {:?}", nextcloud, dropbox, gen);
let to_ret = self.return_first_selection();
println!("====== Returning {:?}", to_ret);
to_ret
}
async fn show_message(&self, m: &str, _: Vec<UserOption>, _: MessageSeverity) -> UserSelection {
println!("====== TestEditor::show_message {}", m);
let to_ret = self.return_first_selection();
println!("====== Returning {:?}", to_ret);
to_ret
}
fn start_rest_server(&self) -> bool {
false
}
}