From c529f0274654f546cf64460173c07bb99d2f4897 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 23 Dec 2025 10:26:33 +0100 Subject: [PATCH 1/2] tests/system/framework/utils/tools.py: fix GShadowEntry GShadowEntry administrators and members represent a list of usernames, not a single string. Thus, set them to `list[str]`. This fixes type safety and clarifies the expected data structure. Fixes: 458700b5d670 (2025-09-10; "tests/system/framework/: fix Python linter issues") Signed-off-by: Iker Pedrosa --- tests/system/framework/utils/tools.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/system/framework/utils/tools.py b/tests/system/framework/utils/tools.py index a2c85cba4b..03a79219af 100644 --- a/tests/system/framework/utils/tools.py +++ b/tests/system/framework/utils/tools.py @@ -388,8 +388,8 @@ def __init__( self, name: str | None, password: str | None, - administrators: str | None, - members: str | None, + administrators: list[str], + members: list[str], ) -> None: self.name: str | None = name """ @@ -401,18 +401,18 @@ def __init__( Group password. """ - self.administrators: str | None = administrators + self.administrators: list[str] = administrators """ Group administrators. """ - self.members: str | None = members + self.members: list[str] = members """ Group members. """ def __str__(self) -> str: - return f"({self.name}:{self.password}:{self.administrators}:" f"{self.members})" + return f"({self.name}:{self.password}:" f"{self.administrators}:" f"{self.members})" def __repr__(self) -> str: return str(self) @@ -422,7 +422,7 @@ def FromDict(cls, d: dict[str, Any]) -> GShadowEntry: return cls( name=d.get("group_name", None), password=d.get("password", None), - administrators=d.get("administrators", None), + administrators=d.get("administrators", []), members=d.get("members", []), ) From 1ad1d8cda0892cc783b1dab9ecbcf38c893c33c8 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Tue, 23 Dec 2025 09:51:56 +0100 Subject: [PATCH 2/2] tests/system/tests/test_groupmod.py: add test for groupmod -U with user list Add comprehensive test for the groupmod -U option when provided with a list of users to set group membership. This test verifies: - Setting initial group membership with multiple users - Proper membership verification in both group and gshadow entries - Updating group membership by modifying the user list - Correct handling of membership changes in group databases Signed-off-by: Iker Pedrosa --- tests/system/tests/test_groupmod.py | 60 +++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/system/tests/test_groupmod.py b/tests/system/tests/test_groupmod.py index 6617602f5b..40aa38484e 100644 --- a/tests/system/tests/test_groupmod.py +++ b/tests/system/tests/test_groupmod.py @@ -69,3 +69,63 @@ def test_groupmod__u_option_empty_string_clears_members(shadow: Shadow): assert gshadow_entry is not None, "Group should be found" assert gshadow_entry.name == "tgroup", "Incorrect groupname" assert not gshadow_entry.members, "Group should have no members" + + +@pytest.mark.topology(KnownTopology.Shadow) +def test_groupmod__u_option_with_user_list(shadow: Shadow): + """ + :title: Test groupmod -U option with user list to set group membership + :setup: + 1. Create three test users + 2. Create test group + :steps: + 1. Set group membership to all three users using groupmod -U + 2. Verify all three users are members in group and gshadow entry + 3. Modify group membership to only two users using groupmod -U + 4. Verify updated membership in group and gshadow entries + :expectedresults: + 1. Initial groupmod -U command sets membership correctly for all three users + 2. group and gshadow entries show correct membership + 3. Second groupmod -U command updates membership correctly + 4. Updated group and gshadow entries reflect new membership + :customerscenario: False + """ + shadow.useradd("tuser1") + shadow.useradd("tuser2") + shadow.useradd("tuser3") + shadow.groupadd("tgroup") + shadow.groupmod("-U tuser1,tuser2,tuser3 tgroup") + + group_entry = shadow.tools.getent.group("tgroup") + assert group_entry is not None, "Group should be found" + assert group_entry.name == "tgroup", "Incorrect groupname" + assert len(group_entry.members) == 3, f"Group should have 3 members, but has {len(group_entry.members)}" + assert "tuser1" in group_entry.members, "tuser1 should be a member of tgroup" + assert "tuser2" in group_entry.members, "tuser2 should be a member of tgroup" + assert "tuser3" in group_entry.members, "tuser3 should be a member of tgroup" + + if shadow.host.features["gshadow"]: + gshadow_entry = shadow.tools.getent.gshadow("tgroup") + assert gshadow_entry is not None, "Group should be found" + assert gshadow_entry.name == "tgroup", "Incorrect groupname" + assert len(gshadow_entry.members) == 3, f"Group should have 3 members, but has {len(gshadow_entry.members)}" + assert "tuser1" in gshadow_entry.members, "tuser1 should be a member of tgroup" + assert "tuser2" in gshadow_entry.members, "tuser2 should be a member of tgroup" + assert "tuser3" in gshadow_entry.members, "tuser3 should be a member of tgroup" + + shadow.groupmod("-U tuser1,tuser2 tgroup") + + group_entry = shadow.tools.getent.group("tgroup") + assert group_entry is not None, "Group should be found" + assert group_entry.name == "tgroup", "Incorrect groupname" + assert len(group_entry.members) == 2, f"Group should have 2 members, but has {len(group_entry.members)}" + assert "tuser1" in group_entry.members, "tuser1 should be a member of tgroup" + assert "tuser2" in group_entry.members, "tuser2 should be a member of tgroup" + + if shadow.host.features["gshadow"]: + gshadow_entry = shadow.tools.getent.gshadow("tgroup") + assert gshadow_entry is not None, "Group should be found" + assert gshadow_entry.name == "tgroup", "Incorrect groupname" + assert len(gshadow_entry.members) == 2, f"Group should have 2 members, but has {len(gshadow_entry.members)}" + assert "tuser1" in gshadow_entry.members, "tuser1 should be a member of tgroup" + assert "tuser2" in gshadow_entry.members, "tuser2 should be a member of tgroup"