From dce6bce9fa49e2c6dc4d7d3a7e93b494d4cc9540 Mon Sep 17 00:00:00 2001 From: Kristina <7614497+khjrtbrg@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:19:20 +0200 Subject: [PATCH] [web] update notification preferences on ownership transfer (#33854) GitOrigin-RevId: c20c7f992d9dbdd6e9f80feafc7b53587c122dd7 --- .../OwnershipTransferHandler.mjs | 16 +++++++++++++ .../OwnershipTransferHandler.test.mjs | 24 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.mjs b/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.mjs index 99d77cc254..920ebe0fbb 100644 --- a/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.mjs +++ b/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.mjs @@ -14,6 +14,7 @@ import TagsHandler from '../Tags/TagsHandler.mjs' import { promiseMapWithLimit } from '@overleaf/promise-utils' import LimitationsManager from '../Subscription/LimitationsManager.mjs' import AsyncLocalStorage from '../../infrastructure/AsyncLocalStorage.mjs' +import Modules from '../../infrastructure/Modules.mjs' export default { promises: { @@ -142,6 +143,21 @@ async function transferOwnership(projectId, newOwnerId, options = {}) { pendingPrivilegeLevel ) + // Update notification preferences for both new and previous owner + try { + await Modules.promises.hooks.fire( + 'projectOwnershipTransferred', + projectId, + previousOwnerId, + newOwnerId + ) + } catch (err) { + logger.error( + { err, projectId, previousOwnerId, newOwnerId }, + 'failed to update notification preferences on ownership transfer' + ) + } + // Flush project to TPDS await TpdsProjectFlusher.promises.flushProjectToTpds(projectId) diff --git a/services/web/test/unit/src/Collaborators/OwnershipTransferHandler.test.mjs b/services/web/test/unit/src/Collaborators/OwnershipTransferHandler.test.mjs index 2ec6831ee3..3407345075 100644 --- a/services/web/test/unit/src/Collaborators/OwnershipTransferHandler.test.mjs +++ b/services/web/test/unit/src/Collaborators/OwnershipTransferHandler.test.mjs @@ -85,6 +85,13 @@ describe('OwnershipTransferHandler', function () { canAddXEditCollaborators: sinon.stub().resolves(true), }, } + ctx.Modules = { + promises: { + hooks: { + fire: sinon.stub().resolves(), + }, + }, + } vi.mock('../../../../app/src/Features/Errors/Errors.js', () => vi.importActual('../../../../app/src/Features/Errors/Errors.js') @@ -148,6 +155,10 @@ describe('OwnershipTransferHandler', function () { }) ) + vi.doMock('../../../../app/src/infrastructure/Modules.mjs', () => ({ + default: ctx.Modules, + })) + ctx.handler = (await import(MODULE_PATH)).default }) @@ -510,6 +521,19 @@ describe('OwnershipTransferHandler', function () { ) }) + it('should fire the ownershipTransferred hook', async function (ctx) { + await ctx.handler.promises.transferOwnership( + ctx.project._id, + ctx.collaborator._id + ) + expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith( + 'projectOwnershipTransferred', + ctx.project._id, + ctx.user._id, + ctx.collaborator._id + ) + }) + it('should decline to transfer ownership to a non-collaborator', async function (ctx) { ctx.project.collaberator_refs = [] ctx.project.readOnly_refs = []