Skip to content

Commit c048b2a

Browse files
authored
🐛(backend) manage invitation partial update without email
An invitation can be updated to change its role. The front use a PATCH sending only the changed role, so the email is missing in the InivtationSerializer.validate method. We have to check first if an email is present before working on it.
1 parent 5908afb commit c048b2a

File tree

4 files changed

+45
-1
lines changed

4 files changed

+45
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ and this project adheres to
1515
### Fixed
1616

1717
- ⚡️(backend) improve trashbin endpoint performance
18+
- 🐛(backend) manage invitation partial update without email #1494
19+
1820

1921
## [3.8.0] - 2025-10-14
2022

src/backend/core/api/serializers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,8 @@ def validate(self, attrs):
749749
if self.instance is None:
750750
attrs["issuer"] = user
751751

752-
attrs["email"] = attrs["email"].lower()
752+
if attrs.get("email"):
753+
attrs["email"] = attrs["email"].lower()
753754

754755
return attrs
755756

src/backend/core/tests/documents/test_api_document_invitations.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,37 @@ def test_api_document_invitations_update_authenticated_unprivileged(
769769
assert value == old_invitation_values[key]
770770

771771

772+
@pytest.mark.parametrize("via", VIA)
773+
@pytest.mark.parametrize("role", ["administrator", "owner"])
774+
def test_api_document_invitations_patch(via, role, mock_user_teams):
775+
"""Partially updating an invitation should be allowed."""
776+
777+
user = factories.UserFactory()
778+
invitation = factories.InvitationFactory(role="editor")
779+
780+
if via == USER:
781+
factories.UserDocumentAccessFactory(
782+
document=invitation.document, user=user, role=role
783+
)
784+
elif via == TEAM:
785+
mock_user_teams.return_value = ["lasuite", "unknown"]
786+
factories.TeamDocumentAccessFactory(
787+
document=invitation.document, team="lasuite", role=role
788+
)
789+
790+
client = APIClient()
791+
client.force_login(user)
792+
793+
response = client.patch(
794+
f"/api/v1.0/documents/{invitation.document.id!s}/invitations/{invitation.id!s}/",
795+
{"role": "reader"},
796+
format="json",
797+
)
798+
assert response.status_code == 200
799+
invitation.refresh_from_db()
800+
assert invitation.role == "reader"
801+
802+
772803
# Delete
773804

774805

src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,19 @@ test.describe('Document create member', () => {
202202
);
203203
await expect(userInvitation).toBeVisible();
204204

205+
const responsePromisePatchInvitation = page.waitForResponse(
206+
(response) =>
207+
response.url().includes('/invitations/') &&
208+
response.status() === 200 &&
209+
response.request().method() === 'PATCH',
210+
);
211+
205212
await userInvitation.getByLabel('doc-role-dropdown').click();
206213
await page.getByRole('menuitem', { name: 'Reader' }).click();
207214

215+
const responsePatchInvitation = await responsePromisePatchInvitation;
216+
expect(responsePatchInvitation.ok()).toBeTruthy();
217+
208218
const moreActions = userInvitation.getByRole('button', {
209219
name: 'Open invitation actions menu',
210220
});

0 commit comments

Comments
 (0)