Skip to content

Commit eca9ff7

Browse files
committed
Simplify factories.Group(): don't add creators as members
Change `factories.Group()` to *not* automatically add the group's creator as a member of the group. Future commits need to replace the `group.members` relation with a `group.memberships` relation (which is a list of `GroupMembership`'s rather than a list of `User`'s). See <#9047>. This is necessary because `GroupMembership`'s will in future have additional attributes (e.g. `roles`) and to add a user to a group with a particular role it'll be necessary to append a `GroupMembership` with that role to `group.memberships`, it's not enough to append a `User` to `group.members` because the role is an attribute of the membership not an attribute of the user, so we need to actually create a `GroupMembership` with the desired role and append that. With this change it'll no longer be possible for `factories.Group`'s `add_creator_as_member()` to add the creator as a member. For example this kind of thing won't work: @factory.post_generation def add_creator_as_member( # pylint:disable=no-self-argument obj, _create, _extracted, **_kwargs ): if ( obj.creator and obj.creator not in obj.members ): obj.memberships.append( models.GroupMembership( group=obj, user=obj.creator, role="owner ) ) The problem is that the `GroupMembership` that's been appended will not have been added to the DB session, which causes this SQLAlchemy error: https://docs.sqlalchemy.org/en/20/errors.html#object-is-being-merged-into-a-session-along-the-backref-cascade Or alternatively you get a `NotNullViolation`, depending. Nor can `factories.Group.add_creator_as_member()` simply add the `GroupMembership` to the DB session: it doesn't have access to the DB session (and this wouldn't necessarily get around `NotNullViolation`'s anyway). Removing this feels like a good direction to me because `add_creator_as_member()` seems too clever for a test factory, and my experience with test factories is that having them do extra things like this automatically usually ends up creating problems and it's better to keep the factories simpler and just make certain tests do more work. There looks to have been a bunch of tests that were implicitly or explicitly relying on the fact that the factory adds the group's creator as a member, even when this concept is irrelevant to the test at hand. So I think removing this is a good thing. The current behavior is also potentially confusing when you do something like `factories.Group(members=[...])` and then it auto-generates a user to be the group's `creator` and adds them to the group's members even though that user wasn't in the members list that was passed in.
1 parent 3f9bf26 commit eca9ff7

6 files changed

Lines changed: 35 additions & 33 deletions

File tree

