Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test spec for preferences API. #304

Merged
merged 1 commit into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/helpers/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,9 @@ pub async fn new_account(
.text("NEW ACCOUNT".into())
.text(format!("{}", account_name).into())
.text(
"Creating a new account will perform the following actions:".into())
"Creating a new account will perform the following actions:"
.into(),
)
.text(message.into())
.render();
println!("{}", banner);
Expand Down
12 changes: 5 additions & 7 deletions src/helpers/secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,7 @@ pub fn print_secret(
.text(secret_meta.label().into());

let mut banner = match secret_data {
Secret::Note { text, .. } => {
banner.text(text.expose_secret().into())
}
Secret::Note { text, .. } => banner.text(text.expose_secret().into()),
Secret::Account {
account,
url,
Expand Down Expand Up @@ -125,9 +123,7 @@ pub fn print_secret(
Secret::Password { password, .. } => {
banner.text(password.expose_secret().into())
}
Secret::Link { url, .. } => {
banner.text(url.expose_secret().into())
}
Secret::Link { url, .. } => banner.text(url.expose_secret().into()),
Secret::Signer { .. } | Secret::Age { .. } => {
banner.text("[REDACTED PRIVATE SIGNING KEY]".into())
}
Expand Down Expand Up @@ -248,7 +244,9 @@ fn multiline_banner(kind: &str, label: &str) {
.text(format!("[{}] {}", kind, label).into())
.text(
r#"To abort enter Ctrl+C
To save enter Ctrl+D on a newline"#.into())
To save enter Ctrl+D on a newline"#
.into(),
)
.render();
println!("{}", banner);
}
Expand Down
1 change: 1 addition & 0 deletions tests/local_account/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod identity_login;
mod migrate_export;
mod migrate_import;
mod move_secret;
mod preferences;
mod search;
mod secret_lifecycle;
mod security_report;
Expand Down
109 changes: 109 additions & 0 deletions tests/local_account/preferences.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use crate::test_utils::{setup, teardown};
use anyhow::Result;
use sos_net::sdk::prelude::*;

/// Tests the account preferences.
#[tokio::test]
async fn local_preferences() -> Result<()> {
const TEST_ID: &str = "preferences";
//crate::test_utils::init_tracing();

let mut dirs = setup(TEST_ID, 1).await?;
let data_dir = dirs.clients.remove(0);

let account_name = TEST_ID.to_string();
let (password, _) = generate_passphrase()?;

let mut account = LocalAccount::new_account(
account_name.clone(),
password.clone(),
Some(data_dir.clone()),
)
.await?;

let key: AccessKey = password.into();
account.sign_in(&key).await?;

let identity = account.public_identity().await?.clone();
let identities = vec![identity];

CachedPreferences::initialize(&identities, Some(data_dir.clone()))
.await?;

// Create preferences
CachedPreferences::set(
account.address(),
"mock.bool".to_owned(),
true.into(),
)
.await?;

CachedPreferences::set(
account.address(),
"mock.int".to_owned(),
(-15 as i64).into(),
)
.await?;

CachedPreferences::set(
account.address(),
"mock.double".to_owned(),
(3.14 as f64).into(),
)
.await?;
CachedPreferences::set(
account.address(),
"mock.string".to_owned(),
"message".to_owned().into(),
)
.await?;
let list = vec!["item-1".to_owned(), "item-2".to_owned()];
CachedPreferences::set(
account.address(),
"mock.string-list".to_owned(),
list.into(),
)
.await?;

// Retrieve preferences
let missing =
CachedPreferences::get(account.address(), "mock.non-existent")
.await?;
assert!(matches!(missing, None));

let boolean =
CachedPreferences::get(account.address(), "mock.bool").await?;
assert!(matches!(boolean, Some(Preference::Bool(_))));

let int = CachedPreferences::get(account.address(), "mock.int").await?;
assert!(matches!(int, Some(Preference::Int(_))));

let double =
CachedPreferences::get(account.address(), "mock.double").await?;
assert!(matches!(double, Some(Preference::Double(_))));

let string =
CachedPreferences::get(account.address(), "mock.string").await?;
assert!(matches!(string, Some(Preference::String(_))));

let string_list =
CachedPreferences::get(account.address(), "mock.string-list").await?;
assert!(matches!(string_list, Some(Preference::StringList(_))));

// Remove preferences
let removed =
CachedPreferences::remove(account.address(), "mock.bool").await?;
assert!(matches!(removed, Some(Preference::Bool(true))));

// Clear preferences
CachedPreferences::clear(account.address()).await?;
let prefs = CachedPreferences::load(account.address()).await;
let items = prefs.iter().collect::<Vec<_>>();
assert!(items.is_empty());

account.sign_out().await?;

teardown(TEST_ID).await;

Ok(())
}
70 changes: 55 additions & 15 deletions workspace/sdk/src/account/preferences.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,18 @@ impl CachedPreferences {
///
/// If a preferences file exists for an account it is loaded
/// into memory otherwise empty preferences are used.
pub async fn initialize(accounts: &[PublicIdentity]) -> Result<()> {
pub async fn initialize(
accounts: &[PublicIdentity],
data_dir: Option<PathBuf>,
) -> Result<()> {
let data_dir = if let Some(data_dir) = data_dir {
data_dir
} else {
Paths::data_dir()?
};
let mut cache = CACHE.lock().await;
for account in accounts {
let paths =
Paths::new(Paths::data_dir()?, account.address().to_string());
let paths = Paths::new(&data_dir, account.address().to_string());
let file = paths.preferences();
let prefs = if file.exists() {
let mut prefs = Preferences::new(&paths);
Expand All @@ -44,7 +51,7 @@ impl CachedPreferences {
}

/// Load the preferences for an account.
pub async fn load_preferences(address: &Address) -> Preferences {
pub async fn load(address: &Address) -> Preferences {
let cache = CACHE.lock().await;
if let Some(prefs) = cache.get(address) {
prefs.clone()
Expand All @@ -54,7 +61,7 @@ impl CachedPreferences {
}

/// Get a preference for an account.
pub async fn get_preference(
pub async fn get(
address: &Address,
key: impl AsRef<str>,
) -> Result<Option<Preference>> {
Expand All @@ -71,7 +78,7 @@ impl CachedPreferences {
}

/// Set a preference for an account.
pub async fn set_preference(
pub async fn set(
address: &Address,
key: String,
value: Preference,
Expand All @@ -83,18 +90,17 @@ impl CachedPreferences {
}

/// Remove a preference for an account.
pub async fn remove_preference(
pub async fn remove(
address: &Address,
key: impl AsRef<str>,
) -> Result<()> {
) -> Result<Option<Preference>> {
let mut cache = CACHE.lock().await;
let prefs = cache.entry(*address).or_default();
prefs.remove(key).await?;
Ok(())
prefs.remove(key).await
}

/// Clear all preferences for an account.
pub async fn clear_preferences(address: &Address) -> Result<()> {
pub async fn clear(address: &Address) -> Result<()> {
let mut cache = CACHE.lock().await;
if let Some(mut prefs) = cache.remove(address) {
prefs.clear().await?;
Expand All @@ -104,7 +110,7 @@ impl CachedPreferences {
}

/// Preference value.
#[derive(Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Preference {
/// Boolean value.
Expand All @@ -119,6 +125,36 @@ pub enum Preference {
StringList(Vec<String>),
}

impl From<bool> for Preference {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}

impl From<f64> for Preference {
fn from(value: f64) -> Self {
Self::Double(value)
}
}

impl From<i64> for Preference {
fn from(value: i64) -> Self {
Self::Int(value)
}
}

impl From<String> for Preference {
fn from(value: String) -> Self {
Self::String(value)
}
}

impl From<Vec<String>> for Preference {
fn from(value: Vec<String>) -> Self {
Self::StringList(value)
}
}

/// Preferences for an account.
#[derive(Default, Clone, Serialize, Deserialize)]
pub struct Preferences {
Expand Down Expand Up @@ -179,9 +215,13 @@ impl Preferences {
/// Remove a preference.
///
/// Changes are written to disc.
pub async fn remove(&mut self, key: impl AsRef<str>) -> Result<()> {
self.values.remove(key.as_ref());
self.save().await
pub async fn remove(
&mut self,
key: impl AsRef<str>,
) -> Result<Option<Preference>> {
let pref = self.values.remove(key.as_ref());
self.save().await?;
Ok(pref)
}

/// Clear all preferences.
Expand Down
2 changes: 1 addition & 1 deletion workspace/sdk/src/audit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize};
use crate::{
events::{Event, EventKind, LogEvent, ReadEvent, WriteEvent},
signer::ecdsa::Address,
UtcDateTime,
vault::{secret::SecretId, VaultId},
UtcDateTime,
};

use crate::events::AccountEvent;
Expand Down
2 changes: 1 addition & 1 deletion workspace/sdk/src/date_time.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! UTC date and time that can be encoded to and from binary.
//!
//! Encoded as an i64 of the seconds since the UNIX epoch and
//! a u32 nanosecond offset from the second so the total size
//! a u32 nanosecond offset from the second so the total size
//! when encoded is 12 bytes.

use serde::{Deserialize, Serialize};
Expand Down
3 changes: 1 addition & 2 deletions workspace/sdk/src/events/log/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ use crate::{
EventLogRecord, FileIdentity, FileItem, FormatStream,
FormatStreamIterator,
},
UtcDateTime,
vfs::{self, File, OpenOptions},
Error, Result,
Error, Result, UtcDateTime,
};

use crate::events::AccountEvent;
Expand Down
3 changes: 1 addition & 2 deletions workspace/sdk/src/events/log/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! Event log types and traits.
use crate::{
commit::CommitHash, decode, formats::EventLogRecord,
UtcDateTime, Result,
commit::CommitHash, decode, formats::EventLogRecord, Result, UtcDateTime,
};
use binary_stream::futures::Decodable;

Expand Down
4 changes: 1 addition & 3 deletions workspace/sdk/src/events/log/reducer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,7 @@ mod files {
/// Reduce file events to a canonical collection
/// of external files.
#[cfg(not(feature = "sync"))]
pub async fn reduce(
self,
) -> Result<IndexSet<ExternalFile>> {
pub async fn reduce(self) -> Result<IndexSet<ExternalFile>> {
let mut files: IndexSet<ExternalFile> = IndexSet::new();

let stream = self.log.stream(false).await;
Expand Down
2 changes: 2 additions & 0 deletions workspace/sdk/src/prelude.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Prelude re-exports common types.
#[cfg(all(feature = "account", feature = "archive"))]
pub use crate::account::archive::*;
#[cfg(all(feature = "account", feature = "preferences"))]
pub use crate::account::preferences::*;
#[cfg(all(feature = "account", feature = "security-report"))]
pub use crate::account::security_report::*;
#[cfg(feature = "account")]
Expand Down