use std::cell::RefCell;
use std::sync::Mutex;
use crate::credential::CredentialApi;
use crate::error::{Error, Result, decode_password};
#[derive(Debug)]
struct MockCredential {
inner: Mutex<RefCell<MockData>>,
}
impl Default for MockCredential {
fn default() -> Self {
Self {
inner: Mutex::new(RefCell::new(MockData::default())),
}
}
}
#[derive(Debug, Default)]
struct MockData {
secret: Option<Vec<u8>>,
error: Option<Error>,
}
#[async_trait::async_trait]
impl CredentialApi for MockCredential {
async 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),
}
}
async 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),
}
}
async 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),
}
}
async 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),
}
}
async 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) -> Self {
Self::default()
}
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);
}
}
#[cfg(test)]
mod tests {
use super::MockCredential;
use crate::{Entry, Error, tests::generate_random_string};
fn entry_new(service: &str, user: &str) -> Entry {
let credential = MockCredential::new_with_target(None, service, user);
Entry::new_with_credential(Box::new(credential))
}
#[tokio::test]
async fn test_missing_entry() {
crate::tests::test_missing_entry(entry_new).await;
}
#[tokio::test]
async fn test_empty_password() {
crate::tests::test_empty_password(entry_new).await;
}
#[tokio::test]
async fn test_round_trip_ascii_password() {
crate::tests::test_round_trip_ascii_password(entry_new).await;
}
#[tokio::test]
async fn test_round_trip_non_ascii_password() {
crate::tests::test_round_trip_non_ascii_password(entry_new).await;
}
#[tokio::test]
async fn test_round_trip_random_secret() {
crate::tests::test_round_trip_random_secret(entry_new).await;
}
#[tokio::test]
async fn test_update() {
crate::tests::test_update(entry_new).await;
}
#[tokio::test]
async fn test_get_update_attributes() {
crate::tests::test_noop_get_update_attributes(entry_new).await;
}
#[tokio::test]
async 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).await,
Err(Error::Invalid(_, _))
),
"set: No error"
);
entry
.set_password(password)
.await
.expect("set: Error not cleared");
mock.set_error(Error::NoEntry);
assert!(
matches!(entry.get_password().await, Err(Error::NoEntry)),
"get: No error"
);
let stored_password = entry.get_password().await.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().await, Err(Error::TooLong(_, 3))),
"delete: No error"
);
entry
.delete_credential()
.await
.expect("delete: Error not cleared");
assert!(
matches!(entry.get_password().await, Err(Error::NoEntry)),
"Able to read a deleted ascii password"
);
}
}