Skip to content

Commit 1cc63cf

Browse files
committed
6009: trim comments, scope accept() to actual toggles, per rtibbles review
- InvitationSerializer.update(): read `accepted` only from validated_data (drop the `or instance.accepted` fallback) so accept() only fires on an actual incoming toggle, not on every later update to an already-accepted invitation, per rtibbles' review comment. - Trim the LLM-verbose in-line comments this PR added in invitation.py and test_invitation.py down to 1-2 lines each, per rtibbles' request to keep them concise. Also softened the co-owner share_mode test comment, which asserted a maintainer confirmation that wasn't on record, to describe observed behavior instead.
1 parent a366dd5 commit 1cc63cf

2 files changed

Lines changed: 15 additions & 36 deletions

File tree

contentcuration/contentcuration/tests/viewsets/test_invitation.py

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,9 @@ def test_update_invitation_accept(self):
147147
self.assertTrue(models.Change.objects.filter(channel=self.channel).exists())
148148

149149
def test_update_invitation_accept_admin_share_mode_grants_editor_access(self):
150-
# A channel invitation's "co-owner" share_mode (admin) documents
151-
# existing behavior rather than introducing a new access tier:
152150
# _accept_channel_invitation only special-cases VIEW_ACCESS, so
153-
# anything else - including "admin" - grants the same editor access
154-
# as "edit". This locks that in as intended, confirmed behavior.
151+
# "admin" currently grants the same editor access as "edit" -
152+
# documenting observed behavior, not asserting it's the intended design.
155153
invitation = models.Invitation.objects.create(
156154
share_mode=ADMIN_ACCESS, **self.invitation_db_metadata
157155
)
@@ -560,13 +558,8 @@ def test_invitation_with_channel_and_organization_is_rejected(self):
560558
pass
561559

562560
def test_create_organization_invitation_without_user_id_is_rejected(self):
563-
# Organization-scoped invitation changes get no special routing in
564-
# handle_changes - like any other change with no channel_id, they
565-
# only go through if the client tags user_id as its own id. Omitting
566-
# it isn't org-specific behaviour, it's rejected the same way any
567-
# other self-only change with a missing/mismatched user_id is, and
568-
# the actor gets that feedback back in "disallowed" rather than a
569-
# silent no-op.
561+
# No org-specific routing in handle_changes - a missing user_id is
562+
# rejected like any other self-only change, with feedback returned.
570563
invitation = self.invitation_metadata
571564
response = self.sync_changes(
572565
[
@@ -589,11 +582,8 @@ def test_create_organization_invitation_without_user_id_is_rejected(self):
589582
pass
590583

591584
def test_organization_invitation_change_with_mismatched_user_id_is_rejected(self):
592-
# A client-supplied user_id that doesn't match the actor must not be
593-
# trusted, since that would let an org admin inject a change into an
594-
# arbitrary user's sync feed. There's no org-specific routing to fall
595-
# back to, so it's simply rejected like any other self-only change
596-
# with a mismatched user_id.
585+
# A user_id that doesn't match the actor is rejected, not routed
586+
# elsewhere - it must not inject a change into another user's feed.
597587
unrelated_user = testdata.user("unrelated-target@inc.com")
598588
invitation = self.invitation_metadata
599589
response = self.sync_changes(
@@ -668,11 +658,8 @@ def test_revoke_organization_invitation_by_different_admin(self):
668658
self.assertTrue(invitation.revoked)
669659

670660
def test_admin_cannot_force_accept_on_behalf_of_invitee(self):
671-
# Org-admin edit rights on the queryset must not let an admin trigger
672-
# instance.accept() on someone else's invitation by syncing
673-
# {"accepted": true} themselves - accepted is correctly kept
674-
# read-only for them (get_fields), so this must be a no-op, not a
675-
# silent OrganizationRole grant.
661+
# Org-admin edit rights must not let an admin trigger accept() on
662+
# someone else's invitation - accepted stays read-only for them.
676663
invitation = models.Invitation.objects.create(
677664
id=uuid.uuid4().hex,
678665
organization=self.organization,
@@ -722,10 +709,8 @@ def test_delete_organization_invitation(self):
722709
pass
723710

724711
def test_accept_organization_invitation_created_via_sync(self):
725-
# Unlike the fixtures above (which set `invited` directly via the
726-
# ORM), an invitation created through the sync API - the real
727-
# creation path - never gets `invited` populated. The real invitee
728-
# must still be able to accept it.
712+
# Unlike the fixtures above, an invitation created via sync never
713+
# gets `invited` populated - the real invitee must still accept it.
729714
invitation = self.invitation_metadata
730715
response = self.sync_changes(
731716
[

contentcuration/contentcuration/viewsets/invitation.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,9 @@ def create(self, validated_data):
7474

7575
def update(self, instance, validated_data):
7676
instance = super(InvitationSerializer, self).update(instance, validated_data)
77-
# Read from validated_data, not initial_data (the raw client mods
78-
# dict) - get_fields() correctly keeps accepted/revoked read-only for
79-
# anyone but the invitee/sender, but initial_data bypasses that,
80-
# letting e.g. an org admin force instance.accept() (and thus an
81-
# active OrganizationRole) onto an invitation that isn't theirs to
82-
# accept, even though the field itself stays correctly unwritten.
83-
accepted = validated_data.get("accepted") or instance.accepted
77+
# validated_data, not initial_data, respects get_fields' read-only
78+
# flags; only trigger accept() on an actual incoming toggle.
79+
accepted = validated_data.get("accepted")
8480
revoked = validated_data.get("revoked") or instance.revoked
8581

8682
if accepted and not revoked:
@@ -105,10 +101,8 @@ def get_fields(self):
105101

106102
# allow invitation state to be modified under the right conditions
107103
if request and request.user and self.instance:
108-
# Match on email rather than the `invited` FK, since `invited` is
109-
# only ever populated by the channel email-invite flow - it's
110-
# never set for invitations created through the sync API, which
111-
# would otherwise leave the real invitee unable to accept/decline.
104+
# Match on email, not the `invited` FK - `invited` is only set by
105+
# the channel email-invite flow, never for sync-created invitations.
112106
if (request.user.email or "").lower() == (
113107
self.instance.email or ""
114108
).lower():

0 commit comments

Comments
 (0)