Skip to content

Commit e007c48

Browse files
authored
feat(recover-account): add reset workspace functions (#520)
* feat(recover-account): add reset workspace, remove user from all invited workspaces functions to workspace usecases * chore(test): add tests for workspace repository
1 parent ec7c051 commit e007c48

6 files changed

Lines changed: 395 additions & 0 deletions

File tree

src/modules/folder/folder.repository.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,27 @@ export class SequelizeFolderRepository implements FolderRepository {
691691
},
692692
);
693693
}
694+
async deleteByUserAndUuids(
695+
user: User,
696+
folderUuids: Folder['uuid'][],
697+
): Promise<void> {
698+
await this.folderModel.update(
699+
{
700+
removed: true,
701+
removedAt: new Date(),
702+
deleted: true,
703+
deletedAt: new Date(),
704+
},
705+
{
706+
where: {
707+
userId: user.id,
708+
uuid: {
709+
[Op.in]: folderUuids,
710+
},
711+
},
712+
},
713+
);
714+
}
694715

695716
async findAllCursorWhereUpdatedAfter(
696717
where: Partial<Folder>,

src/modules/folder/folder.usecase.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,11 @@ export class FolderUseCases {
464464
deletedAt: new Date(),
465465
});
466466
}
467+
468+
async deleteByUuids(user: User, uuids: Folder['uuid'][]): Promise<void> {
469+
await this.folderRepository.deleteByUserAndUuids(user, uuids);
470+
}
471+
467472
async moveFoldersToTrash(
468473
user: User,
469474
folderIds: FolderAttributes['id'][],

src/modules/workspaces/repositories/workspaces.repository.spec.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,4 +665,43 @@ describe('SequelizeWorkspaceRepository', () => {
665665
);
666666
});
667667
});
668+
669+
describe('findWorkspaceUsersByUserUuid', () => {
670+
it('When workspaceUsers are found for respective user, then it should return instances of workspaceUser', async () => {
671+
const mockUser = newUser();
672+
const mockWorkspaceUser = newWorkspaceUser({ memberId: mockUser.uuid });
673+
const mockWorkspaceUserModelResponse = {
674+
...mockWorkspaceUser,
675+
toJSON: jest.fn().mockReturnValue({
676+
...mockWorkspaceUser,
677+
}),
678+
};
679+
680+
jest
681+
.spyOn(workspaceUserModel, 'findAll')
682+
.mockResolvedValueOnce([mockWorkspaceUserModelResponse] as any);
683+
684+
const result = await repository.findWorkspaceUsersByUserUuid(
685+
mockUser.uuid,
686+
);
687+
688+
expect(result).toBeInstanceOf(Array);
689+
expect(result[0]).toBeInstanceOf(WorkspaceUser);
690+
expect(result[0].id).toBe(mockWorkspaceUser.id);
691+
expect(result[0].memberId).toBe(mockWorkspaceUser.memberId);
692+
});
693+
694+
it('When workspaceUsers are not found for respective user, then it should return empty array', async () => {
695+
const mockUser = newUser();
696+
697+
jest.spyOn(workspaceUserModel, 'findAll').mockResolvedValueOnce([]);
698+
699+
const result = await repository.findWorkspaceUsersByUserUuid(
700+
mockUser.uuid,
701+
);
702+
703+
expect(result).toBeInstanceOf(Array);
704+
expect(result.length).toEqual(0);
705+
});
706+
});
668707
});

src/modules/workspaces/repositories/workspaces.repository.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,40 @@ export class SequelizeWorkspaceRepository {
361361
);
362362
}
363363

