From c9775508dbeef164c8848d745be693893f60a45f Mon Sep 17 00:00:00 2001 From: Ebba Andersson Date: Thu, 23 Apr 2026 17:04:14 +0200 Subject: [PATCH 1/4] test: implement integration tests for S3 FileService --- .../file/FileServiceIntegrationTest.java | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java diff --git a/src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java b/src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java new file mode 100644 index 0000000..63a6cac --- /dev/null +++ b/src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java @@ -0,0 +1,156 @@ +package org.example.visacasemanagementsystem.file; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.PutObjectResponse; +import software.amazon.awssdk.services.s3.presigner.S3Presigner; +import static org.assertj.core.api.Assertions.assertThat; +import software.amazon.awssdk.services.s3.model.*; +import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; +import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.*; + +@SpringBootTest +@ActiveProfiles("test") +class FileServiceIntegrationTest { + + @Autowired + private FileService fileService; + + @MockitoBean + private S3Client s3Client; + @MockitoBean + private S3Presigner s3Presigner; + + + @Test + void uploadFile_ShouldReturnSKey_WhenUploadIsSuccessful() throws IOException { + // Arrange + MockMultipartFile mockFile = new MockMultipartFile( + "file", + "test-document.pdf", + "application/pdf", + "fake pdf content".getBytes() + ); + + when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class))) + .thenReturn(PutObjectResponse.builder().build()); + + // Act + String resultKey = fileService.uploadFile(mockFile); + + // Assert + assertThat(resultKey).contains("test-document.pdf"); + assertThat(resultKey).contains("_"); + assertThat(resultKey).endsWith("test-document.pdf"); + + verify(s3Client).putObject( + argThat((PutObjectRequest req) -> req.bucket().equals("test-bucket")), + any(RequestBody.class) + ); + } + + @Test + void uploadFile_shouldThrowException_WhenFileIsToLarge() { + // Arrange + byte[] largeContent = new byte[10 * 1024 * 1024 + 1]; + MockMultipartFile largeFile = new MockMultipartFile( + "file", "large.pdf", "application/pdf", largeContent + ); + + // Act & Assert + assertThatThrownBy(() -> fileService.uploadFile(largeFile)) + .isInstanceOf(IOException.class) + .hasMessageContaining("File exceeds maximum allowed size of 10 MB"); + + } + + @Test + void uploadFile_ShouldSanitizeFileName() throws IOException { + // Arrange + MockMultipartFile mockFile = new MockMultipartFile( + "file", + "my picture!.png", + "image/png", + "content".getBytes() + ); + + when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class))) + .thenReturn(PutObjectResponse.builder().build()); + + String resultKey = fileService.uploadFile(mockFile); + + assertThat(resultKey).contains("my_picture_.png"); + assertThat(resultKey).doesNotContain("!"); + assertThat(resultKey).doesNotContain(" "); + + } + + @Test + void uploadFile_ShouldThrowException_WhenFileTypeIsInvalid() { + // Arrange + MockMultipartFile mockFile = new MockMultipartFile( + "file", + "virus.exe", + "application/x-msdownload", + "bad content".getBytes() + ); + + // Act & Assert + assertThatThrownBy(() -> fileService.uploadFile(mockFile)) + .isInstanceOf(IOException.class) + .hasMessageContaining("Unsupported document type: " + mockFile.getContentType()); + + + } + + @Test + void getPresignedDownloadUrl_ShouldReturnFormattedUrl() throws MalformedURLException { + // Arrange + String fileName = "test-file.pdf"; + String expectedUrl = "http://localhost:9000/test-bucket/" + fileName; + + PresignedGetObjectRequest mockPresignedRequest = mock(PresignedGetObjectRequest.class); + + when(mockPresignedRequest.url()).thenReturn(URI.create(expectedUrl).toURL()); + + when(s3Presigner.presignGetObject(any(GetObjectPresignRequest.class))) + .thenReturn(mockPresignedRequest); + + // Act + String url = fileService.getPresignedDownloadUrl(fileName); + + // Assert + assertThat(url).isEqualTo(expectedUrl); + } + + @Test + void deleteFile_ShouldCallS3DeleteObject() { + // Arrange + String s3Key = "some-uuid_test.pdf"; + + // Act + fileService.deleteFile(s3Key); + + // Assert + verify(s3Client).deleteObject(argThat((DeleteObjectRequest req) -> + req.bucket().equals("test-bucket") && req.key().equals(s3Key))); + + } + +} From ca9ef36717fd3a0c3b08ff6e9dbccaa995bd166e Mon Sep 17 00:00:00 2001 From: Ebba Andersson Date: Thu, 23 Apr 2026 17:13:56 +0200 Subject: [PATCH 2/4] fix: correct misspelling in test --- .../file/FileServiceIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java b/src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java index 63a6cac..6608f06 100644 --- a/src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java @@ -66,7 +66,7 @@ void uploadFile_ShouldReturnSKey_WhenUploadIsSuccessful() throws IOException { } @Test - void uploadFile_shouldThrowException_WhenFileIsToLarge() { + void uploadFile_shouldThrowException_WhenFileIsTooLarge() { // Arrange byte[] largeContent = new byte[10 * 1024 * 1024 + 1]; MockMultipartFile largeFile = new MockMultipartFile( From a217b614a2bd51a724fc0fffc2899298cb6b763e Mon Sep 17 00:00:00 2001 From: Ebba Andersson Date: Thu, 23 Apr 2026 17:16:17 +0200 Subject: [PATCH 3/4] fix: small spelling correction --- .../file/FileServiceIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java b/src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java index 6608f06..2a112a7 100644 --- a/src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java @@ -39,7 +39,7 @@ class FileServiceIntegrationTest { @Test - void uploadFile_ShouldReturnSKey_WhenUploadIsSuccessful() throws IOException { + void uploadFile_ShouldReturnS3Key_WhenUploadIsSuccessful() throws IOException { // Arrange MockMultipartFile mockFile = new MockMultipartFile( "file", From 8b80e9956540ba4245210044ee98fcb62d3b6744 Mon Sep 17 00:00:00 2001 From: Ebba Andersson Date: Thu, 23 Apr 2026 21:06:47 +0200 Subject: [PATCH 4/4] fix: nitpick fixes from CodeRabbit --- .../file/FileServiceIntegrationTest.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java b/src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java index 2a112a7..75963f7 100644 --- a/src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java +++ b/src/test/java/org/example/visacasemanagementsystem/file/FileServiceIntegrationTest.java @@ -60,7 +60,10 @@ void uploadFile_ShouldReturnS3Key_WhenUploadIsSuccessful() throws IOException { assertThat(resultKey).endsWith("test-document.pdf"); verify(s3Client).putObject( - argThat((PutObjectRequest req) -> req.bucket().equals("test-bucket")), + argThat((PutObjectRequest req) -> + "test-bucket".equals(req.bucket()) + && resultKey.equals(req.key()) + && "application/pdf".equals(req.contentType())), any(RequestBody.class) ); } @@ -78,6 +81,9 @@ void uploadFile_shouldThrowException_WhenFileIsTooLarge() { .isInstanceOf(IOException.class) .hasMessageContaining("File exceeds maximum allowed size of 10 MB"); + verify(s3Client, never()).putObject( + any(PutObjectRequest.class), any(RequestBody.class)); + } @Test @@ -115,6 +121,8 @@ void uploadFile_ShouldThrowException_WhenFileTypeIsInvalid() { assertThatThrownBy(() -> fileService.uploadFile(mockFile)) .isInstanceOf(IOException.class) .hasMessageContaining("Unsupported document type: " + mockFile.getContentType()); + verify(s3Client, never()).putObject( + any(PutObjectRequest.class), any(RequestBody.class)); } @@ -129,8 +137,13 @@ void getPresignedDownloadUrl_ShouldReturnFormattedUrl() throws MalformedURLExcep when(mockPresignedRequest.url()).thenReturn(URI.create(expectedUrl).toURL()); - when(s3Presigner.presignGetObject(any(GetObjectPresignRequest.class))) - .thenReturn(mockPresignedRequest); + when(s3Presigner.presignGetObject(argThat((GetObjectPresignRequest req) -> + req.signatureDuration().equals(java.time.Duration.ofMinutes(10)) + && "test-bucket".equals(req.getObjectRequest().bucket()) + && fileName.equals(req.getObjectRequest().key()) + && ("attachment; filename=\"" + fileName + "\"") + .equals(req.getObjectRequest().responseContentDisposition())))) + .thenReturn(mockPresignedRequest); // Act String url = fileService.getPresignedDownloadUrl(fileName);