server: return user-defined attributes for groups in graphql

Part of #67
This commit is contained in:
Valentin Tolmer
2023-09-14 00:52:46 +02:00
committed by nitnelave
parent e53ce92c96
commit 034794d58d
7 changed files with 190 additions and 23 deletions
+3
View File
@@ -24,6 +24,8 @@ type Group {
displayName: String! displayName: String!
creationDate: DateTimeUtc! creationDate: DateTimeUtc!
uuid: String! uuid: String!
"User-defined attributes."
attributes: [AttributeValue!]!
"The groups to which this user belongs." "The groups to which this user belongs."
users: [User!]! users: [User!]!
} }
@@ -83,6 +85,7 @@ type User {
avatar: String avatar: String
creationDate: DateTimeUtc! creationDate: DateTimeUtc!
uuid: String! uuid: String!
"User-defined attributes."
attributes: [AttributeValue!]! attributes: [AttributeValue!]!
"The groups to which this user belongs." "The groups to which this user belongs."
groups: [Group!]! groups: [Group!]!
+16 -1
View File
@@ -1,7 +1,7 @@
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::domain::types::{GroupId, Serialized}; use crate::domain::types::{AttributeValue, GroupId, Serialized};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "group_attributes")] #[sea_orm(table_name = "group_attributes")]
@@ -55,3 +55,18 @@ impl Related<super::GroupAttributeSchema> for Entity {
} }
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
impl From<Model> for AttributeValue {
fn from(
Model {
group_id: _,
attribute_name,
value,
}: Model,
) -> Self {
Self {
name: attribute_name,
value,
}
}
}
+2
View File
@@ -37,6 +37,7 @@ impl From<Model> for crate::domain::types::Group {
creation_date: group.creation_date, creation_date: group.creation_date,
uuid: group.uuid, uuid: group.uuid,
users: vec![], users: vec![],
attributes: Vec::new(),
} }
} }
} }
@@ -48,6 +49,7 @@ impl From<Model> for crate::domain::types::GroupDetails {
display_name: group.display_name, display_name: group.display_name,
creation_date: group.creation_date, creation_date: group.creation_date,
uuid: group.uuid, uuid: group.uuid,
attributes: Vec::new(),
} }
} }
} }
+36 -7
View File
@@ -5,7 +5,7 @@ use crate::domain::{
}, },
model::{self, GroupColumn, MembershipColumn}, model::{self, GroupColumn, MembershipColumn},
sql_backend_handler::SqlBackendHandler, sql_backend_handler::SqlBackendHandler,
types::{Group, GroupDetails, GroupId, Uuid}, types::{AttributeValue, Group, GroupDetails, GroupId, Uuid},
}; };
use async_trait::async_trait; use async_trait::async_trait;
use sea_orm::{ use sea_orm::{
@@ -63,8 +63,7 @@ impl GroupListerBackendHandler for SqlBackendHandler {
#[instrument(skip(self), level = "debug", ret, err)] #[instrument(skip(self), level = "debug", ret, err)]
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>> { async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>> {
let results = model::Group::find() let results = model::Group::find()
// The order_by must be before find_with_related otherwise the primary order is by group_id. .order_by_asc(GroupColumn::GroupId)
.order_by_asc(GroupColumn::DisplayName)
.find_with_related(model::Membership) .find_with_related(model::Membership)
.filter( .filter(
filters filters
@@ -84,7 +83,7 @@ impl GroupListerBackendHandler for SqlBackendHandler {
) )
.all(&self.sql_pool) .all(&self.sql_pool)
.await?; .await?;
Ok(results let mut groups: Vec<_> = results
.into_iter() .into_iter()
.map(|(group, users)| { .map(|(group, users)| {
let users: Vec<_> = users.into_iter().map(|u| u.user_id).collect(); let users: Vec<_> = users.into_iter().map(|u| u.user_id).collect();
@@ -93,7 +92,30 @@ impl GroupListerBackendHandler for SqlBackendHandler {
..group.into() ..group.into()
} }
}) })
.collect()) .collect();
let group_ids = groups.iter().map(|u| &u.id);
let attributes = model::GroupAttributes::find()
.filter(model::GroupAttributesColumn::GroupId.is_in(group_ids))
.order_by_asc(model::GroupAttributesColumn::GroupId)
.order_by_asc(model::GroupAttributesColumn::AttributeName)
.all(&self.sql_pool)
.await?;
let mut attributes_iter = attributes.into_iter().peekable();
use itertools::Itertools; // For take_while_ref
for group in groups.iter_mut() {
assert!(attributes_iter
.peek()
.map(|u| u.group_id >= group.id)
.unwrap_or(true),
"Attributes are not sorted, groups are not sorted, or previous group didn't consume all the attributes");
group.attributes = attributes_iter
.take_while_ref(|u| u.group_id == group.id)
.map(AttributeValue::from)
.collect();
}
groups.sort_by(|g1, g2| g1.display_name.cmp(&g2.display_name));
Ok(groups)
} }
} }
@@ -101,11 +123,18 @@ impl GroupListerBackendHandler for SqlBackendHandler {
impl GroupBackendHandler for SqlBackendHandler { impl GroupBackendHandler for SqlBackendHandler {
#[instrument(skip(self), level = "debug", ret, err)] #[instrument(skip(self), level = "debug", ret, err)]
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupDetails> { async fn get_group_details(&self, group_id: GroupId) -> Result<GroupDetails> {
model::Group::find_by_id(group_id) let mut group_details = model::Group::find_by_id(group_id)
.one(&self.sql_pool) .one(&self.sql_pool)
.await? .await?
.map(Into::<GroupDetails>::into) .map(Into::<GroupDetails>::into)
.ok_or_else(|| DomainError::EntityNotFound(format!("{:?}", group_id))) .ok_or_else(|| DomainError::EntityNotFound(format!("{:?}", group_id)))?;
let attributes = model::GroupAttributes::find()
.filter(model::GroupAttributesColumn::GroupId.eq(group_details.group_id))
.order_by_asc(model::GroupAttributesColumn::AttributeName)
.all(&self.sql_pool)
.await?;
group_details.attributes = attributes.into_iter().map(AttributeValue::from).collect();
Ok(group_details)
} }
#[instrument(skip(self), level = "debug", err, fields(group_id = ?request.group_id))] #[instrument(skip(self), level = "debug", err, fields(group_id = ?request.group_id))]
+23 -3
View File
@@ -63,7 +63,7 @@ macro_rules! uuid {
}; };
} }
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, DeriveValueType)] #[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, DeriveValueType)]
#[sea_orm(column_type = "Binary(BlobSize::Long)", array_type = "Bytes")] #[sea_orm(column_type = "Binary(BlobSize::Long)", array_type = "Bytes")]
pub struct Serialized(Vec<u8>); pub struct Serialized(Vec<u8>);
@@ -283,7 +283,7 @@ impl IntoActiveValue<Serialized> for JpegPhoto {
} }
} }
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, Hash)]
pub struct AttributeValue { pub struct AttributeValue {
pub name: String, pub name: String,
pub value: Serialized, pub value: Serialized,
@@ -314,7 +314,19 @@ impl Default for User {
} }
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, DeriveValueType)] #[derive(
Debug,
Copy,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
DeriveValueType,
)]
pub struct GroupId(pub i32); pub struct GroupId(pub i32);
impl TryFromU64 for GroupId { impl TryFromU64 for GroupId {
@@ -323,6 +335,12 @@ impl TryFromU64 for GroupId {
} }
} }
impl From<&GroupId> for Value {
fn from(id: &GroupId) -> Self {
(*id).into()
}
}
#[derive( #[derive(
Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, EnumString, IntoStaticStr, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, EnumString, IntoStaticStr,
)] )]
@@ -375,6 +393,7 @@ pub struct Group {
pub creation_date: NaiveDateTime, pub creation_date: NaiveDateTime,
pub uuid: Uuid, pub uuid: Uuid,
pub users: Vec<UserId>, pub users: Vec<UserId>,
pub attributes: Vec<AttributeValue>,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
@@ -383,6 +402,7 @@ pub struct GroupDetails {
pub display_name: String, pub display_name: String,
pub creation_date: NaiveDateTime, pub creation_date: NaiveDateTime,
pub uuid: Uuid, pub uuid: Uuid,
pub attributes: Vec<AttributeValue>,
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
+98 -12
View File
@@ -286,7 +286,8 @@ impl<Handler: BackendHandler> User<Handler> {
self.user.uuid.as_str() self.user.uuid.as_str()
} }
fn attributes(&self) -> Vec<AttributeValue<Handler>> { /// User-defined attributes.
fn attributes(&self) -> Vec<AttributeValue<Handler, SchemaUserAttributeExtractor>> {
self.user self.user
.attributes .attributes
.clone() .clone()
@@ -344,6 +345,7 @@ pub struct Group<Handler: BackendHandler> {
display_name: String, display_name: String,
creation_date: chrono::NaiveDateTime, creation_date: chrono::NaiveDateTime,
uuid: String, uuid: String,
attributes: Vec<DomainAttributeValue>,
members: Option<Vec<String>>, members: Option<Vec<String>>,
_phantom: std::marker::PhantomData<Box<Handler>>, _phantom: std::marker::PhantomData<Box<Handler>>,
} }
@@ -362,6 +364,16 @@ impl<Handler: BackendHandler> Group<Handler> {
fn uuid(&self) -> String { fn uuid(&self) -> String {
self.uuid.clone() self.uuid.clone()
} }
/// User-defined attributes.
fn attributes(&self) -> Vec<AttributeValue<Handler, SchemaGroupAttributeExtractor>> {
self.attributes
.clone()
.into_iter()
.map(Into::into)
.collect()
}
/// The groups to which this user belongs. /// The groups to which this user belongs.
async fn users(&self, context: &Context<Handler>) -> FieldResult<Vec<User<Handler>>> { async fn users(&self, context: &Context<Handler>) -> FieldResult<Vec<User<Handler>>> {
let span = debug_span!("[GraphQL query] group::users"); let span = debug_span!("[GraphQL query] group::users");
@@ -392,6 +404,7 @@ impl<Handler: BackendHandler> From<GroupDetails> for Group<Handler> {
display_name: group_details.display_name, display_name: group_details.display_name,
creation_date: group_details.creation_date, creation_date: group_details.creation_date,
uuid: group_details.uuid.into_string(), uuid: group_details.uuid.into_string(),
attributes: group_details.attributes,
members: None, members: None,
_phantom: std::marker::PhantomData, _phantom: std::marker::PhantomData,
} }
@@ -405,6 +418,7 @@ impl<Handler: BackendHandler> From<DomainGroup> for Group<Handler> {
display_name: group.display_name, display_name: group.display_name,
creation_date: group.creation_date, creation_date: group.creation_date,
uuid: group.uuid.into_string(), uuid: group.uuid.into_string(),
attributes: group.attributes,
members: Some(group.users.into_iter().map(UserId::into_string).collect()), members: Some(group.users.into_iter().map(UserId::into_string).collect()),
_phantom: std::marker::PhantomData, _phantom: std::marker::PhantomData,
} }
@@ -501,14 +515,37 @@ impl<Handler: BackendHandler> From<DomainSchema> for Schema<Handler> {
} }
} }
trait SchemaAttributeExtractor: std::marker::Send {
fn get_attributes(schema: &DomainSchema) -> &DomainAttributeList;
}
struct SchemaUserAttributeExtractor;
impl SchemaAttributeExtractor for SchemaUserAttributeExtractor {
fn get_attributes(schema: &DomainSchema) -> &DomainAttributeList {
&schema.get_schema().user_attributes
}
}
struct SchemaGroupAttributeExtractor;
impl SchemaAttributeExtractor for SchemaGroupAttributeExtractor {
fn get_attributes(schema: &DomainSchema) -> &DomainAttributeList {
&schema.get_schema().group_attributes
}
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
pub struct AttributeValue<Handler: BackendHandler> { pub struct AttributeValue<Handler: BackendHandler, Extractor> {
attribute: DomainAttributeValue, attribute: DomainAttributeValue,
_phantom: std::marker::PhantomData<Box<Handler>>, _phantom: std::marker::PhantomData<Box<Handler>>,
_phantom_extractor: std::marker::PhantomData<Extractor>,
} }
#[graphql_object(context = Context<Handler>)] #[graphql_object(context = Context<Handler>)]
impl<Handler: BackendHandler> AttributeValue<Handler> { impl<Handler: BackendHandler, Extractor: SchemaAttributeExtractor>
AttributeValue<Handler, Extractor>
{
fn name(&self) -> &str { fn name(&self) -> &str {
&self.attribute.name &self.attribute.name
} }
@@ -518,19 +555,17 @@ impl<Handler: BackendHandler> AttributeValue<Handler> {
.get_user_restricted_lister_handler(&context.validation_result); .get_user_restricted_lister_handler(&context.validation_result);
serialize_attribute( serialize_attribute(
&self.attribute, &self.attribute,
&PublicSchema::from(handler.get_schema().await?), Extractor::get_attributes(&PublicSchema::from(handler.get_schema().await?)),
) )
} }
} }
pub fn serialize_attribute( pub fn serialize_attribute(
attribute: &DomainAttributeValue, attribute: &DomainAttributeValue,
schema: &DomainSchema, attributes: &DomainAttributeList,
) -> FieldResult<Vec<String>> { ) -> FieldResult<Vec<String>> {
let convert_date = |date| chrono::Utc.from_utc_datetime(&date).to_rfc3339(); let convert_date = |date| chrono::Utc.from_utc_datetime(&date).to_rfc3339();
schema attributes
.get_schema()
.user_attributes
.get_attribute_type(&attribute.name) .get_attribute_type(&attribute.name)
.map(|attribute_type| { .map(|attribute_type| {
match attribute_type { match attribute_type {
@@ -575,11 +610,14 @@ pub fn serialize_attribute(
.ok_or_else(|| FieldError::from(anyhow::anyhow!("Unknown attribute: {}", &attribute.name))) .ok_or_else(|| FieldError::from(anyhow::anyhow!("Unknown attribute: {}", &attribute.name)))
} }
impl<Handler: BackendHandler> From<DomainAttributeValue> for AttributeValue<Handler> { impl<Handler: BackendHandler, Extractor> From<DomainAttributeValue>
for AttributeValue<Handler, Extractor>
{
fn from(value: DomainAttributeValue) -> Self { fn from(value: DomainAttributeValue) -> Self {
Self { Self {
attribute: value, attribute: value,
_phantom: std::marker::PhantomData, _phantom: std::marker::PhantomData,
_phantom_extractor: std::marker::PhantomData,
} }
} }
} }
@@ -634,12 +672,49 @@ mod tests {
displayName displayName
creationDate creationDate
uuid uuid
attributes {
name
value
}
} }
} }
}"#; }"#;
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
setup_default_schema(&mut mock); mock.expect_get_schema().returning(|| {
Ok(crate::domain::handler::Schema {
user_attributes: DomainAttributeList {
attributes: vec![
DomainAttributeSchema {
name: "first_name".to_owned(),
attribute_type: AttributeType::String,
is_list: false,
is_visible: true,
is_editable: true,
is_hardcoded: true,
},
DomainAttributeSchema {
name: "last_name".to_owned(),
attribute_type: AttributeType::String,
is_list: false,
is_visible: true,
is_editable: true,
is_hardcoded: true,
},
],
},
group_attributes: DomainAttributeList {
attributes: vec![DomainAttributeSchema {
name: "club_name".to_owned(),
attribute_type: AttributeType::String,
is_list: false,
is_visible: true,
is_editable: true,
is_hardcoded: false,
}],
},
})
});
mock.expect_get_user_details() mock.expect_get_user_details()
.with(eq(UserId::new("bob"))) .with(eq(UserId::new("bob")))
.return_once(|_| { .return_once(|_| {
@@ -667,12 +742,17 @@ mod tests {
display_name: "Bobbersons".to_string(), display_name: "Bobbersons".to_string(),
creation_date: chrono::Utc.timestamp_nanos(42).naive_utc(), creation_date: chrono::Utc.timestamp_nanos(42).naive_utc(),
uuid: crate::uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"), uuid: crate::uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
attributes: vec![DomainAttributeValue {
name: "club_name".to_owned(),
value: Serialized::from("Gang of Four"),
}],
}); });
groups.insert(GroupDetails { groups.insert(GroupDetails {
group_id: GroupId(7), group_id: GroupId(7),
display_name: "Jefferees".to_string(), display_name: "Jefferees".to_string(),
creation_date: chrono::Utc.timestamp_nanos(12).naive_utc(), creation_date: chrono::Utc.timestamp_nanos(12).naive_utc(),
uuid: crate::uuid!("b1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"), uuid: crate::uuid!("b1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
attributes: Vec::new(),
}); });
mock.expect_get_user_groups() mock.expect_get_user_groups()
.with(eq(UserId::new("bob"))) .with(eq(UserId::new("bob")))
@@ -704,13 +784,19 @@ mod tests {
"id": 3, "id": 3,
"displayName": "Bobbersons", "displayName": "Bobbersons",
"creationDate": "1970-01-01T00:00:00.000000042+00:00", "creationDate": "1970-01-01T00:00:00.000000042+00:00",
"uuid": "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8" "uuid": "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8",
"attributes": [{
"name": "club_name",
"value": ["Gang of Four"],
},
],
}, },
{ {
"id": 7, "id": 7,
"displayName": "Jefferees", "displayName": "Jefferees",
"creationDate": "1970-01-01T00:00:00.000000012+00:00", "creationDate": "1970-01-01T00:00:00.000000012+00:00",
"uuid": "b1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8" "uuid": "b1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8",
"attributes": [],
}] }]
} }
}), }),
+12
View File
@@ -856,6 +856,7 @@ mod tests {
display_name: group, display_name: group,
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"), uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
attributes: Vec::new(),
}); });
Ok(set) Ok(set)
}); });
@@ -942,6 +943,7 @@ mod tests {
display_name: "lldap_admin".to_string(), display_name: "lldap_admin".to_string(),
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"), uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
attributes: Vec::new(),
}); });
Ok(set) Ok(set)
}); });
@@ -1028,6 +1030,7 @@ mod tests {
display_name: "rockstars".to_string(), display_name: "rockstars".to_string(),
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"), uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
attributes: Vec::new(),
}]), }]),
}]) }])
}); });
@@ -1344,6 +1347,7 @@ mod tests {
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
users: vec![UserId::new("bob"), UserId::new("john")], users: vec![UserId::new("bob"), UserId::new("john")],
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"), uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
attributes: Vec::new(),
}, },
Group { Group {
id: GroupId(3), id: GroupId(3),
@@ -1351,6 +1355,7 @@ mod tests {
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
users: vec![UserId::new("john")], users: vec![UserId::new("john")],
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"), uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
attributes: Vec::new(),
}, },
]) ])
}); });
@@ -1442,6 +1447,7 @@ mod tests {
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
users: vec![], users: vec![],
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"), uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
attributes: Vec::new(),
}]) }])
}); });
let mut ldap_handler = setup_bound_admin_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
@@ -1512,6 +1518,7 @@ mod tests {
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
users: vec![], users: vec![],
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"), uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
attributes: Vec::new(),
}]) }])
}); });
let mut ldap_handler = setup_bound_admin_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
@@ -1881,6 +1888,7 @@ mod tests {
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
users: vec![UserId::new("bob"), UserId::new("john")], users: vec![UserId::new("bob"), UserId::new("john")],
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"), uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
attributes: Vec::new(),
}]) }])
}); });
let mut ldap_handler = setup_bound_admin_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
@@ -1963,6 +1971,7 @@ mod tests {
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
users: vec![UserId::new("bob"), UserId::new("john")], users: vec![UserId::new("bob"), UserId::new("john")],
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"), uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
attributes: Vec::new(),
}]) }])
}); });
let mut ldap_handler = setup_bound_admin_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
@@ -2344,6 +2353,7 @@ mod tests {
display_name: "lldap_admin".to_string(), display_name: "lldap_admin".to_string(),
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"), uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
attributes: Vec::new(),
}); });
mock.expect_get_user_groups() mock.expect_get_user_groups()
.with(eq(UserId::new("bob"))) .with(eq(UserId::new("bob")))
@@ -2571,6 +2581,7 @@ mod tests {
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
users: vec![UserId::new("bob")], users: vec![UserId::new("bob")],
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"), uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
attributes: Vec::new(),
}]) }])
}); });
let mut ldap_handler = setup_bound_admin_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
@@ -2664,6 +2675,7 @@ mod tests {
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
users: vec![UserId::new("bob")], users: vec![UserId::new("bob")],
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"), uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
attributes: Vec::new(),
}]) }])
}); });
let mut ldap_handler = setup_bound_admin_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;