From 866c8c046d1c9493548cdd2d904a93d7fd908fb1 Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Tue, 23 Dec 2025 16:16:14 +0530 Subject: [PATCH] fix: create host records when team membership transitions to accepted via PATCH (#26138) The PATCH endpoint for /v2/teams/:teamId/memberships/:membershipId had a bug where host records for assignAllTeamMembers event types were not created when a membership transitioned from accepted=false to accepted=true. The bug was caused by incorrect operation ordering: 1. Update membership (first update) 2. Get current membership (gets already-updated state) 3. Update membership again (second update) 4. Check if transition happened (condition never fires) Fixed by reordering to match the org-scoped controller pattern: 1. Get current membership BEFORE any updates 2. Update membership 3. Compare and call updateNewTeamMemberEventTypes if transition occurred Also added an e2e test to verify host records are created when membership transitions from accepted=false to accepted=true via PATCH. Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../teams-memberships.controller.e2e-spec.ts | 51 +++++++++++++++++++ .../teams-memberships.controller.ts | 6 +-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/apps/api/v2/src/modules/teams/memberships/controllers/teams-memberships.controller.e2e-spec.ts b/apps/api/v2/src/modules/teams/memberships/controllers/teams-memberships.controller.e2e-spec.ts index 847f5b93e9..631a8d343e 100644 --- a/apps/api/v2/src/modules/teams/memberships/controllers/teams-memberships.controller.e2e-spec.ts +++ b/apps/api/v2/src/modules/teams/memberships/controllers/teams-memberships.controller.e2e-spec.ts @@ -307,6 +307,57 @@ describe("Teams Memberships Endpoints", () => { }); }); + it("should assign team wide events when membership transitions from accepted=false to accepted=true via PATCH", async () => { + const pendingUserEmail = `pending-user-${randomString()}@api.com`; + const pendingUser = await userRepositoryFixture.create({ + email: pendingUserEmail, + username: pendingUserEmail, + bio, + metadata, + }); + + const createPendingMembershipBody: CreateTeamMembershipInput = { + userId: pendingUser.id, + accepted: false, + role: "MEMBER", + }; + + const createResponse = await request(app.getHttpServer()) + .post(`/v2/teams/${team.id}/memberships`) + .send(createPendingMembershipBody) + .expect(201); + + const pendingMembership: TeamMembershipOutput = createResponse.body.data; + expect(pendingMembership.accepted).toEqual(false); + + const teamEventTypesBeforePatch = await eventTypesRepositoryFixture.getAllTeamEventTypes(team.id); + const collectiveEventBeforePatch = teamEventTypesBeforePatch?.find( + (eventType) => eventType.slug === teamEventType.slug + ); + const userHostBeforePatch = collectiveEventBeforePatch?.hosts.find((host) => host.userId === pendingUser.id); + expect(userHostBeforePatch).toBeFalsy(); + + const updateToAcceptedBody: UpdateTeamMembershipInput = { + accepted: true, + }; + + const patchResponse = await request(app.getHttpServer()) + .patch(`/v2/teams/${team.id}/memberships/${pendingMembership.id}`) + .send(updateToAcceptedBody) + .expect(200); + + const acceptedMembership: TeamMembershipOutput = patchResponse.body.data; + expect(acceptedMembership.accepted).toEqual(true); + + await userHasCorrectEventTypes(pendingUser.id); + + await request(app.getHttpServer()) + .delete(`/v2/teams/${team.id}/memberships/${pendingMembership.id}`) + .expect(200); + + await userRepositoryFixture.deleteByEmail(pendingUserEmail); + }); + it("should delete the membership of the org's team we created via api", async () => { return request(app.getHttpServer()) .delete(`/v2/teams/${team.id}/memberships/${membershipCreatedViaApi.id}`) diff --git a/apps/api/v2/src/modules/teams/memberships/controllers/teams-memberships.controller.ts b/apps/api/v2/src/modules/teams/memberships/controllers/teams-memberships.controller.ts index 56b60adb66..ff21ee24ee 100644 --- a/apps/api/v2/src/modules/teams/memberships/controllers/teams-memberships.controller.ts +++ b/apps/api/v2/src/modules/teams/memberships/controllers/teams-memberships.controller.ts @@ -118,8 +118,6 @@ export class TeamsMembershipsController { @Param("membershipId", ParseIntPipe) membershipId: number, @Body() body: UpdateTeamMembershipInput ): Promise { - const membership = await this.teamsMembershipsService.updateTeamMembership(teamId, membershipId, body); - const currentMembership = await this.teamsMembershipsService.getTeamMembership(teamId, membershipId); const updatedMembership = await this.teamsMembershipsService.updateTeamMembership( @@ -127,6 +125,7 @@ export class TeamsMembershipsController { membershipId, body ); + if (!currentMembership.accepted && updatedMembership.accepted) { try { await updateNewTeamMemberEventTypes(updatedMembership.userId, teamId); @@ -134,9 +133,10 @@ export class TeamsMembershipsController { this.logger.error("Could not update new team member eventTypes", err); } } + return { status: SUCCESS_STATUS, - data: plainToClass(TeamMembershipOutput, membership, { strategy: "excludeAll" }), + data: plainToClass(TeamMembershipOutput, updatedMembership, { strategy: "excludeAll" }), }; }