From d1f78cc55659772904af229261e75be2ba0628ef Mon Sep 17 00:00:00 2001 From: jhonatapers Date: Sat, 31 May 2025 13:19:11 -0300 Subject: [PATCH] fix: file creation validation and error handling in DefaultCreateFileUseCase --- .../file/create/DefaultCreateFileUseCase.java | 18 +- .../create/DefaultCreateFileUseCaseTest.java | 213 +++++++++++++++++- 2 files changed, 218 insertions(+), 13 deletions(-) diff --git a/application/src/main/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCase.java b/application/src/main/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCase.java index 9b4ac009..bfa504aa 100644 --- a/application/src/main/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCase.java +++ b/application/src/main/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCase.java @@ -59,14 +59,24 @@ public CreateFileOutput execute(final CreateFileInput input) { if (actualUsedQuota + input.size() > owner.getQuota().sizeInBytes()) throw QuotaExceededException.with("Quota exceeded", - new Error("You have exceeded your actual quota of " + owner.getQuota().sizeInBytes() + " bytes")); + Error.with("You have exceeded your actual quota of " + owner.getQuota().sizeInBytes() + " bytes")); final FolderID folderId = folderGateway .findById(FolderID.of(input.folderId())) .map(Folder::getId) .orElseThrow(() -> NotFoundException.with(Folder.class, input.folderId().toString())); + final Notification notification = Notification.create(); + final FileName fileName = FileName.of(input.name()); + fileName.validate(notification); + if (notification.hasError()) + throw ValidationException.with("Could not create Aggregate File", notification); + + final List filesOnSameFolder = fileGateway.findByFolder(folderId); + if (filesOnSameFolder.stream().map(File::getName).anyMatch(fileName::equals)) + throw ValidationException.with("Could not create Aggregate File", + Error.with("File with same name already exists on this folder")); final String randomContentName = UUID.randomUUID().toString(); final String contentLocation = storeContentFile(randomContentName, input.content()); @@ -75,13 +85,7 @@ public CreateFileOutput execute(final CreateFileInput input) { final Content content = Content.of(contentLocation, contentType, contentSize); - final Notification notification = Notification.create(); final File file = notification.valdiate(() -> File.create(ownerId, folderId, fileName, content)); - - List filesOnSameFolder = fileGateway.findByFolder(folderId); - if (filesOnSameFolder.stream().map(File::getName).anyMatch(fileName::equals)) - notification.append(Error.with("File with same name already exists on this folder")); - if (notification.hasError()) throw ValidationException.with("Could not create Aggregate File", notification); diff --git a/application/src/test/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCaseTest.java b/application/src/test/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCaseTest.java index 95b9f380..52eab0f6 100644 --- a/application/src/test/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCaseTest.java +++ b/application/src/test/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCaseTest.java @@ -25,6 +25,7 @@ import com.callv2.drive.domain.exception.InternalErrorException; import com.callv2.drive.domain.exception.NotFoundException; +import com.callv2.drive.domain.exception.QuotaExceededException; import com.callv2.drive.domain.exception.ValidationException; import com.callv2.drive.domain.file.Content; import com.callv2.drive.domain.file.File; @@ -32,6 +33,7 @@ import com.callv2.drive.domain.file.FileName; import com.callv2.drive.domain.folder.Folder; import com.callv2.drive.domain.folder.FolderGateway; +import com.callv2.drive.domain.folder.FolderID; import com.callv2.drive.domain.member.Member; import com.callv2.drive.domain.member.MemberGateway; import com.callv2.drive.domain.member.MemberID; @@ -127,7 +129,7 @@ void givenAValidParams_whenCallsExecute_thenShouldCreateFile() { } @Test - void givenAnInvalidId_whenCallsExecute_thenShouldThrowNotFoundException() { + void givenAnInvalidFolderId_whenCallsExecute_thenShouldThrowNotFoundException() { final var owner = Member.create(MemberID.of("owner")) .requestQuota(Quota.of(1, QuotaUnit.GIGABYTE)) @@ -177,6 +179,48 @@ void givenAnInvalidId_whenCallsExecute_thenShouldThrowNotFoundException() { } + @Test + void givenAnInvalidMemberId_whenCallsExecute_thenShouldThrowNotFoundException() { + + final var expectedOwnerId = MemberID.of("inexistent"); + final var expectedFolderId = FolderID.unique(); + + final var expectedFileName = FileName.of("file"); + final var expectedContentType = "image/jpeg"; + final var contentBytes = "content".getBytes(); + + final var expectedContent = new ByteArrayInputStream(contentBytes); + final var expectedContentSize = (long) contentBytes.length; + + final var expectedExceptionMessage = "Member with id '%s' not found" + .formatted(expectedOwnerId.getValue()); + + when(memberGateway.findById(any())) + .thenReturn(Optional.empty()); + + final var input = CreateFileInput.of( + expectedOwnerId.getValue(), + expectedFolderId.getValue(), + expectedFileName.value(), + expectedContentType, + expectedContent, + expectedContentSize); + + final var actualException = assertThrows(NotFoundException.class, () -> useCase.execute(input)); + + assertEquals(expectedExceptionMessage, actualException.getMessage()); + + verify(memberGateway, times(1)).findById(any()); + verify(memberGateway, times(1)).findById(eq(expectedOwnerId)); + verify(folderGateway, times(0)).findById(any()); + verify(storageService, times(0)).store(any(), any()); + verify(storageService, times(0)).store(any(), eq(expectedContent)); + verify(storageService, times(0)).delete(any()); + verify(fileGateway, times(0)).findByFolder(any()); + verify(fileGateway, times(0)).create(any()); + + } + @Test void givenAValidParamsWithAlreadyExistingFileNameOnSameFolder_whenCallsExecute_thenShouldThrowValidationException() { @@ -212,9 +256,6 @@ void givenAValidParamsWithAlreadyExistingFileNameOnSameFolder_whenCallsExecute_t when(folderGateway.findById(any())) .thenReturn(Optional.of(folder)); - when(storageService.store(any(), any())) - .then(returnsFirstArg()); - final var input = CreateFileInput.of( ownerId.getValue(), expectedFolderId.getValue(), @@ -230,8 +271,7 @@ void givenAValidParamsWithAlreadyExistingFileNameOnSameFolder_whenCallsExecute_t verify(folderGateway, times(1)).findById(any()); verify(folderGateway, times(1)).findById(eq(expectedFolderId)); - verify(storageService, times(1)).store(any(), any()); - verify(storageService, times(1)).store(any(), eq(expectedContent)); + verify(storageService, times(0)).store(any(), any()); verify(storageService, times(0)).delete(any()); verify(fileGateway, times(1)).findByFolder(any()); verify(fileGateway, times(1)).findByFolder(eq(folder.getId())); @@ -438,8 +478,169 @@ void givenAValidParams_whenCallsExecuteAndContentGatewayStoreThrowsRandomExcepti verify(folderGateway, times(1)).findById(eq(expectedFolderId)); verify(storageService, times(1)).store(any(), any()); verify(storageService, times(1)).store(any(), eq(expectedContent)); + verify(fileGateway, times(1)).findByFolder(any()); + verify(fileGateway, times(1)).findByFolder(eq(expectedFolderId)); verify(storageService, times(0)).delete(any()); + verify(fileGateway, times(0)).create(any()); + + } + + @Test + void givenAnInvalidFileName_whenCallsExecute_thenShouldThrowValidationException() { + + final var owner = Member.create(MemberID.of("owner")) + .requestQuota(Quota.of(1, QuotaUnit.GIGABYTE)) + .approveQuotaRequest(); + + final var ownerId = owner.getId(); + + final var folder = Folder.createRoot(ownerId); + final var expectedFolderId = folder.getId(); + + final var expectedFileName = FileName.of("NUL"); + final var expectedContentType = "image/jpeg"; + final var contentBytes = "content".getBytes(); + + final var expectedContent = new ByteArrayInputStream(contentBytes); + final var expectedContentSize = (long) contentBytes.length; + + final var expectedExceptionMessage = "Could not create Aggregate File"; + final var expectedErrorCount = 1; + final var expectedErrorMessage = "'name' cannot be a reserved name: NUL"; + + when(memberGateway.findById(ownerId)) + .thenReturn(Optional.of(owner)); + + when(folderGateway.findById(expectedFolderId)) + .thenReturn(Optional.of(folder)); + + final var input = CreateFileInput.of( + ownerId.getValue(), + expectedFolderId.getValue(), + expectedFileName.value(), + expectedContentType, + expectedContent, + expectedContentSize); + + final var actualException = assertThrows(ValidationException.class, () -> useCase.execute(input)); + + assertEquals(expectedExceptionMessage, actualException.getMessage()); + assertEquals(expectedErrorCount, actualException.getErrors().size()); + assertEquals(expectedErrorMessage, actualException.getErrors().get(0).message()); + + verify(folderGateway, times(1)).findById(any()); + verify(folderGateway, times(1)).findById(eq(expectedFolderId)); + verify(storageService, times(0)).store(any(), any()); verify(fileGateway, times(0)).findByFolder(any()); + verify(storageService, times(0)).delete(any()); + verify(fileGateway, times(0)).create(any()); + + } + + @Test + void givenAValidParams_whenCallsExecuteAndMemberQuotaIsExceeded_thenShouldThrowsQuotaExceededException() { + + final var owner = Member.create(MemberID.of("owner")) + .requestQuota(Quota.of(1, QuotaUnit.BYTE)) + .approveQuotaRequest(); + + final var ownerId = owner.getId(); + + final var folder = Folder.createRoot(ownerId); + final var expectedFolderId = folder.getId(); + + final var expectedFileName = FileName.of("NUL"); + final var expectedContentType = "image/jpeg"; + final var contentBytes = "content".getBytes(); + + final var expectedContent = new ByteArrayInputStream(contentBytes); + final var expectedContentSize = (long) contentBytes.length; + + final var expectedExceptionMessage = "Quota exceeded"; + final var expectedErrorCount = 1; + final var expectedErrorMessage = "You have exceeded your actual quota of 1 bytes"; + + when(memberGateway.findById(ownerId)) + .thenReturn(Optional.of(owner)); + + final var input = CreateFileInput.of( + ownerId.getValue(), + expectedFolderId.getValue(), + expectedFileName.value(), + expectedContentType, + expectedContent, + expectedContentSize); + + final var actualException = assertThrows(QuotaExceededException.class, () -> useCase.execute(input)); + + assertEquals(expectedExceptionMessage, actualException.getMessage()); + assertEquals(expectedErrorCount, actualException.getErrors().size()); + assertEquals(expectedErrorMessage, actualException.getErrors().get(0).message()); + + verify(folderGateway, times(0)).findById(any()); + verify(storageService, times(0)).store(any(), any()); + verify(fileGateway, times(0)).findByFolder(any()); + verify(storageService, times(0)).delete(any()); + verify(fileGateway, times(0)).create(any()); + + } + + @Test + void givenAnInvalidParamsWithContentTypeNull_whenCallsExecute_thenShouldThrowsValidationException() { + + final var owner = Member.create(MemberID.of("owner")) + .requestQuota(Quota.of(1, QuotaUnit.GIGABYTE)) + .approveQuotaRequest(); + + final var ownerId = owner.getId(); + + final var folder = Folder.createRoot(ownerId); + final var expectedFolderId = folder.getId(); + + final var expectedFileName = FileName.of("file"); + final String expectedContentType = null; + final var contentBytes = "content".getBytes(); + + final var expectedContent = new ByteArrayInputStream(contentBytes); + final var expectedContentSize = (long) contentBytes.length; + + final var expectedExceptionMessage = "Could not create Aggregate File"; + final var expectedErrorCount = 1; + final var expectedErrorMessage = "'type' cannot be null."; + + when(memberGateway.findById(any())) + .thenReturn(Optional.of(owner)); + + when(fileGateway.findByFolder(any())) + .thenReturn(List.of()); + + when(folderGateway.findById(any())) + .thenReturn(Optional.of(folder)); + + when(storageService.store(any(), any())) + .then(returnsFirstArg()); + + final var input = CreateFileInput.of( + ownerId.getValue(), + expectedFolderId.getValue(), + expectedFileName.value(), + expectedContentType, + expectedContent, + expectedContentSize); + + final var actualException = assertThrows(ValidationException.class, () -> useCase.execute(input)); + + assertEquals(expectedExceptionMessage, actualException.getMessage()); + assertEquals(expectedErrorCount, actualException.getErrors().size()); + assertEquals(expectedErrorMessage, actualException.getErrors().get(0).message()); + + verify(folderGateway, times(1)).findById(any()); + verify(folderGateway, times(1)).findById(eq(expectedFolderId)); + verify(storageService, times(1)).store(any(), any()); + verify(storageService, times(1)).store(any(), eq(expectedContent)); + verify(storageService, times(0)).delete(any()); + verify(fileGateway, times(1)).findByFolder(any()); + verify(fileGateway, times(1)).findByFolder(eq(folder.getId())); verify(fileGateway, times(0)).create(any()); }