use crate::error::Result;
use crate::provider::{AwsProvider, CloudProvider};
use crate::store::traits::GroupStore;
use crate::wami::identity::group::{
builder as group_builder, CreateGroupRequest, Group, ListGroupsRequest, UpdateGroupRequest,
};
use std::sync::{Arc, RwLock};
pub struct GroupService<S> {
store: Arc<RwLock<S>>,
provider: Arc<dyn CloudProvider>,
account_id: String,
}
impl<S: GroupStore> GroupService<S> {
pub fn new(store: Arc<RwLock<S>>, account_id: String) -> Self {
Self {
store,
provider: Arc::new(AwsProvider::new()),
account_id,
}
}
pub fn with_provider(&self, provider: Arc<dyn CloudProvider>) -> Self {
Self {
store: self.store.clone(),
provider,
account_id: self.account_id.clone(),
}
}
pub async fn create_group(&self, request: CreateGroupRequest) -> Result<Group> {
let group = group_builder::build_group(
request.group_name,
request.path,
&*self.provider,
&self.account_id,
);
self.store.write().unwrap().create_group(group).await
}
pub async fn get_group(&self, group_name: &str) -> Result<Option<Group>> {
self.store.read().unwrap().get_group(group_name).await
}
pub async fn update_group(&self, request: UpdateGroupRequest) -> Result<Group> {
let mut group = self
.store
.read()
.unwrap()
.get_group(&request.group_name)
.await?
.ok_or_else(|| crate::error::AmiError::ResourceNotFound {
resource: format!("Group: {}", request.group_name),
})?;
if let Some(new_group_name) = request.new_group_name {
group = group_builder::update_group_name(group, new_group_name);
}
if let Some(new_path) = request.new_path {
group = group_builder::update_group_path(group, new_path);
}
self.store.write().unwrap().update_group(group).await
}
pub async fn delete_group(&self, group_name: &str) -> Result<()> {
self.store.write().unwrap().delete_group(group_name).await
}
pub async fn list_groups(
&self,
request: ListGroupsRequest,
) -> Result<(Vec<Group>, bool, Option<String>)> {
self.store
.read()
.unwrap()
.list_groups(request.path_prefix.as_deref(), request.pagination.as_ref())
.await
}
pub async fn add_user_to_group(&self, group_name: &str, user_name: &str) -> Result<()> {
self.store
.write()
.unwrap()
.add_user_to_group(group_name, user_name)
.await
}
pub async fn remove_user_from_group(&self, group_name: &str, user_name: &str) -> Result<()> {
self.store
.write()
.unwrap()
.remove_user_from_group(group_name, user_name)
.await
}
pub async fn list_groups_for_user(&self, user_name: &str) -> Result<Vec<Group>> {
self.store
.read()
.unwrap()
.list_groups_for_user(user_name)
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::store::memory::InMemoryWamiStore;
use crate::store::traits::UserStore;
use crate::wami::identity::user::builder as user_builder;
fn setup_service() -> GroupService<InMemoryWamiStore> {
let store = Arc::new(RwLock::new(InMemoryWamiStore::default()));
GroupService::new(store, "123456789012".to_string())
}
#[tokio::test]
async fn test_create_and_get_group() {
let service = setup_service();
let request = CreateGroupRequest {
group_name: "admins".to_string(),
path: Some("/it/".to_string()),
tags: None,
};
let group = service.create_group(request).await.unwrap();
assert_eq!(group.group_name, "admins");
assert_eq!(group.path, "/it/");
let retrieved = service.get_group("admins").await.unwrap();
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap().group_name, "admins");
}
#[tokio::test]
async fn test_update_group() {
let service = setup_service();
let create_request = CreateGroupRequest {
group_name: "developers".to_string(),
path: Some("/".to_string()),
tags: None,
};
service.create_group(create_request).await.unwrap();
let update_request = UpdateGroupRequest {
group_name: "developers".to_string(),
new_group_name: Some("engineers".to_string()),
new_path: Some("/tech/".to_string()),
};
let updated = service.update_group(update_request).await.unwrap();
assert_eq!(updated.group_name, "engineers");
assert_eq!(updated.path, "/tech/");
}
#[tokio::test]
async fn test_delete_group() {
let service = setup_service();
let request = CreateGroupRequest {
group_name: "temp_group".to_string(),
path: None,
tags: None,
};
service.create_group(request).await.unwrap();
service.delete_group("temp_group").await.unwrap();
let retrieved = service.get_group("temp_group").await.unwrap();
assert!(retrieved.is_none());
}
#[tokio::test]
async fn test_list_groups() {
let service = setup_service();
for name in ["group1", "group2", "group3"] {
let request = CreateGroupRequest {
group_name: name.to_string(),
path: Some("/test/".to_string()),
tags: None,
};
service.create_group(request).await.unwrap();
}
let list_request = ListGroupsRequest {
path_prefix: Some("/test/".to_string()),
pagination: None,
};
let (groups, _, _) = service.list_groups(list_request).await.unwrap();
assert_eq!(groups.len(), 3);
}
#[tokio::test]
async fn test_group_membership() {
let store = Arc::new(RwLock::new(InMemoryWamiStore::default()));
let service = GroupService::new(store.clone(), "123456789012".to_string());
let user = user_builder::build_user(
"alice".to_string(),
Some("/".to_string()),
&*service.provider,
&service.account_id,
);
store.write().unwrap().create_user(user).await.unwrap();
let request = CreateGroupRequest {
group_name: "admins".to_string(),
path: None,
tags: None,
};
service.create_group(request).await.unwrap();
service.add_user_to_group("admins", "alice").await.unwrap();
let groups = service.list_groups_for_user("alice").await.unwrap();
assert_eq!(groups.len(), 1);
assert_eq!(groups[0].group_name, "admins");
service
.remove_user_from_group("admins", "alice")
.await
.unwrap();
let groups_after = service.list_groups_for_user("alice").await.unwrap();
assert_eq!(groups_after.len(), 0);
}
}