From 96a21c47c18bf1fd45a7d5413016f04ba1a5b5ce Mon Sep 17 00:00:00 2001 From: Rickard Ankar Date: Sun, 26 Apr 2026 00:21:52 +0200 Subject: [PATCH 1/5] test: add uploadFile fallback test for S3 ServiceTest --- .../backendlab/team4you/s3/S3Controller.java | 22 ++++++-- .../backendlab/team4you/s3/S3Service.java | 54 ++++++++++++------- .../backendlab/team4you/s3/S3ServiceTest.java | 12 +++++ 3 files changed, 63 insertions(+), 25 deletions(-) diff --git a/src/main/java/backendlab/team4you/s3/S3Controller.java b/src/main/java/backendlab/team4you/s3/S3Controller.java index 73157fc4..de911c37 100644 --- a/src/main/java/backendlab/team4you/s3/S3Controller.java +++ b/src/main/java/backendlab/team4you/s3/S3Controller.java @@ -6,6 +6,8 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import backendlab.team4you.exceptions.FileStorageConfigurationException; + import java.io.IOException; import java.io.InputStream; @@ -30,20 +32,26 @@ public ResponseEntity uploadFile(@RequestParam("file") MultipartFile fil if (key == null || key.isBlank()) { return ResponseEntity.badRequest().body("Invalid filename"); } - s3Service.uploadFile(key, file.getBytes(), file.getContentType()); - return ResponseEntity.ok("File uploaded: " + key); + try { + s3Service.uploadFile(key, file.getBytes(), file.getContentType()); + return ResponseEntity.ok("File uploaded: " + key); + } catch (FileStorageConfigurationException e) { + return ResponseEntity.internalServerError().body("Kunde inte ladda upp filen: " + key); + } } // GET /api/files/download/{key} — download a file @PreAuthorize("hasRole('ADMIN')") @GetMapping("/download/{key}") - public ResponseEntity downloadFile(@PathVariable String key) throws IOException { + public ResponseEntity downloadFile(@PathVariable String key) throws IOException { try (InputStream stream = s3Service.downloadFile(key)) { byte[] bytes = stream.readAllBytes(); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + key + "\"") .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(bytes); + } catch (FileStorageConfigurationException e) { + return ResponseEntity.internalServerError().body("Kunde inte ladda ner filen: " + key); } } @@ -51,7 +59,11 @@ public ResponseEntity downloadFile(@PathVariable String key) throws IOEx @PreAuthorize("hasRole('ADMIN')") @DeleteMapping("/delete/{key}") public ResponseEntity deleteFile(@PathVariable String key) { - s3Service.deleteFile(key); - return ResponseEntity.ok("File deleted: " + key); + try { + s3Service.deleteFile(key); + return ResponseEntity.ok("File deleted: " + key); + } catch (FileStorageConfigurationException e) { + return ResponseEntity.internalServerError().body("Kunde inte radera filen: " + key); + } } } diff --git a/src/main/java/backendlab/team4you/s3/S3Service.java b/src/main/java/backendlab/team4you/s3/S3Service.java index 89bc2e74..a79db409 100644 --- a/src/main/java/backendlab/team4you/s3/S3Service.java +++ b/src/main/java/backendlab/team4you/s3/S3Service.java @@ -10,6 +10,8 @@ import software.amazon.awssdk.services.s3.model.PutObjectRequest; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import software.amazon.awssdk.services.s3.model.S3Exception; +import backendlab.team4you.exceptions.FileStorageConfigurationException; + import java.io.InputStream; @@ -29,34 +31,46 @@ public S3Service(S3Client s3Client) { // Upload a file to S3 public void uploadFile(String key, byte[] data, String contentType) { - s3Client.putObject( - PutObjectRequest.builder() - .bucket(bucketName) - .key(key) - .contentType(contentType) - .build(), - RequestBody.fromBytes(data) - ); + try { + s3Client.putObject( + PutObjectRequest.builder() + .bucket(bucketName) + .key(key) + .contentType(contentType) + .build(), + RequestBody.fromBytes(data) + ); + } catch (S3Exception e) { + throw new FileStorageConfigurationException("Kunde inte ladda upp filen: " + key, e); + } } // Download a file from S3 public InputStream downloadFile(String key) { - return s3Client.getObject( - GetObjectRequest.builder() - .bucket(bucketName) - .key(key) - .build() - ); + try { + return s3Client.getObject( + GetObjectRequest.builder() + .bucket(bucketName) + .key(key) + .build() + ); + } catch (S3Exception e) { + throw new FileStorageConfigurationException("Kunde inte ladda ner filen: " + key, e); + } } // Delete a file from S3 public void deleteFile(String key) { - s3Client.deleteObject( - DeleteObjectRequest.builder() - .bucket(bucketName) - .key(key) - .build() - ); + try { + s3Client.deleteObject( + DeleteObjectRequest.builder() + .bucket(bucketName) + .key(key) + .build() + ); + } catch (S3Exception e) { + throw new FileStorageConfigurationException("Kunde inte radera filen: " + key, e); + } } public void uploadFileIfAbsent(String key, byte[] data, String contentType) { diff --git a/src/test/java/backendlab/team4you/s3/S3ServiceTest.java b/src/test/java/backendlab/team4you/s3/S3ServiceTest.java index 1810474f..0483a187 100644 --- a/src/test/java/backendlab/team4you/s3/S3ServiceTest.java +++ b/src/test/java/backendlab/team4you/s3/S3ServiceTest.java @@ -11,6 +11,8 @@ import software.amazon.awssdk.services.s3.model.*; import org.junit.jupiter.api.BeforeEach; import org.springframework.test.util.ReflectionTestUtils; +import static org.junit.jupiter.api.Assertions.*; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -54,6 +56,7 @@ void deleteFile_shouldCallDeleteObject() { // Assert — verify that deleteObject was called once verify(s3Client, times(1)).deleteObject(any(DeleteObjectRequest.class)); } + @Test void downloadFile_shouldCallGetObject() { // Arrange @@ -67,4 +70,13 @@ void downloadFile_shouldCallGetObject() { // Assert — verify that getObject was called once verify(s3Client, times(1)).getObject(any(GetObjectRequest.class)); } + + @Test + void uploadFile_shouldThrowFileStorageConfigurationException_whenS3Fails() { + when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class))) + .thenThrow(S3Exception.builder().message("S3 error").build()); + + assertThrows(backendlab.team4you.exceptions.FileStorageConfigurationException.class, + () -> s3Service.uploadFile("test.txt", "Hello".getBytes(), "text/plain")); + } } \ No newline at end of file From 6921d6983a0dd603e167e22e330b122566e1e337 Mon Sep 17 00:00:00 2001 From: Rickard Ankar Date: Sun, 26 Apr 2026 00:46:14 +0200 Subject: [PATCH 2/5] test: add downloadFile fallback test for S3 ServiceTest --- .../java/backendlab/team4you/s3/S3ServiceTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/java/backendlab/team4you/s3/S3ServiceTest.java b/src/test/java/backendlab/team4you/s3/S3ServiceTest.java index 0483a187..fb7c99b9 100644 --- a/src/test/java/backendlab/team4you/s3/S3ServiceTest.java +++ b/src/test/java/backendlab/team4you/s3/S3ServiceTest.java @@ -73,10 +73,23 @@ void downloadFile_shouldCallGetObject() { @Test void uploadFile_shouldThrowFileStorageConfigurationException_whenS3Fails() { + // Arrange when(s3Client.putObject(any(PutObjectRequest.class), any(RequestBody.class))) .thenThrow(S3Exception.builder().message("S3 error").build()); + // Act & Assert assertThrows(backendlab.team4you.exceptions.FileStorageConfigurationException.class, () -> s3Service.uploadFile("test.txt", "Hello".getBytes(), "text/plain")); } + + @Test + void downloadFile_shouldThrowFileStorageConfigurationException_whenS3Fails() { + // Arrange + when(s3Client.getObject(any(GetObjectRequest.class))) + .thenThrow(S3Exception.builder().message("S3 error").build()); + + // Act & Assert + assertThrows(backendlab.team4you.exceptions.FileStorageConfigurationException.class, + () -> s3Service.downloadFile("test.txt")); + } } \ No newline at end of file From 77441bcb4562acad685457743a6b2e99b527ddb7 Mon Sep 17 00:00:00 2001 From: Rickard Ankar Date: Sun, 26 Apr 2026 01:06:06 +0200 Subject: [PATCH 3/5] test: add deleteFile fallback test for S3 ServiceTest --- .../java/backendlab/team4you/s3/S3ServiceTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/java/backendlab/team4you/s3/S3ServiceTest.java b/src/test/java/backendlab/team4you/s3/S3ServiceTest.java index fb7c99b9..67d72ae6 100644 --- a/src/test/java/backendlab/team4you/s3/S3ServiceTest.java +++ b/src/test/java/backendlab/team4you/s3/S3ServiceTest.java @@ -92,4 +92,15 @@ void downloadFile_shouldThrowFileStorageConfigurationException_whenS3Fails() { assertThrows(backendlab.team4you.exceptions.FileStorageConfigurationException.class, () -> s3Service.downloadFile("test.txt")); } + + @Test + void deleteFile_shouldThrowFileStorageConfigurationException_whenS3Fails() { + // Arrange + when(s3Client.deleteObject(any(DeleteObjectRequest.class))) + .thenThrow(S3Exception.builder().message("S3 error").build()); + + // Act & Assert + assertThrows(backendlab.team4you.exceptions.FileStorageConfigurationException.class, + () -> s3Service.deleteFile("test.txt")); + } } \ No newline at end of file From 858fdcc84ac5fc66547ffb8c7145919e960bcd5b Mon Sep 17 00:00:00 2001 From: Rickard Ankar Date: Sun, 26 Apr 2026 01:28:10 +0200 Subject: [PATCH 4/5] fix: add logging to S3Controller catch blocks --- .../java/backendlab/team4you/s3/S3Controller.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/backendlab/team4you/s3/S3Controller.java b/src/main/java/backendlab/team4you/s3/S3Controller.java index de911c37..77caf0b8 100644 --- a/src/main/java/backendlab/team4you/s3/S3Controller.java +++ b/src/main/java/backendlab/team4you/s3/S3Controller.java @@ -7,17 +7,18 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import backendlab.team4you.exceptions.FileStorageConfigurationException; - +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; -// Handles HTTP requests and returns data @RestController -// All endpoints start with /api/files @RequestMapping("/api/files") public class S3Controller { + private static final Logger log = LoggerFactory.getLogger(S3Controller.class); + private final S3Service s3Service; public S3Controller(S3Service s3Service) { @@ -36,6 +37,7 @@ public ResponseEntity uploadFile(@RequestParam("file") MultipartFile fil s3Service.uploadFile(key, file.getBytes(), file.getContentType()); return ResponseEntity.ok("File uploaded: " + key); } catch (FileStorageConfigurationException e) { + log.error("S3 upload failed for key={}", key, e); return ResponseEntity.internalServerError().body("Kunde inte ladda upp filen: " + key); } } @@ -51,6 +53,7 @@ public ResponseEntity downloadFile(@PathVariable String key) throws IOExcepti .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(bytes); } catch (FileStorageConfigurationException e) { + log.error("S3 download failed for key={}", key, e); return ResponseEntity.internalServerError().body("Kunde inte ladda ner filen: " + key); } } @@ -63,7 +66,8 @@ public ResponseEntity deleteFile(@PathVariable String key) { s3Service.deleteFile(key); return ResponseEntity.ok("File deleted: " + key); } catch (FileStorageConfigurationException e) { + log.error("S3 delete failed for key={}", key, e); return ResponseEntity.internalServerError().body("Kunde inte radera filen: " + key); } } -} +} \ No newline at end of file From 7d50bc6a6c0215a71da8387df89c802c04e532d9 Mon Sep 17 00:00:00 2001 From: Rickard Ankar Date: Sun, 26 Apr 2026 01:31:10 +0200 Subject: [PATCH 5/5] fix: use consistent English error messages in S3Controller --- src/main/java/backendlab/team4you/s3/S3Controller.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/backendlab/team4you/s3/S3Controller.java b/src/main/java/backendlab/team4you/s3/S3Controller.java index 77caf0b8..e7bc4ca8 100644 --- a/src/main/java/backendlab/team4you/s3/S3Controller.java +++ b/src/main/java/backendlab/team4you/s3/S3Controller.java @@ -38,7 +38,7 @@ public ResponseEntity uploadFile(@RequestParam("file") MultipartFile fil return ResponseEntity.ok("File uploaded: " + key); } catch (FileStorageConfigurationException e) { log.error("S3 upload failed for key={}", key, e); - return ResponseEntity.internalServerError().body("Kunde inte ladda upp filen: " + key); + return ResponseEntity.internalServerError().body("Could not upload file: " + key); } } @@ -54,7 +54,7 @@ public ResponseEntity downloadFile(@PathVariable String key) throws IOExcepti .body(bytes); } catch (FileStorageConfigurationException e) { log.error("S3 download failed for key={}", key, e); - return ResponseEntity.internalServerError().body("Kunde inte ladda ner filen: " + key); + return ResponseEntity.internalServerError().body("Could not download file: " + key); } } @@ -67,7 +67,7 @@ public ResponseEntity deleteFile(@PathVariable String key) { return ResponseEntity.ok("File deleted: " + key); } catch (FileStorageConfigurationException e) { log.error("S3 delete failed for key={}", key, e); - return ResponseEntity.internalServerError().body("Kunde inte radera filen: " + key); + return ResponseEntity.internalServerError().body("Could not delete file: " + key); } } } \ No newline at end of file