From fb3b58657f1f66f00b63193f316cb54c4f3488ec Mon Sep 17 00:00:00 2001 From: Mohammed Date: Mon, 6 Jul 2026 18:01:15 +0300 Subject: [PATCH] test: Add identity provider integration tests Signed-off-by: Mohammed --- tests/integration/src/identity.rs | 1 + tests/integration/src/identity/group.rs | 18 +++ .../integration/src/identity/group/create.rs | 34 +++++ .../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 | 3 + tests/integration/src/identity/user/create.rs | 25 ++++ tests/integration/src/identity/user/delete.rs | 57 ++++++++ .../src/identity/user/federated.rs | 71 +++++++++ .../src/identity/user/get_domain_id.rs | 38 +++++ tests/integration/src/identity/user/update.rs | 34 ++++- tests/integration/src/identity/user_group.rs | 4 + .../src/identity/user_group/add.rs | 38 +++++ .../src/identity/user_group/bulk.rs | 83 +++++++++++ .../src/identity/user_group/expiring.rs | 135 ++++++++++++++++++ .../src/identity/user_group/remove.rs | 55 +++++++ .../src/identity/user_group/set.rs | 58 ++++++++ 18 files changed, 842 insertions(+), 1 deletion(-) 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/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 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..ca3d9c7df --- /dev/null +++ b/tests/integration/src/identity/group/create.rs @@ -0,0 +1,34 @@ +// 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 crate::common::get_state; +use crate::{create_domain, create_group}; + +#[tokio::test] +#[traced_test] +async fn test_create() -> Result<()> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + + let group = create_group!(state, domain.id.clone())?; + + assert!(!group.id.is_empty(), "an id was generated"); + assert!(!group.name.is_empty()); + assert_eq!(group.domain_id, domain.id); + 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..104fcad42 --- /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 openstack_keystone::identity::IdentityApi; + +use crate::common::get_state; +use crate::{create_domain, create_group}; + +#[tokio::test] +#[traced_test] +async fn test_delete() -> Result<()> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let group = create_group!(state, domain.id.clone())?; + + 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(()) +} + +#[tokio::test] +#[traced_test] +async fn test_delete_not_found() -> Result<()> { + let (state, _tmp) = get_state().await?; + let result = state + .provider + .get_identity_provider() + .delete_group(&state, "does-not-exist") + .await; + assert!(result.is_err(), "deleting a missing group errors"); + 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..edadf329e 100644 --- a/tests/integration/src/identity/user.rs +++ b/tests/integration/src/identity/user.rs @@ -15,6 +15,9 @@ 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/create.rs b/tests/integration/src/identity/user/create.rs index 56b05f0cf..95c6140c0 100644 --- a/tests/integration/src/identity/user/create.rs +++ b/tests/integration/src/identity/user/create.rs @@ -404,3 +404,28 @@ async fn test_create_with_expiry_and_unique_count() -> Result<()> { Ok(()) } + +#[tokio::test] +#[traced_test] +async fn test_create_invalid_name_too_long() -> Result<()> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + + let result = state + .provider + .get_identity_provider() + .create_user( + &state, + UserCreateBuilder::default() + .name("x".repeat(256)) + .domain_id(domain.id.clone()) + .enabled(true) + .build()?, + ) + .await; + assert!( + result.is_err(), + "creating a user with an over-length name is rejected" + ); + Ok(()) +} diff --git a/tests/integration/src/identity/user/delete.rs b/tests/integration/src/identity/user/delete.rs new file mode 100644 index 000000000..d9d07bd1a --- /dev/null +++ b/tests/integration/src/identity/user/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 user functionality. + +use eyre::Result; +use tracing_test::traced_test; + +use openstack_keystone::identity::IdentityApi; + +use crate::common::get_state; +use crate::{create_domain, create_user}; + +#[tokio::test] +#[traced_test] +async fn test_delete() -> Result<()> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let user = create_user!(state, domain.id.clone())?; + + 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(()) +} + +#[tokio::test] +#[traced_test] +async fn test_delete_not_found() -> Result<()> { + let (state, _tmp) = get_state().await?; + let result = state + .provider + .get_identity_provider() + .delete_user(&state, "does-not-exist") + .await; + assert!(result.is_err(), "deleting a missing user errors"); + Ok(()) +} diff --git a/tests/integration/src/identity/user/federated.rs b/tests/integration/src/identity/user/federated.rs new file mode 100644 index 000000000..5d393e74c --- /dev/null +++ b/tests/integration/src/identity/user/federated.rs @@ -0,0 +1,71 @@ +// 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(); + + // Use the helper so the federated user is cleaned up automatically; the + // macro can't set the `federated` field we need here. + let created = crate::identity::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..29d62b672 --- /dev/null +++ b/tests/integration/src/identity/user/get_domain_id.rs @@ -0,0 +1,38 @@ +// 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 openstack_keystone::identity::IdentityApi; + +use crate::common::get_state; +use crate::{create_domain, create_user}; + +#[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 = create_user!(state, domain.id.clone())?; + + 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/update.rs b/tests/integration/src/identity/user/update.rs index f6a166385..40a5f715d 100644 --- a/tests/integration/src/identity/user/update.rs +++ b/tests/integration/src/identity/user/update.rs @@ -23,7 +23,7 @@ use openstack_keystone_core_types::auth::AuthenticationError; use openstack_keystone_core_types::identity::*; use crate::common::get_state; -use crate::create_domain; +use crate::{create_domain, create_user}; use super::helpers::{assert_expires_at_approx, setup_test_config}; @@ -102,6 +102,38 @@ async fn test_update_password_basic() -> Result<()> { Ok(()) } +#[tokio::test] +#[traced_test] +async fn test_update_name() -> Result<()> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let user = create_user!(state, domain.id.clone())?; + + let new_name = Uuid::new_v4().simple().to_string(); + let updated = state + .provider + .get_identity_provider() + .update_user( + &state, + &user.id, + UserUpdateBuilder::default() + .name(new_name.clone()) + .build()?, + ) + .await?; + assert_eq!(updated.name, new_name, "name was updated"); + + // Confirm the change was persisted. + let fetched = state + .provider + .get_identity_provider() + .get_user(&state, &user.id) + .await? + .expect("user found"); + assert_eq!(fetched.name, new_name, "updated name persisted"); + Ok(()) +} + #[tokio::test] #[traced_test] async fn test_update_password_with_expiry() -> Result<()> { diff --git a/tests/integration/src/identity/user_group.rs b/tests/integration/src/identity/user_group.rs index 386af43e1..80c6f6090 100644 --- a/tests/integration/src/identity/user_group.rs +++ b/tests/integration/src/identity/user_group.rs @@ -19,7 +19,11 @@ use openstack_keystone_core::keystone::ServiceState; use openstack_keystone_core_types::identity::*; mod add; +mod bulk; +mod expiring; 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/add.rs b/tests/integration/src/identity/user_group/add.rs index 381af40bf..21b9729c3 100644 --- a/tests/integration/src/identity/user_group/add.rs +++ b/tests/integration/src/identity/user_group/add.rs @@ -51,3 +51,41 @@ async fn test_expiring_groups() -> Result<()> { assert!(groups.iter().find(|x| x.id == group_b.id).is_some()); Ok(()) } + +#[tokio::test] +#[traced_test] +async fn test_add_user_to_nonexistent_group() -> Result<()> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let user = create_user!(state, domain.id.clone())?; + + let result = state + .provider + .get_identity_provider() + .add_user_to_group(&state, &user.id, "does-not-exist") + .await; + assert!( + result.is_err(), + "adding a user to a non-existent group errors" + ); + Ok(()) +} + +#[tokio::test] +#[traced_test] +async fn test_add_nonexistent_user_to_group() -> Result<()> { + let (state, _tmp) = get_state().await?; + let domain = create_domain!(state)?; + let group = create_group!(state, domain.id.clone())?; + + let result = state + .provider + .get_identity_provider() + .add_user_to_group(&state, "does-not-exist", &group.id) + .await; + assert!( + result.is_err(), + "adding a non-existent user to a group errors" + ); + Ok(()) +} 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(()) +} 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(()) +}