From c5f1f8999445cb3dee1aea9395b54ac28fc4647a Mon Sep 17 00:00:00 2001 From: Mohammed Date: Tue, 23 Jun 2026 16:06:56 +0300 Subject: [PATCH 1/2] test: Add identity provider integration tests --- tests/integration/src/identity.rs | 1 + tests/integration/src/identity/group.rs | 18 +++++ .../integration/src/identity/group/create.rs | 52 +++++++++++++ .../integration/src/identity/group/delete.rs | 57 ++++++++++++++ tests/integration/src/identity/group/get.rs | 54 +++++++++++++ tests/integration/src/identity/group/list.rs | 78 +++++++++++++++++++ tests/integration/src/identity/user.rs | 1 + tests/integration/src/identity/user/delete.rs | 58 ++++++++++++++ tests/integration/src/identity/user_group.rs | 2 + .../src/identity/user_group/remove.rs | 55 +++++++++++++ .../src/identity/user_group/set.rs | 58 ++++++++++++++ 11 files changed, 434 insertions(+) create mode 100644 tests/integration/src/identity/group.rs create mode 100644 tests/integration/src/identity/group/create.rs create mode 100644 tests/integration/src/identity/group/delete.rs create mode 100644 tests/integration/src/identity/group/get.rs create mode 100644 tests/integration/src/identity/group/list.rs create mode 100644 tests/integration/src/identity/user/delete.rs create mode 100644 tests/integration/src/identity/user_group/remove.rs create mode 100644 tests/integration/src/identity/user_group/set.rs diff --git a/tests/integration/src/identity.rs b/tests/integration/src/identity.rs index 94fa8a599..73fd6e0c7 100644 --- a/tests/integration/src/identity.rs +++ b/tests/integration/src/identity.rs @@ -25,6 +25,7 @@ use openstack_keystone_core_types::identity::*; use crate::common::*; use crate::impl_deleter; +mod group; mod service_account; mod user; mod user_group; diff --git a/tests/integration/src/identity/group.rs b/tests/integration/src/identity/group.rs new file mode 100644 index 000000000..7edc051f4 --- /dev/null +++ b/tests/integration/src/identity/group.rs @@ -0,0 +1,18 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +mod create; +mod delete; +mod get; +mod list; diff --git a/tests/integration/src/identity/group/create.rs b/tests/integration/src/identity/group/create.rs new file mode 100644 index 000000000..04d32689c --- /dev/null +++ b/tests/integration/src/identity/group/create.rs @@ -0,0 +1,52 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +//! Test create group functionality. + +use eyre::Result; +use tracing_test::traced_test; +use uuid::Uuid; + +use openstack_keystone::identity::IdentityApi; +use openstack_keystone_core_types::identity::GroupCreate; + +use crate::common::get_state; +use crate::create_domain; + +#[tokio::test] +#[traced_test] +async fn test_create() -> Result<()> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let name = Uuid::new_v4().to_string(); + + let group = state + .provider + .get_identity_provider() + .create_group( + &state, + GroupCreate { + name: name.clone(), + domain_id: domain.id.clone(), + description: Some("a group".into()), + ..Default::default() + }, + ) + .await?; + + assert!(!group.id.is_empty(), "an id was generated"); + assert_eq!(group.name, name); + assert_eq!(group.domain_id, domain.id); + assert_eq!(group.description.as_deref(), Some("a group")); + Ok(()) +} diff --git a/tests/integration/src/identity/group/delete.rs b/tests/integration/src/identity/group/delete.rs new file mode 100644 index 000000000..f977f58c4 --- /dev/null +++ b/tests/integration/src/identity/group/delete.rs @@ -0,0 +1,57 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +//! Test delete group functionality. + +use eyre::Result; +use tracing_test::traced_test; +use uuid::Uuid; + +use openstack_keystone::identity::IdentityApi; +use openstack_keystone_core_types::identity::GroupCreate; + +use crate::common::get_state; +use crate::create_domain; + +#[tokio::test] +#[traced_test] +async fn test_delete() -> Result<()> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let group = state + .provider + .get_identity_provider() + .create_group( + &state, + GroupCreate { + name: Uuid::new_v4().to_string(), + domain_id: domain.id.clone(), + ..Default::default() + }, + ) + .await?; + + state + .provider + .get_identity_provider() + .delete_group(&state, &group.id) + .await?; + + let fetched = state + .provider + .get_identity_provider() + .get_group(&state, &group.id) + .await?; + assert!(fetched.is_none(), "group is gone after delete"); + Ok(()) +} diff --git a/tests/integration/src/identity/group/get.rs b/tests/integration/src/identity/group/get.rs new file mode 100644 index 000000000..84a862ae6 --- /dev/null +++ b/tests/integration/src/identity/group/get.rs @@ -0,0 +1,54 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +//! Test get group functionality. + +use eyre::Result; +use tracing_test::traced_test; + +use openstack_keystone::identity::IdentityApi; + +use crate::common::get_state; +use crate::{create_domain, create_group}; + +#[tokio::test] +#[traced_test] +async fn test_get() -> Result<()> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let group = create_group!(state, domain.id.clone())?; + + let fetched = state + .provider + .get_identity_provider() + .get_group(&state, &group.id) + .await? + .expect("group found"); + assert_eq!(fetched.id, group.id); + assert_eq!(fetched.name, group.name); + assert_eq!(fetched.domain_id, group.domain_id); + Ok(()) +} + +#[tokio::test] +#[traced_test] +async fn test_get_not_found() -> Result<()> { + let (state, _tmp) = get_state().await?; + let result = state + .provider + .get_identity_provider() + .get_group(&state, "missing") + .await?; + assert!(result.is_none(), "a missing group returns None"); + Ok(()) +} diff --git a/tests/integration/src/identity/group/list.rs b/tests/integration/src/identity/group/list.rs new file mode 100644 index 000000000..98fb6ca23 --- /dev/null +++ b/tests/integration/src/identity/group/list.rs @@ -0,0 +1,78 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +//! Test list groups functionality. + +use eyre::Result; +use tracing_test::traced_test; + +use openstack_keystone::identity::IdentityApi; +use openstack_keystone_core_types::identity::GroupListParameters; + +use crate::common::get_state; +use crate::{create_domain, create_group}; + +#[tokio::test] +#[traced_test] +async fn test_list() -> Result<()> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let group_a = create_group!(state, domain.id.clone())?; + let group_b = create_group!(state, domain.id.clone())?; + + let groups = state + .provider + .get_identity_provider() + .list_groups(&state, &GroupListParameters::default()) + .await?; + + assert!( + groups.iter().any(|g| g.id == group_a.id), + "first group is listed" + ); + assert!( + groups.iter().any(|g| g.id == group_b.id), + "second group is listed" + ); + Ok(()) +} + +#[tokio::test] +#[traced_test] +async fn test_list_by_domain() -> Result<()> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let group = create_group!(state, domain.id.clone())?; + + let groups = state + .provider + .get_identity_provider() + .list_groups( + &state, + &GroupListParameters { + domain_id: Some(domain.id.clone()), + name: None, + }, + ) + .await?; + + assert!( + groups.iter().any(|g| g.id == group.id), + "group is listed for its domain" + ); + assert!( + groups.iter().all(|g| g.domain_id == domain.id), + "only groups from the requested domain are returned" + ); + Ok(()) +} diff --git a/tests/integration/src/identity/user.rs b/tests/integration/src/identity/user.rs index 9bd4bd8f4..094eb20b1 100644 --- a/tests/integration/src/identity/user.rs +++ b/tests/integration/src/identity/user.rs @@ -15,6 +15,7 @@ pub(crate) mod helpers; mod create; +mod delete; mod get; mod list; mod update; diff --git a/tests/integration/src/identity/user/delete.rs b/tests/integration/src/identity/user/delete.rs new file mode 100644 index 000000000..75d13098e --- /dev/null +++ b/tests/integration/src/identity/user/delete.rs @@ -0,0 +1,58 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +//! Test delete user functionality. + +use eyre::Result; +use tracing_test::traced_test; +use uuid::Uuid; + +use openstack_keystone::identity::IdentityApi; +use openstack_keystone_core_types::identity::UserCreateBuilder; + +use crate::common::get_state; +use crate::create_domain; + +#[tokio::test] +#[traced_test] +async fn test_delete() -> Result<()> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + + let user = state + .provider + .get_identity_provider() + .create_user( + &state, + UserCreateBuilder::default() + .name(Uuid::new_v4().to_string()) + .domain_id(domain.id.clone()) + .enabled(true) + .build()?, + ) + .await?; + + state + .provider + .get_identity_provider() + .delete_user(&state, &user.id) + .await?; + + let fetched = state + .provider + .get_identity_provider() + .get_user(&state, &user.id) + .await?; + assert!(fetched.is_none(), "user is gone after delete"); + Ok(()) +} diff --git a/tests/integration/src/identity/user_group.rs b/tests/integration/src/identity/user_group.rs index 386af43e1..fa54fcee8 100644 --- a/tests/integration/src/identity/user_group.rs +++ b/tests/integration/src/identity/user_group.rs @@ -20,6 +20,8 @@ use openstack_keystone_core_types::identity::*; mod add; mod list; +mod remove; +mod set; async fn list_user_groups(state: &ServiceState, user_id: U) -> Result, Report> where diff --git a/tests/integration/src/identity/user_group/remove.rs b/tests/integration/src/identity/user_group/remove.rs new file mode 100644 index 000000000..c953ea374 --- /dev/null +++ b/tests/integration/src/identity/user_group/remove.rs @@ -0,0 +1,55 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +//! Test remove user group membership functionality. + +use eyre::Report; +use tracing_test::traced_test; + +use openstack_keystone::identity::IdentityApi; + +use super::*; +use crate::common::get_state; +use crate::{create_domain, create_group, create_user}; + +#[tokio::test] +#[traced_test] +async fn test_remove_user_from_group() -> Result<(), Report> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let user = create_user!(state, domain.id.clone())?; + let group_a = create_group!(state, domain.id.clone())?; + let group_b = create_group!(state, domain.id.clone())?; + + state + .provider + .get_identity_provider() + .add_user_to_group(&state, &user.id, &group_a.id) + .await?; + state + .provider + .get_identity_provider() + .add_user_to_group(&state, &user.id, &group_b.id) + .await?; + + state + .provider + .get_identity_provider() + .remove_user_from_group(&state, &user.id, &group_a.id) + .await?; + + let groups = list_user_groups(&state, &user.id).await?; + assert_eq!(groups.len(), 1, "one membership remains"); + assert_eq!(groups[0].id, group_b.id, "the other group remains"); + Ok(()) +} diff --git a/tests/integration/src/identity/user_group/set.rs b/tests/integration/src/identity/user_group/set.rs new file mode 100644 index 000000000..fa2096b51 --- /dev/null +++ b/tests/integration/src/identity/user_group/set.rs @@ -0,0 +1,58 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +//! Test setting user group memberships. + +use std::collections::HashSet; + +use eyre::Report; +use tracing_test::traced_test; + +use openstack_keystone::identity::IdentityApi; + +use super::*; +use crate::common::get_state; +use crate::{create_domain, create_group, create_user}; + +#[tokio::test] +#[traced_test] +async fn test_set_user_groups() -> Result<(), Report> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let user = create_user!(state, domain.id.clone())?; + let group_a = create_group!(state, domain.id.clone())?; + let group_b = create_group!(state, domain.id.clone())?; + + // Start as a member of group_a only. + state + .provider + .get_identity_provider() + .add_user_to_group(&state, &user.id, &group_a.id) + .await?; + + // Replace the whole membership set with group_b only. + let groups: HashSet<&str> = HashSet::from([group_b.id.as_str()]); + state + .provider + .get_identity_provider() + .set_user_groups(&state, &user.id, groups) + .await?; + + let memberships = list_user_groups(&state, &user.id).await?; + assert_eq!(memberships.len(), 1, "membership set was replaced"); + assert_eq!( + memberships[0].id, group_b.id, + "only the newly set group remains" + ); + Ok(()) +} From 1446772f04ab51c66c30952223ffb921173a493f Mon Sep 17 00:00:00 2001 From: Mohammed Date: Tue, 23 Jun 2026 16:20:58 +0300 Subject: [PATCH 2/2] test: Cover remaining identity provider methods --- tests/integration/src/identity/user.rs | 2 + .../src/identity/user/federated.rs | 72 ++++++++++ .../src/identity/user/get_domain_id.rs | 52 +++++++ tests/integration/src/identity/user_group.rs | 2 + .../src/identity/user_group/bulk.rs | 83 +++++++++++ .../src/identity/user_group/expiring.rs | 135 ++++++++++++++++++ 6 files changed, 346 insertions(+) create mode 100644 tests/integration/src/identity/user/federated.rs create mode 100644 tests/integration/src/identity/user/get_domain_id.rs create mode 100644 tests/integration/src/identity/user_group/bulk.rs create mode 100644 tests/integration/src/identity/user_group/expiring.rs diff --git a/tests/integration/src/identity/user.rs b/tests/integration/src/identity/user.rs index 094eb20b1..edadf329e 100644 --- a/tests/integration/src/identity/user.rs +++ b/tests/integration/src/identity/user.rs @@ -16,6 +16,8 @@ pub(crate) mod helpers; mod create; mod delete; +mod federated; mod get; +mod get_domain_id; mod list; mod update; diff --git a/tests/integration/src/identity/user/federated.rs b/tests/integration/src/identity/user/federated.rs new file mode 100644 index 000000000..7ee00f980 --- /dev/null +++ b/tests/integration/src/identity/user/federated.rs @@ -0,0 +1,72 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +//! Test find_federated_user functionality. + +use eyre::Result; +use tracing_test::traced_test; +use uuid::Uuid; + +use openstack_keystone::identity::IdentityApi; +use openstack_keystone_core_types::identity::{Federation, FederationProtocol, UserCreateBuilder}; + +use crate::common::get_state; +use crate::create_domain; + +#[tokio::test] +#[traced_test] +async fn test_find_federated_user() -> Result<()> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let unique_id = Uuid::new_v4().to_string(); + + let created = state + .provider + .get_identity_provider() + .create_user( + &state, + UserCreateBuilder::default() + .name(Uuid::new_v4().to_string()) + .domain_id(domain.id.clone()) + .enabled(true) + .federated(vec![Federation { + idp_id: "idp_id".into(), + unique_id: unique_id.clone(), + protocols: vec![FederationProtocol { + protocol_id: "mapped".into(), + unique_id: unique_id.clone(), + }], + }]) + .build()?, + ) + .await?; + + let found = state + .provider + .get_identity_provider() + .find_federated_user(&state, "idp_id", &unique_id) + .await? + .expect("federated user found"); + assert_eq!( + found.id, created.id, + "finds the federated user by unique id" + ); + + let missing = state + .provider + .get_identity_provider() + .find_federated_user(&state, "idp_id", "does-not-exist") + .await?; + assert!(missing.is_none(), "an unknown unique id returns None"); + Ok(()) +} diff --git a/tests/integration/src/identity/user/get_domain_id.rs b/tests/integration/src/identity/user/get_domain_id.rs new file mode 100644 index 000000000..df7c56aa9 --- /dev/null +++ b/tests/integration/src/identity/user/get_domain_id.rs @@ -0,0 +1,52 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +//! Test get_user_domain_id functionality. + +use eyre::Result; +use tracing_test::traced_test; +use uuid::Uuid; + +use openstack_keystone::identity::IdentityApi; +use openstack_keystone_core_types::identity::UserCreateBuilder; + +use crate::common::get_state; +use crate::create_domain; + +#[tokio::test] +#[traced_test] +async fn test_get_user_domain_id() -> Result<()> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + + let user = state + .provider + .get_identity_provider() + .create_user( + &state, + UserCreateBuilder::default() + .name(Uuid::new_v4().to_string()) + .domain_id(domain.id.clone()) + .enabled(true) + .build()?, + ) + .await?; + + let domain_id = state + .provider + .get_identity_provider() + .get_user_domain_id(&state, &user.id) + .await?; + assert_eq!(domain_id, domain.id, "domain id matches the user's domain"); + Ok(()) +} diff --git a/tests/integration/src/identity/user_group.rs b/tests/integration/src/identity/user_group.rs index fa54fcee8..80c6f6090 100644 --- a/tests/integration/src/identity/user_group.rs +++ b/tests/integration/src/identity/user_group.rs @@ -19,6 +19,8 @@ use openstack_keystone_core::keystone::ServiceState; use openstack_keystone_core_types::identity::*; mod add; +mod bulk; +mod expiring; mod list; mod remove; mod set; diff --git a/tests/integration/src/identity/user_group/bulk.rs b/tests/integration/src/identity/user_group/bulk.rs new file mode 100644 index 000000000..8398f44e9 --- /dev/null +++ b/tests/integration/src/identity/user_group/bulk.rs @@ -0,0 +1,83 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +//! Test bulk user group membership operations. + +use std::collections::HashSet; + +use eyre::Report; +use tracing_test::traced_test; + +use openstack_keystone::identity::IdentityApi; + +use super::*; +use crate::common::get_state; +use crate::{create_domain, create_group, create_user}; + +#[tokio::test] +#[traced_test] +async fn test_add_users_to_groups() -> Result<(), Report> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let user = create_user!(state, domain.id.clone())?; + let group_a = create_group!(state, domain.id.clone())?; + let group_b = create_group!(state, domain.id.clone())?; + + state + .provider + .get_identity_provider() + .add_users_to_groups( + &state, + vec![ + (user.id.as_str(), group_a.id.as_str()), + (user.id.as_str(), group_b.id.as_str()), + ], + ) + .await?; + + let groups = list_user_groups(&state, &user.id).await?; + assert_eq!(groups.len(), 2, "both memberships were added"); + Ok(()) +} + +#[tokio::test] +#[traced_test] +async fn test_remove_user_from_groups() -> Result<(), Report> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let user = create_user!(state, domain.id.clone())?; + let group_a = create_group!(state, domain.id.clone())?; + let group_b = create_group!(state, domain.id.clone())?; + + state + .provider + .get_identity_provider() + .add_user_to_group(&state, &user.id, &group_a.id) + .await?; + state + .provider + .get_identity_provider() + .add_user_to_group(&state, &user.id, &group_b.id) + .await?; + + let to_remove: HashSet<&str> = HashSet::from([group_a.id.as_str(), group_b.id.as_str()]); + state + .provider + .get_identity_provider() + .remove_user_from_groups(&state, &user.id, to_remove) + .await?; + + let groups = list_user_groups(&state, &user.id).await?; + assert!(groups.is_empty(), "all memberships were removed"); + Ok(()) +} diff --git a/tests/integration/src/identity/user_group/expiring.rs b/tests/integration/src/identity/user_group/expiring.rs new file mode 100644 index 000000000..ec98d8111 --- /dev/null +++ b/tests/integration/src/identity/user_group/expiring.rs @@ -0,0 +1,135 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +//! Test expiring user group membership operations. + +use std::collections::HashSet; + +use chrono::Utc; +use eyre::Report; +use tracing_test::traced_test; + +use openstack_keystone::identity::IdentityApi; + +use super::*; +use crate::common::get_state; +use crate::{create_domain, create_group, create_user}; + +#[tokio::test] +#[traced_test] +async fn test_add_users_to_groups_expiring() -> Result<(), Report> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let user = create_user!(state, domain.id.clone())?; + let group_a = create_group!(state, domain.id.clone())?; + let group_b = create_group!(state, domain.id.clone())?; + + state + .provider + .get_identity_provider() + .add_users_to_groups_expiring( + &state, + vec![ + (user.id.as_str(), group_a.id.as_str()), + (user.id.as_str(), group_b.id.as_str()), + ], + "idp_id", + ) + .await?; + + let groups = list_user_groups(&state, &user.id).await?; + assert_eq!(groups.len(), 2, "both expiring memberships are active"); + Ok(()) +} + +#[tokio::test] +#[traced_test] +async fn test_set_user_groups_expiring() -> Result<(), Report> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let user = create_user!(state, domain.id.clone())?; + let group = create_group!(state, domain.id.clone())?; + + let now = Utc::now(); + let groups: HashSet<&str> = HashSet::from([group.id.as_str()]); + state + .provider + .get_identity_provider() + .set_user_groups_expiring(&state, &user.id, groups, "idp_id", Some(&now)) + .await?; + + let memberships = list_user_groups(&state, &user.id).await?; + assert!( + memberships.iter().any(|g| g.id == group.id), + "the expiring membership is active" + ); + Ok(()) +} + +#[tokio::test] +#[traced_test] +async fn test_remove_user_from_group_expiring() -> Result<(), Report> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let user = create_user!(state, domain.id.clone())?; + let group = create_group!(state, domain.id.clone())?; + + state + .provider + .get_identity_provider() + .add_user_to_group_expiring(&state, &user.id, &group.id, "idp_id") + .await?; + state + .provider + .get_identity_provider() + .remove_user_from_group_expiring(&state, &user.id, &group.id, "idp_id") + .await?; + + let groups = list_user_groups(&state, &user.id).await?; + assert!(groups.is_empty(), "expiring membership was removed"); + Ok(()) +} + +#[tokio::test] +#[traced_test] +async fn test_remove_user_from_groups_expiring() -> Result<(), Report> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let user = create_user!(state, domain.id.clone())?; + let group_a = create_group!(state, domain.id.clone())?; + let group_b = create_group!(state, domain.id.clone())?; + + state + .provider + .get_identity_provider() + .add_users_to_groups_expiring( + &state, + vec![ + (user.id.as_str(), group_a.id.as_str()), + (user.id.as_str(), group_b.id.as_str()), + ], + "idp_id", + ) + .await?; + + let to_remove: HashSet<&str> = HashSet::from([group_a.id.as_str(), group_b.id.as_str()]); + state + .provider + .get_identity_provider() + .remove_user_from_groups_expiring(&state, &user.id, to_remove, "idp_id") + .await?; + + let groups = list_user_groups(&state, &user.id).await?; + assert!(groups.is_empty(), "all expiring memberships were removed"); + Ok(()) +}