364+
async findWorkspaceUsersByUserUuid(
365+
userUuid: string,
366+
): Promise<WorkspaceUser[]> {
367+
const workspaceUsers = await this.modelWorkspaceUser.findAll({
368+
where: { memberId: userUuid },
369+
});
370+
return workspaceUsers.map((user) => this.workspaceUserToDomain(user));
371+
}
372+
373+
async deleteUsersFromWorkspace(
374+
workspaceId: WorkspaceUser['id'],
375+
memberIds: WorkspaceUser['memberId'][],
376+
): Promise<void> {
377+
await this.modelWorkspaceUser.destroy({
378+
where: { memberId: { [Op.in]: memberIds }, workspaceId },
379+
});
380+
}
381+
382+
async deleteAllInvitationsByWorkspace(
383+
workspaceId: WorkspaceAttributes['id'],
384+
): Promise<void> {
385+
await this.modelWorkspaceInvite.destroy({
386+
where: { workspaceId },
387+
});
388+
}
389+
390+
async deleteAllInvitationByUser(
391+
userUUid: WorkspaceInviteAttributes['invitedUser'],
392+
): Promise<void> {
393+
await this.modelWorkspaceInvite.destroy<WorkspaceInviteModel>({
394+
where: { invitedUser: userUUid },
395+
});
396+
}
397+
364398
async findUserAvailableWorkspaces(userUuid: string) {
365399
const userWorkspaces = await this.modelWorkspaceUser.findAll({
366400
where: { memberId: userUuid },

src/modules/workspaces/workspaces.usecase.spec.ts

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6364,4 +6364,231 @@ describe('WorkspacesUsecases', () => {
63646364
);
63656365
});
63666366
});
6367+
6368+
describe('removeUserFromNonOwnedWorkspaces', () => {
6369+
const user = newUser();
6370+
6371+
it('When user owns no workspaces, then it should remove user from all workspaces', async () => {
6372+
jest.spyOn(workspaceRepository, 'findByOwner').mockResolvedValueOnce([]);
6373+
6374+
const memberships = [newWorkspaceUser(), newWorkspaceUser()];
6375+
jest
6376+
.spyOn(workspaceRepository, 'findWorkspaceUsersByUserUuid')
6377+
.mockResolvedValueOnce(memberships);
6378+
6379+
const leaveWorkspaceSpy = jest
6380+
.spyOn(service, 'leaveWorkspace')
6381+
.mockResolvedValue(undefined);
6382+
6383+
await service.removeUserFromNonOwnedWorkspaces(user);
6384+
6385+
expect(leaveWorkspaceSpy).toHaveBeenCalledTimes(2);
6386+
expect(leaveWorkspaceSpy).toHaveBeenCalledWith(
6387+
memberships[0].workspaceId,
6388+
user,
6389+
);
6390+
expect(leaveWorkspaceSpy).toHaveBeenCalledWith(
6391+
memberships[1].workspaceId,
6392+
user,
6393+
);
6394+
});
6395+
6396+
it('When user owns some workspaces, then it should only remove user from non-owned workspaces', async () => {
6397+
const ownedWorkspaces = [newWorkspace(), newWorkspace()];
6398+
const nonOwnedWorkspacesIds = [v4(), v4()];
6399+
6400+
jest
6401+
.spyOn(workspaceRepository, 'findByOwner')
6402+
.mockResolvedValueOnce(ownedWorkspaces);
6403+
6404+
const memberships = [
6405+
newWorkspaceUser({ workspaceId: ownedWorkspaces[0].id }),
6406+
newWorkspaceUser({ workspaceId: ownedWorkspaces[1].id }),
6407+
newWorkspaceUser({ workspaceId: nonOwnedWorkspacesIds[0] }),
6408+
newWorkspaceUser({ workspaceId: nonOwnedWorkspacesIds[1] }),
6409+
];
6410+
jest
6411+
.spyOn(workspaceRepository, 'findWorkspaceUsersByUserUuid')
6412+
.mockResolvedValueOnce(memberships);
6413+
6414+
const leaveWorkspaceSpy = jest
6415+
.spyOn(service, 'leaveWorkspace')
6416+
.mockResolvedValue(undefined);
6417+
6418+
await service.removeUserFromNonOwnedWorkspaces(user);
6419+
6420+
expect(leaveWorkspaceSpy).toHaveBeenCalledTimes(2);
6421+
expect(leaveWorkspaceSpy).toHaveBeenCalledWith(
6422+
nonOwnedWorkspacesIds[0],
6423+
user,
6424+
);
6425+
expect(leaveWorkspaceSpy).toHaveBeenCalledWith(
6426+
nonOwnedWorkspacesIds[1],
6427+
user,
6428+
);
6429+
});
6430+
6431+
it('When user has no workspace memberships, then it should not call leaveWorkspace', async () => {
6432+
jest.spyOn(workspaceRepository, 'findByOwner').mockResolvedValueOnce([]);
6433+
6434+
jest
6435+
.spyOn(workspaceRepository, 'findWorkspaceUsersByUserUuid')
6436+
.mockResolvedValueOnce([]);
6437+
const leaveWorkspaceSpy = jest.spyOn(service, 'leaveWorkspace');
6438+
6439+
await service.removeUserFromNonOwnedWorkspaces(user);
6440+
6441+
expect(leaveWorkspaceSpy).not.toHaveBeenCalled();
6442+
});
6443+
});
6444+
6445+
describe('resetWorkspace', () => {
6446+
it('When called, then it should reset the workspace by removing all non-owner members and reassigning space', async () => {
6447+
const workspaceNetworkUserId = v4();
6448+
const workspaceOwnerId = v4();
6449+
const workspaceNetworkUser = newUser({
6450+
attributes: { uuid: workspaceNetworkUserId },
6451+
});
6452+
const workspace = newWorkspace({
6453+
attributes: {
6454+
workspaceUserId: workspaceNetworkUserId,
6455+
ownerId: workspaceOwnerId,
6456+
},
6457+
});
6458+
jest
6459+
.spyOn(userRepository, 'findByUuid')
6460+
.mockResolvedValueOnce(workspaceNetworkUser);
6461+
6462+
const ownerMember = newWorkspaceUser({
6463+
workspaceId: workspace.id,
6464+
memberId: workspaceOwnerId,
6465+
});
6466+
const nonOwnerMembers = Array.from({ length: 2 }, () =>
6467+
newWorkspaceUser({
6468+
workspaceId: workspace.id,
6469+
}),
6470+
);
6471+
6472+
jest
6473+
.spyOn(workspaceRepository, 'findWorkspaceUsers')
6474+
.mockResolvedValueOnce([ownerMember, ...nonOwnerMembers]);
6475+
6476+
const deleteFoldersSpy = jest
6477+
.spyOn(folderUseCases, 'deleteByUuids')
6478+
.mockResolvedValueOnce(undefined);
6479+
6480+
const deleteUsersSpy = jest
6481+
.spyOn(workspaceRepository, 'deleteUsersFromWorkspace')
6482+
.mockResolvedValueOnce(undefined);
6483+
6484+
const totalSpace = 5000000;
6485+
jest
6486+
.spyOn(service, 'getWorkspaceNetworkLimit')
6487+
.mockResolvedValueOnce(totalSpace);
6488+
6489+
const updateUserSpy = jest
6490+
.spyOn(workspaceRepository, 'updateWorkspaceUserBy')
6491+
.mockResolvedValueOnce(undefined);
6492+
6493+
await service.resetWorkspace(workspace);
6494+
6495+
expect(deleteFoldersSpy).toHaveBeenCalledWith(
6496+
workspaceNetworkUser,
6497+
expect.arrayContaining([
6498+
nonOwnerMembers[1].rootFolderId,
6499+
nonOwnerMembers[0].rootFolderId,
6500+
]),
6501+
);
6502+
6503+
expect(deleteUsersSpy).toHaveBeenCalledWith(
6504+
workspace.id,
6505+
expect.arrayContaining([
6506+
nonOwnerMembers[1].memberId,
6507+
nonOwnerMembers[0].memberId,
6508+
]),
6509+
);
6510+
6511+
expect(updateUserSpy).toHaveBeenCalledWith(
6512+
{ workspaceId: workspace.id, memberId: workspace.ownerId },
6513+
{ spaceLimit: totalSpace },
6514+
);
6515+
});
6516+
6517+
it('When workspace has only the owner as member, it should still update space allocation', async () => {
6518+
const workspaceNetworkUserId = v4();
6519+
const workspaceOwnerId = v4();
6520+
const workspaceNetworkUser = newUser({
6521+
attributes: { uuid: workspaceNetworkUserId },
6522+
});
6523+
const workspace = newWorkspace({
6524+
attributes: {
6525+
workspaceUserId: workspaceNetworkUserId,
6526+
ownerId: workspaceOwnerId,
6527+
},
6528+
});
6529+
jest
6530+
.spyOn(userRepository, 'findByUuid')
6531+
.mockResolvedValueOnce(workspaceNetworkUser);
6532+
6533+
const ownerMember = newWorkspaceUser({
6534+
workspaceId: workspace.id,
6535+
memberId: workspaceOwnerId,
6536+
});
6537+
6538+
jest
6539+
.spyOn(workspaceRepository, 'findWorkspaceUsers')
6540+
.mockResolvedValueOnce([ownerMember]);
6541+
6542+
const deleteFoldersSpy = jest.spyOn(folderUseCases, 'deleteByUuids');
6543+
6544+
const deleteUsersSpy = jest.spyOn(
6545+
workspaceRepository,
6546+
'deleteUsersFromWorkspace',
6547+
);
6548+
6549+
const totalSpace = 5000000;
6550+
jest
6551+
.spyOn(service, 'getWorkspaceNetworkLimit')
6552+
.mockResolvedValueOnce(totalSpace);
6553+
6554+
const updateUserSpy = jest.spyOn(
6555+
workspaceRepository,
6556+
'updateWorkspaceUserBy',
6557+
);
6558+
6559+
await service.resetWorkspace(workspace);
6560+
6561+
expect(deleteFoldersSpy).toHaveBeenCalledWith(workspaceNetworkUser, []);
6562+
expect(deleteUsersSpy).toHaveBeenCalledWith(workspace.id, []);
6563+
6564+
expect(updateUserSpy).toHaveBeenCalledWith(
6565+
{ workspaceId: workspace.id, memberId: workspace.ownerId },
6566+
{ spaceLimit: totalSpace },
6567+
);
6568+
});
6569+
});
6570+
6571+
describe('emptyAllUserOwnedWorkspaces', () => {
6572+
it('When user owns multiple workspaces, it should reset all of them', async () => {
6573+
const user = newUser();
6574+
const ownedWorkspaces = Array.from({ length: 3 }, () =>
6575+
newWorkspace({ owner: user }),
6576+
);
6577+
6578+
jest
6579+
.spyOn(workspaceRepository, 'findByOwner')
6580+
.mockResolvedValueOnce(ownedWorkspaces);
6581+
6582+
const resetWorkspaceSpy = jest
6583+
.spyOn(service, 'resetWorkspace')
6584+
.mockResolvedValue(undefined);
6585+
6586+
await service.emptyAllUserOwnedWorkspaces(user);
6587+
6588+
expect(resetWorkspaceSpy).toHaveBeenCalledTimes(3);
6589+
expect(resetWorkspaceSpy).toHaveBeenCalledWith(ownedWorkspaces[0]);
6590+
expect(resetWorkspaceSpy).toHaveBeenCalledWith(ownedWorkspaces[1]);
6591+
expect(resetWorkspaceSpy).toHaveBeenCalledWith(ownedWorkspaces[2]);
6592+
});
6593+
});
63676594
});

0 commit comments

Comments
 (0)