tests/common/factories/group.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,6 @@ def scopes( # pylint: disable=method-hidden,unused-argument
3333

3434
self.scopes = scopes or []
3535

36-
@factory.post_generation
37-
def add_creator_as_member( # pylint:disable=no-self-argument
38-
obj, _create, _extracted, **_kwargs
39-
):
40-
if (
41-
obj.creator
42-
and obj.creator
43-
not in obj.members # pylint:disable=unsupported-membership-test
44-
):
45-
obj.members.insert(0, obj.creator) # pylint:disable=no-member
46-
4736

4837
class OpenGroup(Group):
4938
name = factory.Sequence(lambda n: f"Open Group {n}")

tests/functional/api/groups/members_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import pytest
44

5+
from h.models import GroupMembership
56
from h.models.auth_client import GrantType
67

78

@@ -21,17 +22,16 @@ def test_it_returns_list_of_members_if_user_has_access_to_private_group(
2122
self, app, factories, db_session, group, user_with_token, token_auth_header
2223
):
2324
user, _ = user_with_token
24-
group.members.append(user)
25+
group.members.extend([user, factories.User()])
2526
db_session.commit()
27+
2628
res = app.get(
2729
"/api/groups/{pubid}/members".format(pubid=group.pubid),
2830
headers=token_auth_header,
2931
)
3032

3133
returned_usernames = [member["username"] for member in res.json]
32-
assert user.username in returned_usernames
33-
assert group.creator.username in returned_usernames
34-
34+
assert returned_usernames == [member.username for member in group.members]
3535
assert res.status_code == 200
3636

3737
def test_it_returns_404_if_user_does_not_have_read_access_to_group(

tests/functional/api/groups/read_test.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import pytest
44

5+
from h.models import GroupMembership
56
from h.models.auth_client import GrantType
67

78

@@ -20,8 +21,10 @@ def test_it_returns_private_groups_along_with_world_groups(
2021
self, app, factories, db_session, user_with_token, token_auth_header
2122
):
2223
user, _ = user_with_token
23-
group1 = factories.Group(creator=user)
24-
group2 = factories.Group(creator=user)
24+
group1 = factories.Group()
25+
db_session.add(GroupMembership(group_id=group1.id, user_id=user.id))
26+
group2 = factories.Group()
27+
db_session.add(GroupMembership(group_id=group2.id, user_id=user.id))
2528
db_session.commit()
2629

2730
res = app.get("/api/groups", headers=token_auth_header)
@@ -35,8 +38,8 @@ def test_it_overrides_authority_param_with_user_authority(
3538
self, app, factories, db_session, user_with_token, token_auth_header
3639
):
3740
user, _ = user_with_token
38-
# This group will be created with the user's authority
39-
group1 = factories.Group(creator=user)
41+
group1 = factories.Group(authority=user.authority)
42+
db_session.add(GroupMembership(group_id=group1.id, user_id=user.id))
4043
db_session.commit()
4144

4245
res = app.get("/api/groups?authority=whatever.com", headers=token_auth_header)
@@ -92,6 +95,7 @@ def test_it_returns_http_200_for_private_group_with_creator_authentication(
9295
):
9396
user, _ = user_with_token
9497
group = factories.Group(creator=user)
98+
db_session.add(GroupMembership(group_id=group.id, user_id=user.id))
9599
db_session.commit()
96100

97101
res = app.get(

tests/unit/h/services/group_list_test.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pytest
55
from h_matchers import Any
66

7-
from h.models.group import Group
7+
from h.models.group import Group, GroupMembership
88
from h.services.group_list import GroupListService, group_list_factory
99
from h.services.group_scope import GroupScopeService
1010

@@ -331,8 +331,10 @@ def document_uri():
331331

332332

333333
@pytest.fixture
334-
def sample_groups(factories, other_authority, document_uri, default_authority, user):
335-
return {
334+
def sample_groups(
335+
factories, other_authority, document_uri, default_authority, user, db_session
336+
):
337+
sample_groups = {
336338
"open": factories.OpenGroup(
337339
name="sample open",
338340
authority=default_authority,
@@ -354,6 +356,13 @@ def sample_groups(factories, other_authority, document_uri, default_authority, u
354356
"private": factories.Group(creator=user),
355357
}
356358

359+
# Make `user` a member of the sample_groups["private"].
360+
db_session.add(
361+
GroupMembership(user_id=user.id, group_id=sample_groups["private"].id)
362+
)
363+
364+
return sample_groups
365+
357366

358367
@pytest.fixture
359368
def group_scope_service(pyramid_config, sample_groups):

tests/unit/h/services/group_members_test.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,13 @@ def test_it_adds_users_in_userids(self, factories, group_members_service):
7676
def test_it_does_not_remove_existing_members(
7777
self, factories, group_members_service
7878
):
79-
creator = factories.User()
80-
group = factories.Group(creator=creator)
81-
users = [factories.User(), factories.User()]
82-
userids = [user.userid for user in users]
79+
group = factories.Group()
80+
existing_member = factories.User()
81+
group.members.append(existing_member)
8382

84-
group_members_service.add_members(group, userids)
83+
group_members_service.add_members(group, [factories.User().userid])
8584

86-
assert len(group.members) == len(users) + 1 # account for creator user
87-
assert creator in group.members
85+
assert existing_member in group.members
8886

8987

9088
class TestUpdateMembers:
@@ -143,7 +141,6 @@ def test_it_proxies_to_member_join_and_leave(
143141
)
144142
assert sorted(group_members_service.member_leave.call_args_list) == sorted(
145143
[
146-
mock.call(group, group.creator.userid),
147144
mock.call(group, new_members[0].userid),
148145
]
149146
)

tests/unit/h/views/activity_test.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,7 +1189,7 @@ def user_search_controller(self, user, pyramid_request):
11891189
@pytest.fixture
11901190
def group(factories):
11911191
group = factories.Group()
1192-
group.members.extend([factories.User(), factories.User()])
1192+
group.members.extend([group.creator, factories.User(), factories.User()])
11931193
return group
11941194

11951195

@@ -1203,20 +1203,23 @@ def no_creator_group(factories):
12031203
@pytest.fixture
12041204
def no_organization_group(factories):
12051205
group = factories.Group(organization=None)
1206-
group.members.extend([factories.User(), factories.User()])
1206+
group.members.extend([group.creator, factories.User(), factories.User()])
12071207
return group
12081208

12091209

12101210
@pytest.fixture
12111211
def open_group(factories):
12121212
open_group = factories.OpenGroup()
1213+
open_group.members.append(open_group.creator)
12131214
return open_group
12141215

12151216

12161217
@pytest.fixture
12171218
def restricted_group(factories):
12181219
restricted_group = factories.RestrictedGroup()
1219-
restricted_group.members.extend([factories.User(), factories.User()])
1220+
restricted_group.members.extend(
1221+
[restricted_group.creator, factories.User(), factories.User()]
1222+
)
12201223
return restricted_group
12211224

12221225

0 commit comments

Comments
 (0)