use crate::login::store::{Backend, CubeState, SavedCubeState};
use chris::types::{CubeUrl, PluginInstanceId, Username};
use color_eyre::eyre::{Result, WrapErr};
use color_eyre::owo_colors::OwoColorize;
use color_eyre::Section;
use serde::{Deserialize, Serialize};
use std::path::Path;
const SERVICE: &str = "org.chrisproject.chrs";
const APP_NAME: &str = "chrs";
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct ChrsSessions {
pub sessions: Vec<SavedCubeState>,
}
impl ChrsSessions {
pub fn get_login(
&self,
cube: Option<&CubeUrl>,
username: Option<&Username>,
) -> Result<Option<CubeState>> {
match self.get_cube(cube, username) {
None => Ok(None),
Some(cube) => Ok(Some(cube.to_owned().into_login(SERVICE)?)),
}
}
pub fn get_cube(
&self,
cube: Option<&CubeUrl>,
username: Option<&Username>,
) -> Option<&SavedCubeState> {
match cube {
None => self.sessions.last(),
Some(cube_url) => self.find_cube(cube_url, username),
}
}
fn find_cube(
&self,
cube_url: &CubeUrl,
username: Option<&Username>,
) -> Option<&SavedCubeState> {
for cube in &self.sessions {
if cube_url == &cube.cube {
if let Some(given_username) = username {
if given_username == &cube.username {
return Some(cube);
}
} else {
return Some(cube);
}
}
}
None
}
pub fn add(&mut self, session: CubeState, backend: Backend) -> Result<()> {
self.remove(&session.cube, Some(&session.username));
self.sessions.push(session.into_saved(backend, SERVICE)?);
Ok(())
}
pub fn remove(&mut self, cube_url: &CubeUrl, username: Option<&Username>) -> bool {
fn keep(a: &SavedCubeState, cube_url: &CubeUrl, username: Option<&Username>) -> bool {
if &a.cube != cube_url {
return true;
}
if let Some(u) = username {
if &a.username == u {
return false;
}
return true;
}
false
}
let original_len = self.sessions.len();
self.sessions.retain(|l| keep(l, cube_url, username));
self.sessions.len() != original_len
}
pub fn set_last(&mut self, b: usize) {
let a = self.sessions.len() - 1;
self.sessions.swap(a, b)
}
pub fn clear(&mut self) -> bool {
let original_len = self.sessions.len();
self.sessions.clear();
original_len != 0
}
pub fn load<P: AsRef<Path>>(config_path: Option<P>) -> Result<Self> {
let result = if let Some(path) = config_path {
confy::load_path(path)
} else {
confy::load(APP_NAME, None)
};
result
.wrap_err("Could not load config file. ")
.with_suggestion(|| {
format!(
"If chrs was upgraded from an old version, please run `{}`",
"rm -rf ~/.config/chrs".bold()
)
})
}
pub fn save<P: AsRef<Path>>(&self, config_path: Option<P>) -> Result<()> {
let result = if let Some(path) = config_path {
confy::store_path(path, self)
} else {
confy::store(APP_NAME, None, self)
};
result.wrap_err("Couldn't write config file")
}
pub fn set_plugin_instance(
&mut self,
cube_url: &CubeUrl,
username: &Username,
plinst: PluginInstanceId,
) -> bool {
for session in &mut self.sessions {
if &session.cube == cube_url && &session.username == username {
session.current_plugin_instance_id = Some(plinst);
return true;
}
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::login::store::StoredToken;
use chris::types::PluginInstanceId;
use rstest::*;
use std::str::FromStr;
#[fixture]
fn sessions() -> Vec<SavedCubeState> {
vec![
SavedCubeState {
cube: CubeUrl::from_static("https://a.example.com/api/v1/"),
username: Username::from_str("aaaaa").unwrap(),
store: StoredToken::Text("token-a".to_string()),
current_plugin_instance_id: None,
ui: None,
},
SavedCubeState {
cube: CubeUrl::from_static("https://b.example.com/api/v1/"),
username: Username::from_str("b-first").unwrap(),
store: StoredToken::Text("token-b1".to_string()),
current_plugin_instance_id: None,
ui: None,
},
SavedCubeState {
cube: CubeUrl::from_static("https://c.example.com/api/v1/"),
username: Username::from_str("ccccc").unwrap(),
store: StoredToken::Keyring,
current_plugin_instance_id: None,
ui: None,
},
SavedCubeState {
cube: CubeUrl::from_static("https://b.example.com/api/v1/"),
username: Username::from_str("b-second").unwrap(),
store: StoredToken::Text("token-b2".to_string()),
current_plugin_instance_id: Some(PluginInstanceId(43)),
ui: None,
},
]
}
#[fixture]
fn chrs_sessions(sessions: Vec<SavedCubeState>) -> ChrsSessions {
ChrsSessions { sessions }
}
#[fixture]
fn example_cube_url() -> CubeUrl {
CubeUrl::from_static("https://example.example.org/api/v1/")
}
#[fixture]
fn example_username() -> Username {
Username::from_static("chrs-example-username")
}
#[rstest]
fn test_empty_config(example_cube_url: CubeUrl, example_username: Username) -> Result<()> {
let empty_config = ChrsSessions::default();
assert!(empty_config.get_login(None, None)?.is_none());
assert!(empty_config
.get_login(Some(&example_cube_url), None)?
.is_none());
assert!(empty_config
.get_login(None, Some(&example_username))?
.is_none());
assert!(empty_config
.get_login(Some(&example_cube_url), Some(&example_username),)?
.is_none());
Ok(())
}
#[rstest]
fn test_get_default_cube(chrs_sessions: ChrsSessions) -> Result<()> {
let expected = CubeState {
cube: CubeUrl::from_static("https://b.example.com/api/v1/"),
username: Username::from_static("b-second"),
token: Some("token-b2".to_string()),
current_plugin_instance_id: Some(PluginInstanceId(43)),
ui: None,
};
assert_eq!(Some(expected), chrs_sessions.get_login(None, None)?);
Ok(())
}
#[rstest]
fn test_get_cube_by_address(chrs_sessions: ChrsSessions) -> Result<()> {
let cube_url = CubeUrl::from_static("https://c.example.com/api/v1/");
assert_eq!(
Some(&chrs_sessions.sessions[2]),
chrs_sessions.find_cube(&cube_url, None)
);
assert_eq!(
Some(&chrs_sessions.sessions[2]),
chrs_sessions.find_cube(&cube_url, Some(&Username::from_str("ccccc").unwrap()))
);
assert_eq!(
None,
chrs_sessions.find_cube(&cube_url, Some(&Username::from_str("aaaaa").unwrap()))
);
Ok(())
}
#[rstest]
fn test_same_cube_different_users(chrs_sessions: ChrsSessions) -> Result<()> {
let cube_url = CubeUrl::from_static("https://b.example.com/api/v1/");
let expected1 = CubeState {
cube: cube_url.clone(),
username: Username::from_static("b-first"),
token: Some("token-b1".to_string()),
current_plugin_instance_id: None,
ui: None,
};
let expected2 = CubeState {
cube: cube_url.clone(),
username: Username::from_static("b-second"),
token: Some("token-b2".to_string()),
current_plugin_instance_id: Some(PluginInstanceId(43)),
ui: None,
};
assert_eq!(
Some(&expected1),
chrs_sessions.get_login(Some(&cube_url), None)?.as_ref()
);
assert_eq!(
Some(&expected1),
chrs_sessions
.get_login(Some(&cube_url), Some(&expected1.username))?
.as_ref()
);
assert_eq!(
Some(&expected2),
chrs_sessions
.get_login(Some(&cube_url), Some(&expected2.username))?
.as_ref()
);
Ok(())
}
#[test]
fn test_add() -> Result<()> {
let mut config = ChrsSessions::default();
assert_eq!(0, config.sessions.len());
config.add(
CubeState {
cube: CubeUrl::from_static("https://example.com/api/v1/"),
username: Username::from_str("apple").unwrap(),
token: Some("red-delicious".to_string()),
current_plugin_instance_id: None,
ui: None,
},
Backend::ClearText,
)?;
assert_eq!(1, config.sessions.len());
assert_eq!(
StoredToken::Text("red-delicious".to_string()),
config
.get_cube(
Some(&CubeUrl::from_static("https://example.com/api/v1/")),
None
)
.unwrap()
.store
);
config.add(
CubeState {
cube: CubeUrl::from_static("https://example.com/api/v1/"),
username: Username::from_str("apple").unwrap(),
token: Some("golden-delicious".to_string()),
current_plugin_instance_id: None,
ui: None,
},
Backend::ClearText,
)?;
assert_eq!(
1,
config.sessions.len(),
"length is not the same after adding a Login with same address and username"
);
assert_eq!(
StoredToken::Text("golden-delicious".to_string()),
config
.get_cube(
Some(&CubeUrl::from_static("https://example.com/api/v1/")),
None
)
.unwrap()
.store
);
config.add(
CubeState {
cube: CubeUrl::from_static("https://example.com/api/v1/"),
username: Username::from_str("pear").unwrap(),
token: Some("yapearisachinesepear".to_string()),
current_plugin_instance_id: None,
ui: None,
},
Backend::ClearText,
)?;
assert_eq!(
2,
config.sessions.len(),
"length did not increase after adding a login with a different username."
);
config.add(
CubeState {
cube: CubeUrl::from_static("https://another.example.com/api/v1/"),
username: Username::from_str("pear").unwrap(),
token: Some("yapearisachinesepear".to_string()),
current_plugin_instance_id: None,
ui: None,
},
Backend::ClearText,
)?;
assert_eq!(
3,
config.sessions.len(),
"length did not increase after adding a login with a different address."
);
Ok(())
}
#[rstest]
fn test_remove() -> Result<()> {
let mut config = ChrsSessions::default();
config.add(
CubeState {
cube: CubeUrl::from_static("https://one.example.com/api/v1/"),
username: Username::from_str("apple").unwrap(),
token: Some("red-delicious".to_string()),
current_plugin_instance_id: None,
ui: None,
},
Backend::ClearText,
)?;
config.add(
CubeState {
cube: CubeUrl::from_static("https://two.example.com/api/v1/"),
username: Username::from_str("pear").unwrap(),
token: Some("yapearisachinesepear".to_string()),
current_plugin_instance_id: None,
ui: None,
},
Backend::ClearText,
)?;
assert_eq!(
StoredToken::Text("red-delicious".to_string()),
config
.get_cube(
Some(&CubeUrl::from_static("https://one.example.com/api/v1/")),
None,
)
.unwrap()
.store
);
assert_eq!(
StoredToken::Text("yapearisachinesepear".to_string()),
config
.get_cube(
Some(&CubeUrl::from_static("https://two.example.com/api/v1/")),
None
)
.unwrap()
.store
);
let addr1 = CubeUrl::from_static("https://one.example.com/api/v1/");
assert!(config.remove(&addr1, None));
assert!(!config.remove(&addr1, None), "login already removed");
assert!(config.get_cube(Some(&addr1), None).is_none());
let addr2 = CubeUrl::from_static("https://two.example.com/api/v1/");
assert_eq!(
StoredToken::Text("yapearisachinesepear".to_string()),
config.get_cube(Some(&addr2), None).unwrap().store
);
assert!(
!config.remove(&addr2, Some(&Username::from_str("apple").unwrap())),
"username should not be found"
);
assert!(config.remove(&addr2, Some(&Username::from_str("pear").unwrap())));
assert!(config.get_cube(Some(&addr2), None).is_none());
Ok(())
}
#[rstest]
fn test_set_plugin_instance(mut chrs_sessions: ChrsSessions) -> Result<()> {
let cube_url = CubeUrl::from_static("https://c.example.com/api/v1/");
let username = Username::from_static("ccccc");
let plinst = PluginInstanceId(108);
chrs_sessions.set_plugin_instance(&cube_url, &username, plinst);
let actual = chrs_sessions
.get_cube(Some(&cube_url), Some(&username))
.unwrap();
assert_eq!(actual.current_plugin_instance_id, Some(plinst));
Ok(())
}
}