From 6914a7e7da1e423abccc3c5cb60e73c2b1862639 Mon Sep 17 00:00:00 2001 From: jzunigax2 <125698953+jzunigax2@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:20:46 -0600 Subject: [PATCH 1/4] feat: validate owner has enough space before transferring files --- .../workspaces/workspaces.usecase.spec.ts | 135 +++++++++++++++--- src/modules/workspaces/workspaces.usecase.ts | 34 ++++- 2 files changed, 141 insertions(+), 28 deletions(-) diff --git a/src/modules/workspaces/workspaces.usecase.spec.ts b/src/modules/workspaces/workspaces.usecase.spec.ts index 7ee7e4ef..b0184914 100644 --- a/src/modules/workspaces/workspaces.usecase.spec.ts +++ b/src/modules/workspaces/workspaces.usecase.spec.ts @@ -4241,6 +4241,9 @@ describe('WorkspacesUsecases', () => { workspaceId: workspace.id, member: workspaceOwner, memberId: workspaceOwner.uuid, + attributes: { + spaceLimit: 1099511627776, // 1TB + }, }); jest @@ -4248,8 +4251,8 @@ describe('WorkspacesUsecases', () => { .mockResolvedValueOnce(workspace); jest .spyOn(workspaceRepository, 'findWorkspaceUser') - .mockResolvedValueOnce(memberWorkspaceUser) - .mockResolvedValueOnce(ownerWorkspaceUser); + .mockResolvedValueOnce(ownerWorkspaceUser) + .mockResolvedValueOnce(memberWorkspaceUser); jest .spyOn(userRepository, 'findByUuid') .mockResolvedValueOnce(workspaceNetworkUser); @@ -4262,6 +4265,10 @@ describe('WorkspacesUsecases', () => { jest .spyOn(folderUseCases, 'getFoldersInWorkspace') .mockResolvedValueOnce([]); + jest + .spyOn(service, 'calculateFilesSizeSum') + .mockResolvedValueOnce(0) + .mockResolvedValueOnce(483183820800); // 450 GB await expect( service.transferPersonalItemsToWorkspaceOwner(workspace.id, member), @@ -4269,6 +4276,55 @@ describe('WorkspacesUsecases', () => { expect(folderUseCases.moveFolder).not.toHaveBeenCalled(); }); + it("When owner doesn't have enough free space then it shoudl throw", async () => { + const workspaceOwner = newUser(); + const workspaceNetworkUser = newUser(); + const member = newUser(); + const workspace = newWorkspace({ + owner: workspaceOwner, + }); + const folderToMove = newFolder(); + const memberWorkspaceUser = newWorkspaceUser({ + workspaceId: workspace.id, + member: member, + memberId: member.uuid, + attributes: { + rootFolderId: folderToMove.uuid, + }, + }); + const ownerWorkspaceUser = newWorkspaceUser({ + workspaceId: workspace.id, + member: workspaceOwner, + memberId: workspaceOwner.uuid, + attributes: { + spaceLimit: 1099511627776, // 1TB + }, + }); + + jest + .spyOn(workspaceRepository, 'findById') + .mockResolvedValueOnce(workspace); + jest + .spyOn(workspaceRepository, 'findWorkspaceUser') + .mockResolvedValueOnce(ownerWorkspaceUser) + .mockResolvedValueOnce(memberWorkspaceUser); + jest + .spyOn(userRepository, 'findByUuid') + .mockResolvedValueOnce(workspaceNetworkUser); + jest + .spyOn(folderUseCases, 'getByUuid') + .mockResolvedValueOnce(folderToMove); + + jest + .spyOn(service, 'calculateFilesSizeSum') + .mockResolvedValueOnce(483183820800) // 450GB + .mockResolvedValueOnce(644245094400); // 600 GB + + await expect( + service.transferPersonalItemsToWorkspaceOwner(workspace.id, member), + ).rejects.toThrow(BadRequestException); + }); + it("When user is not the owner of the workspace, then it should move the member's root folder to the workspace owner's root folder", async () => { const workspaceOwner = newUser(); const workspaceNetworkUser = newUser(); @@ -4289,6 +4345,9 @@ describe('WorkspacesUsecases', () => { workspaceId: workspace.id, member: workspaceOwner, memberId: workspaceOwner.uuid, + attributes: { + spaceLimit: 1099511627776, // 1TB + }, }); const resultingFolder = Object.assign(newFolder(), folderToMove, { parentUuid: ownerWorkspaceUser.rootFolderId, @@ -4307,8 +4366,8 @@ describe('WorkspacesUsecases', () => { .mockResolvedValueOnce(workspace); jest .spyOn(workspaceRepository, 'findWorkspaceUser') - .mockResolvedValueOnce(memberWorkspaceUser) - .mockResolvedValueOnce(ownerWorkspaceUser); + .mockResolvedValueOnce(ownerWorkspaceUser) + .mockResolvedValueOnce(memberWorkspaceUser); jest .spyOn(userRepository, 'findByUuid') .mockResolvedValueOnce(workspaceNetworkUser); @@ -4327,6 +4386,10 @@ describe('WorkspacesUsecases', () => { jest .spyOn(folderUseCases, 'renameFolder') .mockResolvedValueOnce(resultingRenamedFolder); + jest + .spyOn(service, 'calculateFilesSizeSum') + .mockResolvedValueOnce(483183820800) // 450 GB + .mockResolvedValueOnce(483183820800); // 450 GB const shortIdentifier = Buffer.from(memberWorkspaceUser.id) .toString('base64') @@ -4389,6 +4452,9 @@ describe('WorkspacesUsecases', () => { workspaceId: workspace.id, memberId: member.uuid, member, + attributes: { + spaceLimit: 1099511627776, // 1TB + }, }); jest @@ -4398,6 +4464,10 @@ describe('WorkspacesUsecases', () => { .spyOn(workspaceRepository, 'findById') .mockResolvedValue(workspace); jest.spyOn(workspaceRepository, 'deleteUserFromWorkspace'); + jest + .spyOn(service, 'calculateFilesSizeSum') + .mockResolvedValueOnce(483183820800) // 450 GB + .mockResolvedValueOnce(483183820800); // 450 GB expect( await service.removeWorkspaceMember(workspace.id, member.uuid), @@ -4438,6 +4508,9 @@ describe('WorkspacesUsecases', () => { const workspaceUser = newWorkspaceUser({ memberId: user.uuid, workspaceId: workspace.id, + attributes: { + spaceLimit: 1099511627776, // 1TB + }, }); jest .spyOn(workspaceRepository, 'findById') @@ -4446,6 +4519,10 @@ describe('WorkspacesUsecases', () => { .spyOn(workspaceRepository, 'findWorkspaceUser') .mockResolvedValue(workspaceUser); jest.spyOn(service, 'transferPersonalItemsToWorkspaceOwner'); + jest + .spyOn(service, 'calculateFilesSizeSum') + .mockResolvedValueOnce(483183820800) // 450 GB + .mockResolvedValueOnce(483183820800); // 450 GB await service.leaveWorkspace(workspace.id, user); @@ -4461,6 +4538,9 @@ describe('WorkspacesUsecases', () => { const workspaceUser = newWorkspaceUser({ memberId: user.uuid, workspaceId: workspace.id, + attributes: { + spaceLimit: 1099511627776, // 1TB + }, }); const team = newWorkspaceTeam({ workspaceId: workspace.id, @@ -4476,6 +4556,10 @@ describe('WorkspacesUsecases', () => { jest .spyOn(teamRepository, 'getTeamsWhereUserIsManagerByWorkspaceId') .mockResolvedValue([team]); + jest + .spyOn(service, 'calculateFilesSizeSum') + .mockResolvedValueOnce(483183820800) // 450 GB + .mockResolvedValueOnce(483183820800); // 450 GB await service.leaveWorkspace(workspace.id, user); @@ -4492,28 +4576,37 @@ describe('WorkspacesUsecases', () => { workspaceRepository.deleteUserFromWorkspace, ).toHaveBeenCalledWith(user.uuid, workspace.id); }); - }); - it('When user is not a manager of any teams and has no items in the workspace, then they should leave the workspace', async () => { - const user = newUser(); - const workspace = newWorkspace(); - const workspaceUser = newWorkspaceUser({ - memberId: user.uuid, - workspaceId: workspace.id, - }); + it('When user is not a manager of any teams and has no items in the workspace, then they should leave the workspace', async () => { + const user = newUser(); + const workspace = newWorkspace(); + const workspaceUser = newWorkspaceUser({ + memberId: user.uuid, + workspaceId: workspace.id, + attributes: { + spaceLimit: 1099511627776, // 1TB + }, + }); - jest.spyOn(workspaceRepository, 'findById').mockResolvedValue(workspace); - jest - .spyOn(workspaceRepository, 'findWorkspaceUser') - .mockResolvedValue(workspaceUser); + jest + .spyOn(workspaceRepository, 'findById') + .mockResolvedValue(workspace); + jest + .spyOn(workspaceRepository, 'findWorkspaceUser') + .mockResolvedValue(workspaceUser); + jest + .spyOn(service, 'calculateFilesSizeSum') + .mockResolvedValueOnce(0) // 450 GB + .mockResolvedValueOnce(483183820800); // 450 GB - await service.leaveWorkspace(workspace.id, user); + await service.leaveWorkspace(workspace.id, user); - expect(workspaceRepository.deleteUserFromWorkspace).toHaveBeenCalledWith( - user.uuid, - workspace.id, - ); + expect( + workspaceRepository.deleteUserFromWorkspace, + ).toHaveBeenCalledWith(user.uuid, workspace.id); + }); }); + describe('upload workspace Avatar', () => { const newAvatarKey = v4(); const newAvatarURL = `http://localhost:9000/${newAvatarKey}`; diff --git a/src/modules/workspaces/workspaces.usecase.ts b/src/modules/workspaces/workspaces.usecase.ts index 847a9c3c..62ee8d97 100644 --- a/src/modules/workspaces/workspaces.usecase.ts +++ b/src/modules/workspaces/workspaces.usecase.ts @@ -2182,6 +2182,33 @@ export class WorkspacesUsecases { throw new ForbiddenException('You are the owner of this workspace'); } + const memberUsage = await this.calculateFilesSizeSum( + user.uuid, + workspace.id, + [FileStatus.EXISTS], + ); + + const ownerUsage = await this.calculateFilesSizeSum( + workspace.ownerId, + workspace.id, + [FileStatus.EXISTS], + ); + + const combinedUsage = Number(memberUsage) + Number(ownerUsage); + + const ownerWorkspaceUser = await this.workspaceRepository.findWorkspaceUser( + { + workspaceId, + memberId: workspace.ownerId, + }, + ); + + if (Number(ownerWorkspaceUser.spaceLimit) < combinedUsage) { + throw new BadRequestException( + 'Owner does not have enough space to receive the files', + ); + } + const memberWorkspaceUser = await this.workspaceRepository.findWorkspaceUser({ workspaceId, @@ -2227,13 +2254,6 @@ export class WorkspacesUsecases { workspace.workspaceUserId, ); - const ownerWorkspaceUser = await this.workspaceRepository.findWorkspaceUser( - { - workspaceId, - memberId: workspace.ownerId, - }, - ); - const movedFolder = await this.folderUseCases.moveFolder( workspaceNetworkUser, memberRootFolder.uuid, From 309c18ee8a7bd6205c4a4d6c41ef7582f73d2d9b Mon Sep 17 00:00:00 2001 From: jzunigax2 <125698953+jzunigax2@users.noreply.github.com> Date: Tue, 13 Aug 2024 07:04:38 -0600 Subject: [PATCH 2/4] fix: account for owners trashed items --- src/modules/workspaces/workspaces.usecase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/workspaces/workspaces.usecase.ts b/src/modules/workspaces/workspaces.usecase.ts index 33e5cfa8..829cf754 100644 --- a/src/modules/workspaces/workspaces.usecase.ts +++ b/src/modules/workspaces/workspaces.usecase.ts @@ -2212,7 +2212,7 @@ export class WorkspacesUsecases { const ownerUsage = await this.calculateFilesSizeSum( workspace.ownerId, workspace.id, - [FileStatus.EXISTS], + [FileStatus.EXISTS, FileStatus.TRASHED], ); const combinedUsage = Number(memberUsage) + Number(ownerUsage); From 8aa1aa615451e54d78d89e77558328990b5aff5f Mon Sep 17 00:00:00 2001 From: jzunigax2 <125698953+jzunigax2@users.noreply.github.com> Date: Tue, 13 Aug 2024 07:21:35 -0600 Subject: [PATCH 3/4] chore: rerun test --- src/modules/workspaces/workspaces.usecase.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/workspaces/workspaces.usecase.ts b/src/modules/workspaces/workspaces.usecase.ts index 829cf754..885267a0 100644 --- a/src/modules/workspaces/workspaces.usecase.ts +++ b/src/modules/workspaces/workspaces.usecase.ts @@ -2251,7 +2251,6 @@ export class WorkspacesUsecases { }, { limit: 1, offset: 0 }, ); - const filesInPersonalRootFolder = await this.fileUseCases.getFilesInWorkspace( user.uuid, From 067cc817331cba6fc328776955885f0f92ab57fb Mon Sep 17 00:00:00 2001 From: jzunigax2 <125698953+jzunigax2@users.noreply.github.com> Date: Tue, 13 Aug 2024 07:50:32 -0600 Subject: [PATCH 4/4] fix: account for members trashed files --- src/modules/workspaces/workspaces.usecase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/workspaces/workspaces.usecase.ts b/src/modules/workspaces/workspaces.usecase.ts index 885267a0..a214b503 100644 --- a/src/modules/workspaces/workspaces.usecase.ts +++ b/src/modules/workspaces/workspaces.usecase.ts @@ -2206,7 +2206,7 @@ export class WorkspacesUsecases { const memberUsage = await this.calculateFilesSizeSum( user.uuid, workspace.id, - [FileStatus.EXISTS], + [FileStatus.EXISTS, FileStatus.TRASHED], ); const ownerUsage = await this.calculateFilesSizeSum(