use std::cell::RefCell;
use std::sync::Mutex;
use super::credential::{
Credential, CredentialApi, CredentialBuilder, CredentialBuilderApi, CredentialPersistence,
};
use super::error::{decode_password, Error, Result};
#[derive(Debug)]
pub struct MockCredential {
pub inner: Mutex<RefCell<MockData>>,
}
impl Default for MockCredential {
fn default() -> Self {
Self {
inner: Mutex::new(RefCell::new(Default::default())),
}
}
}
#[derive(Debug, Default)]
pub struct MockData {
pub secret: Option<Vec<u8>>,
pub error: Option<Error>,
}
impl CredentialApi for MockCredential {
fn set_password(&self, password: &str) -> Result<()> {
let mut inner = self.inner.lock().expect("Can't access mock data for set");
let data = inner.get_mut();
let err = data.error.take();
match err {
None => {
data.secret = Some(password.as_bytes().to_vec());
Ok(())
}
Some(err) => Err(err),
}
}
fn set_secret(&self, secret: &[u8]) -> Result<()> {
let mut inner = self.inner.lock().expect("Can't access mock data for set");
let data = inner.get_mut();
let err = data.error.take();
match err {
None => {
data.secret = Some(secret.to_vec());
Ok(())
}
Some(err) => Err(err),
}
}
fn get_password(&self) -> Result<String> {
let mut inner = self.inner.lock().expect("Can't access mock data for get");
let data = inner.get_mut();
let err = data.error.take();
match err {
None => match &data.secret {
None => Err(Error::NoEntry),
Some(val) => decode_password(val.clone()),
},
Some(err) => Err(err),
}
}
fn get_secret(&self) -> Result<Vec<u8>> {
let mut inner = self.inner.lock().expect("Can't access mock data for get");
let data = inner.get_mut();
let err = data.error.take();
match err {
None => match &data.secret {
None => Err(Error::NoEntry),
Some(val) => Ok(val.clone()),
},
Some(err) => Err(err),
}
}
fn delete_credential(&self) -> Result<()> {
let mut inner = self
.inner
.lock()
.expect("Can't access mock data for delete");
let data = inner.get_mut();
let err = data.error.take();
match err {
None => match data.secret {
Some(_) => {
data.secret = None;
Ok(())
}
None => Err(Error::NoEntry),
},
Some(err) => Err(err),
}
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}
impl MockCredential {
fn new_with_target(_target: Option<&str>, _service: &str, _user: &str) -> Result<Self> {
Ok(Default::default())
}
pub fn set_error(&self, err: Error) {
let mut inner = self
.inner
.lock()
.expect("Can't access mock data for set_error");
let data = inner.get_mut();
data.error = Some(err);
}
}
pub struct MockCredentialBuilder {}
impl CredentialBuilderApi for MockCredentialBuilder {
fn build(&self, target: Option<&str>, service: &str, user: &str) -> Result<Box<Credential>> {
let credential = MockCredential::new_with_target(target, service, user)?;
Ok(Box::new(credential))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn persistence(&self) -> CredentialPersistence {
CredentialPersistence::EntryOnly
}
}
pub fn default_credential_builder() -> Box<CredentialBuilder> {
Box::new(MockCredentialBuilder {})
}
#[cfg(test)]
mod tests {
use super::{default_credential_builder, MockCredential};
use crate::credential::CredentialPersistence;
use crate::{tests::generate_random_string, Entry, Error};
#[test]
fn test_persistence() {
assert!(matches!(
default_credential_builder().persistence(),
CredentialPersistence::EntryOnly
))
}
fn entry_new(service: &str, user: &str) -> Entry {
let credential = MockCredential::new_with_target(None, service, user).unwrap();
Entry::new_with_credential(Box::new(credential))
}
#[test]
fn test_missing_entry() {
crate::tests::test_missing_entry(entry_new);
}
#[test]
fn test_empty_password() {
crate::tests::test_empty_password(entry_new);
}
#[test]
fn test_round_trip_ascii_password() {
crate::tests::test_round_trip_ascii_password(entry_new);
}
#[test]
fn test_round_trip_non_ascii_password() {
crate::tests::test_round_trip_non_ascii_password(entry_new);
}
#[test]
fn test_round_trip_random_secret() {
crate::tests::test_round_trip_random_secret(entry_new);
}
#[test]
fn test_update() {
crate::tests::test_update(entry_new);
}
#[test]
fn test_get_update_attributes() {
crate::tests::test_noop_get_update_attributes(entry_new);
}
#[test]
fn test_set_error() {
let name = generate_random_string();
let entry = entry_new(&name, &name);
let password = "test ascii password";
let mock: &MockCredential = entry
.inner
.as_any()
.downcast_ref()
.expect("Downcast failed");
mock.set_error(Error::Invalid(
"mock error".to_string(),
"is an error".to_string(),
));
assert!(
matches!(entry.set_password(password), Err(Error::Invalid(_, _))),
"set: No error"
);
entry
.set_password(password)
.expect("set: Error not cleared");
mock.set_error(Error::NoEntry);
assert!(
matches!(entry.get_password(), Err(Error::NoEntry)),
"get: No error"
);
let stored_password = entry.get_password().expect("get: Error not cleared");
assert_eq!(
stored_password, password,
"Retrieved and set ascii passwords don't match"
);
mock.set_error(Error::TooLong("mock".to_string(), 3));
assert!(
matches!(entry.delete_credential(), Err(Error::TooLong(_, 3))),
"delete: No error"
);
entry
.delete_credential()
.expect("delete: Error not cleared");
assert!(
matches!(entry.get_password(), Err(Error::NoEntry)),
"Able to read a deleted ascii password"
)
}
}