diff --git a/pom.xml b/pom.xml index 1c8dd9c27..d6e746855 100644 --- a/pom.xml +++ b/pom.xml @@ -1,225 +1,237 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.3.1 - - - com.hng - hng-java-boilerplate - 0.0.1-SNAPSHOT - hng-java-boilerplate - Hng java boiler plate - - - - - - - - - - - - - - - 17 - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.boot - spring-boot-devtools - runtime - true - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-actuator - - - io.micrometer - micrometer-registry-prometheus - 1.10.0 - - - org.flywaydb - flyway-core - - - org.flywaydb - flyway-database-postgresql - - - org.postgresql - postgresql - runtime - - - org.projectlombok - lombok - true - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.security - spring-security-test - test - - - com.fasterxml.jackson.core - jackson-databind - 2.16.1 - - - com.fasterxml.jackson.core - jackson-core - 2.16.1 - - - com.fasterxml.jackson.core - jackson-annotations - 2.16.1 - - - io.jsonwebtoken - jjwt-impl - 0.12.3 - runtime - - - io.jsonwebtoken - jjwt-api - 0.12.3 - - - io.jsonwebtoken - jjwt-jackson - 0.12.3 - runtime - - - org.springframework.boot - spring-boot-starter-validation - - - org.junit.jupiter - junit-jupiter-engine - 5.10.2 - test - - - org.junit.jupiter - junit-jupiter-api - 5.10.2 - test - - - org.mockito - mockito-core - 5.12.0 - test - - - org.mapstruct - mapstruct - 1.4.2.Final - - - org.mapstruct - mapstruct-processor - 1.4.2.Final - provided - - - io.rest-assured - rest-assured - test - - - jakarta.validation - jakarta.validation-api - 3.0.2 - - - org.hibernate.validator - hibernate-validator - 8.0.1.Final - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.5.0 - - - org.springframework.boot - spring-boot-starter-amqp - - - org.springframework.boot - spring-boot-starter-mail - - - com.restfb - restfb - 2024.9.0 - - - com.google.api-client - google-api-client - 2.6.0 - - - com.beust - jcommander - 1.82 - - - com.stripe - stripe-java - 26.7.0 - - - dev.samstevens.totp - totp - 1.7.1 - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - org.apache.maven.plugins - maven-compiler-plugin - 3.11.0 - - 17 - 17 - - - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.1 + + + com.hng + hng-java-boilerplate + 0.0.1-SNAPSHOT + hng-java-boilerplate + Hng java boiler plate + + + + + + + + + + + + + + + 17 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + io.micrometer + micrometer-registry-prometheus + 1.10.0 + + + org.flywaydb + flyway-core + + + org.flywaydb + flyway-database-postgresql + + + org.postgresql + postgresql + runtime + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + com.fasterxml.jackson.core + jackson-databind + 2.16.1 + + + com.fasterxml.jackson.core + jackson-core + 2.16.1 + + + com.fasterxml.jackson.core + jackson-annotations + 2.16.1 + + + io.jsonwebtoken + jjwt-impl + 0.12.3 + runtime + + + io.jsonwebtoken + jjwt-api + 0.12.3 + + + io.jsonwebtoken + jjwt-jackson + 0.12.3 + runtime + + + org.springframework.boot + spring-boot-starter-validation + + + org.junit.jupiter + junit-jupiter-engine + 5.10.2 + test + + + org.junit.jupiter + junit-jupiter-api + 5.10.2 + test + + + org.mockito + mockito-core + 5.12.0 + test + + + org.mapstruct + mapstruct + 1.4.2.Final + + + org.mapstruct + mapstruct-processor + 1.4.2.Final + provided + + + io.rest-assured + rest-assured + test + + + jakarta.validation + jakarta.validation-api + 3.0.2 + + + org.hibernate.validator + hibernate-validator + 8.0.1.Final + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.5.0 + + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.boot + spring-boot-starter-mail + + + com.restfb + restfb + 2024.9.0 + + + com.google.api-client + google-api-client + 2.6.0 + + + com.beust + jcommander + 1.82 + + + com.stripe + stripe-java + 26.7.0 + + + dev.samstevens.totp + totp + 1.7.1 + + + org.springframework.cloud + spring-cloud-starter-aws + 2.2.6.RELEASE + + + software.amazon.awssdk + s3 + 2.30.30 + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + + + + \ No newline at end of file diff --git a/profile_photos/6e6bbd89-a83d-44a8-994f-9949c48b0f59_oskar-smethurst-B1GtwanCbiw-unsplash.jpg b/profile_photos/6e6bbd89-a83d-44a8-994f-9949c48b0f59_oskar-smethurst-B1GtwanCbiw-unsplash.jpg new file mode 100644 index 000000000..f249c15cd Binary files /dev/null and b/profile_photos/6e6bbd89-a83d-44a8-994f-9949c48b0f59_oskar-smethurst-B1GtwanCbiw-unsplash.jpg differ diff --git a/profile_photos/9f947e39-fe38-44dd-85d3-927253847576_oskar-smethurst-B1GtwanCbiw-unsplash.jpg b/profile_photos/9f947e39-fe38-44dd-85d3-927253847576_oskar-smethurst-B1GtwanCbiw-unsplash.jpg new file mode 100644 index 000000000..f249c15cd Binary files /dev/null and b/profile_photos/9f947e39-fe38-44dd-85d3-927253847576_oskar-smethurst-B1GtwanCbiw-unsplash.jpg differ diff --git a/profile_photos/c3ae0d7a-fecb-4145-9992-be8b8c4f1e2a_oskar-smethurst-B1GtwanCbiw-unsplash.jpg b/profile_photos/c3ae0d7a-fecb-4145-9992-be8b8c4f1e2a_oskar-smethurst-B1GtwanCbiw-unsplash.jpg new file mode 100644 index 000000000..f249c15cd Binary files /dev/null and b/profile_photos/c3ae0d7a-fecb-4145-9992-be8b8c4f1e2a_oskar-smethurst-B1GtwanCbiw-unsplash.jpg differ diff --git a/profile_photos/c72cde4e-e8a6-46a8-a010-5b8a8d185bc9_test.jpg b/profile_photos/c72cde4e-e8a6-46a8-a010-5b8a8d185bc9_test.jpg new file mode 100644 index 000000000..016a09f16 --- /dev/null +++ b/profile_photos/c72cde4e-e8a6-46a8-a010-5b8a8d185bc9_test.jpg @@ -0,0 +1 @@ +dummy image content \ No newline at end of file diff --git a/profile_photos/d53189d2-ae88-4bee-a4ac-d71d1994bfe1_oskar-smethurst-B1GtwanCbiw-unsplash.jpg b/profile_photos/d53189d2-ae88-4bee-a4ac-d71d1994bfe1_oskar-smethurst-B1GtwanCbiw-unsplash.jpg new file mode 100644 index 000000000..f249c15cd Binary files /dev/null and b/profile_photos/d53189d2-ae88-4bee-a4ac-d71d1994bfe1_oskar-smethurst-B1GtwanCbiw-unsplash.jpg differ diff --git a/profile_photos/f28a3ff5-7685-4feb-acae-aaafa0946da2_oskar-smethurst-B1GtwanCbiw-unsplash.jpg b/profile_photos/f28a3ff5-7685-4feb-acae-aaafa0946da2_oskar-smethurst-B1GtwanCbiw-unsplash.jpg new file mode 100644 index 000000000..f249c15cd Binary files /dev/null and b/profile_photos/f28a3ff5-7685-4feb-acae-aaafa0946da2_oskar-smethurst-B1GtwanCbiw-unsplash.jpg differ diff --git a/profile_photos/ff8588cb-395e-4168-a069-8c108280fe6b_test.jpg b/profile_photos/ff8588cb-395e-4168-a069-8c108280fe6b_test.jpg new file mode 100644 index 000000000..016a09f16 --- /dev/null +++ b/profile_photos/ff8588cb-395e-4168-a069-8c108280fe6b_test.jpg @@ -0,0 +1 @@ +dummy image content \ No newline at end of file diff --git a/src/main/java/hng_java_boilerplate/config/AwsConfig.java b/src/main/java/hng_java_boilerplate/config/AwsConfig.java new file mode 100644 index 000000000..86c8ebf71 --- /dev/null +++ b/src/main/java/hng_java_boilerplate/config/AwsConfig.java @@ -0,0 +1,32 @@ +package hng_java_boilerplate.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AwsConfig { + + @Value("${aws.s3.access-key}") + private String accessKey; + + @Value("${aws.s3.secret-key}") + private String secretKey; + + @Value("${aws.s3.region}") + private String region; + + @Bean + public AmazonS3 amazonS3() { + BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey); + return AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCreds)) + .build(); + } +} + diff --git a/src/main/java/hng_java_boilerplate/config/WebSecurityConfig.java b/src/main/java/hng_java_boilerplate/config/WebSecurityConfig.java index 9af46f59e..f467e71b8 100644 --- a/src/main/java/hng_java_boilerplate/config/WebSecurityConfig.java +++ b/src/main/java/hng_java_boilerplate/config/WebSecurityConfig.java @@ -107,7 +107,8 @@ public SecurityFilterChain httpSecurity(HttpSecurity httpSecurity) throws Except "/api/v1/categories", "/api/v1/payment/plans", "/api/v1/payment/webhook", - "/api/v1/notification-settings" + "/api/v1/notification-settings", + "/api/v1/profile/upload-image" ).permitAll() .requestMatchers( diff --git a/src/main/java/hng_java_boilerplate/profile/controller/ProfileController.java b/src/main/java/hng_java_boilerplate/profile/controller/ProfileController.java index 5e5432842..c2203e6a7 100644 --- a/src/main/java/hng_java_boilerplate/profile/controller/ProfileController.java +++ b/src/main/java/hng_java_boilerplate/profile/controller/ProfileController.java @@ -10,16 +10,21 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.Optional; @RestController @RequestMapping("/api/v1/profile") @Tag(name = "User Profile Management", description = "APIs for managing user profiles") +@CrossOrigin("*") +@Slf4j public class ProfileController { private final ProfileService profileService; @@ -54,4 +59,10 @@ public ResponseEntity deactivateUser(@RequestBody @Valid public ResponseEntity getUserProfile(@PathVariable String userId) { return ResponseEntity.ok(profileService.getUserProfile(userId)); } + + @PostMapping("/upload-image") + public ResponseEntity updateProfilePicture(@RequestParam("image") MultipartFile file) throws IOException { + log.info("New Profile Image received"); + return profileService.uploadProfileImage(file); + } } diff --git a/src/main/java/hng_java_boilerplate/profile/dto/response/ProfilePictureResponse.java b/src/main/java/hng_java_boilerplate/profile/dto/response/ProfilePictureResponse.java new file mode 100644 index 000000000..33c81acd4 --- /dev/null +++ b/src/main/java/hng_java_boilerplate/profile/dto/response/ProfilePictureResponse.java @@ -0,0 +1,13 @@ +package hng_java_boilerplate.profile.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor + +public class ProfilePictureResponse { + private boolean success; + private String message; + private String imageUrl; +} diff --git a/src/main/java/hng_java_boilerplate/profile/service/ProfileService.java b/src/main/java/hng_java_boilerplate/profile/service/ProfileService.java index 8999966cc..1bdae19da 100644 --- a/src/main/java/hng_java_boilerplate/profile/service/ProfileService.java +++ b/src/main/java/hng_java_boilerplate/profile/service/ProfileService.java @@ -4,11 +4,16 @@ import hng_java_boilerplate.profile.dto.request.UpdateUserProfileDto; import hng_java_boilerplate.profile.dto.response.DeactivateUserResponse; import hng_java_boilerplate.profile.dto.response.ProfileResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.Optional; public interface ProfileService { public DeactivateUserResponse deactivateUser(DeactivateUserRequest request); Optional updateUserProfile(String userId, UpdateUserProfileDto updateUserProfileDto); ProfileResponse getUserProfile(String userId); + ResponseEntity uploadProfileImage(MultipartFile file) throws IOException; } diff --git a/src/main/java/hng_java_boilerplate/profile/serviceImpl/ProfileServiceImpl.java b/src/main/java/hng_java_boilerplate/profile/serviceImpl/ProfileServiceImpl.java index 6a79b858e..036f89a21 100644 --- a/src/main/java/hng_java_boilerplate/profile/serviceImpl/ProfileServiceImpl.java +++ b/src/main/java/hng_java_boilerplate/profile/serviceImpl/ProfileServiceImpl.java @@ -1,13 +1,13 @@ package hng_java_boilerplate.profile.serviceImpl; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; import hng_java_boilerplate.exception.BadRequestException; import hng_java_boilerplate.exception.NotFoundException; import hng_java_boilerplate.profile.dto.request.DeactivateUserRequest; import hng_java_boilerplate.profile.dto.request.UpdateUserProfileDto; -import hng_java_boilerplate.profile.dto.response.DeactivateUserResponse; -import hng_java_boilerplate.profile.dto.response.ProfileDto; -import hng_java_boilerplate.profile.dto.response.ProfileResponse; -import hng_java_boilerplate.profile.dto.response.ProfileUpdateResponseDto; +import hng_java_boilerplate.profile.dto.response.*; import hng_java_boilerplate.profile.entity.Profile; import hng_java_boilerplate.profile.repository.ProfileRepository; import hng_java_boilerplate.profile.service.ProfileService; @@ -15,10 +15,21 @@ import hng_java_boilerplate.user.repository.UserRepository; import hng_java_boilerplate.user.service.UserService; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; - +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Optional; +import java.util.UUID; @Service @RequiredArgsConstructor @@ -27,6 +38,24 @@ public class ProfileServiceImpl implements ProfileService { private final UserRepository userRepository; private final ProfileRepository profileRepository; + private final AmazonS3 amazonS3; + + @Value("${aws.s3.bucket-name}") + private String bucketName; + + public void uploadFileToS3(MultipartFile file, String keyName) { + try (InputStream inputStream = file.getInputStream()) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); + metadata.setContentType(file.getContentType()); + + amazonS3.putObject(new PutObjectRequest(bucketName, keyName, inputStream, metadata)); + } catch (IOException e) { + throw new RuntimeException("Error uploading file to S3", e); + } + } + + @Override public DeactivateUserResponse deactivateUser(DeactivateUserRequest request) { User authUser = userService.getLoggedInUser(); @@ -37,7 +66,8 @@ public DeactivateUserResponse deactivateUser(DeactivateUserRequest request) { throw new BadRequestException("User has been deactivated"); } - if (!confirmation.equals("true")) throw new BadRequestException("Confirmation needs to be true for deactivation"); + if (!confirmation.equals("true")) + throw new BadRequestException("Confirmation needs to be true for deactivation"); authUser.setIsDeactivated(true); userRepository.save(authUser); @@ -50,30 +80,30 @@ public DeactivateUserResponse deactivateUser(DeactivateUserRequest request) { @Override public Optional updateUserProfile(String id, UpdateUserProfileDto updateUserProfileDto) { - Optional user = userRepository.findById(id); - if (user.isPresent()) { - Profile profile = user.get().getProfile(); - - profile.setFirstName(updateUserProfileDto.getFirstName()); - profile.setLastName(updateUserProfileDto.getLastName()); - profile.setJobTitle(updateUserProfileDto.getJobTitle()); - profile.setPronouns(updateUserProfileDto.getPronouns()); - profile.setJobTitle(updateUserProfileDto.getJobTitle()); - profile.setDepartment(updateUserProfileDto.getDepartment()); - profile.setSocial(updateUserProfileDto.getSocial()); - profile.setBio(updateUserProfileDto.getBio()); - profile.setPhone(updateUserProfileDto.getPhoneNumber()); - profile.setAvatarUrl(updateUserProfileDto.getAvatarUrl()); - - profile = profileRepository.save(profile); - return Optional.of(ProfileUpdateResponseDto.builder() - .statusCode(HttpStatus.OK.value()) - .message("Profile updated successfully") - .data(profile) - .build() - ); - } - throw new NotFoundException("User not found"); + Optional user = userRepository.findById(id); + if (user.isPresent()) { + Profile profile = user.get().getProfile(); + + profile.setFirstName(updateUserProfileDto.getFirstName()); + profile.setLastName(updateUserProfileDto.getLastName()); + profile.setJobTitle(updateUserProfileDto.getJobTitle()); + profile.setPronouns(updateUserProfileDto.getPronouns()); + profile.setJobTitle(updateUserProfileDto.getJobTitle()); + profile.setDepartment(updateUserProfileDto.getDepartment()); + profile.setSocial(updateUserProfileDto.getSocial()); + profile.setBio(updateUserProfileDto.getBio()); + profile.setPhone(updateUserProfileDto.getPhoneNumber()); + profile.setAvatarUrl(updateUserProfileDto.getAvatarUrl()); + + profile = profileRepository.save(profile); + return Optional.of(ProfileUpdateResponseDto.builder() + .statusCode(HttpStatus.OK.value()) + .message("Profile updated successfully") + .data(profile) + .build() + ); + } + throw new NotFoundException("User not found"); } @Override @@ -97,4 +127,28 @@ public ProfileResponse getUserProfile(String userId) { return new ProfileResponse(200, "user profile", profileDto); } -} \ No newline at end of file + + @Override + public ResponseEntity uploadProfileImage(MultipartFile file) { + if (file.isEmpty() || !isValidImage(file)) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(new ProfilePictureResponse(false, "Invalid file type or missing image. Only JPG or JPEG formats are allowed.", null)); + } + try { + String filename = UUID.randomUUID() + "_" + file.getOriginalFilename(); + uploadFileToS3(file, filename); + + String fileUrl = amazonS3.getUrl(bucketName, filename).toString(); + return ResponseEntity.ok(new ProfilePictureResponse(true, "Profile image uploaded successfully", fileUrl)); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ProfilePictureResponse(false, "An error occurred while uploading your profile image. Please try again later.", null)); + } + } + + + private boolean isValidImage(MultipartFile file) { + String contentType = file.getContentType(); + return contentType != null && (contentType.equals("image/jpeg") || contentType.equals("image/jpg")); + } +} diff --git a/src/main/java/hng_java_boilerplate/user/controller/AuthController.java b/src/main/java/hng_java_boilerplate/user/controller/AuthController.java index 964006423..0519b6ea7 100644 --- a/src/main/java/hng_java_boilerplate/user/controller/AuthController.java +++ b/src/main/java/hng_java_boilerplate/user/controller/AuthController.java @@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.*; @RestController +@CrossOrigin("*") @RequestMapping("/api/v1/auth") @RequiredArgsConstructor @Tag(name="Authentication") diff --git a/src/main/resources/application-example.properties b/src/main/resources/application-example.properties index 97212a210..225ab3ed3 100644 --- a/src/main/resources/application-example.properties +++ b/src/main/resources/application-example.properties @@ -76,3 +76,11 @@ flutterwave.secret.key= stripe.api.key= client.url= stripe.secret.key= + +# AWS S3 Configuration +aws.s3.bucket-name= +aws.s3.region= +aws.s3.access-key= +aws.s3.secret-key= + + diff --git a/src/test/java/hng_java_boilerplate/profile/controller/ProfileControllerTest.java b/src/test/java/hng_java_boilerplate/profile/controller/ProfileControllerTest.java index ca891d31f..688fb70b6 100644 --- a/src/test/java/hng_java_boilerplate/profile/controller/ProfileControllerTest.java +++ b/src/test/java/hng_java_boilerplate/profile/controller/ProfileControllerTest.java @@ -13,7 +13,10 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.multipart.MultipartFile; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @@ -62,4 +65,6 @@ void shouldDeactivateUser() throws Exception { .andExpect(jsonPath("$.status_code").value(200)) .andExpect(jsonPath("$.message").value(response.message())); } + + } \ No newline at end of file diff --git a/src/test/java/hng_java_boilerplate/profile/serviceImpl/ProfileServiceImplTest.java b/src/test/java/hng_java_boilerplate/profile/serviceImpl/ProfileServiceImplTest.java index cbebdaf9c..79af2ef71 100644 --- a/src/test/java/hng_java_boilerplate/profile/serviceImpl/ProfileServiceImplTest.java +++ b/src/test/java/hng_java_boilerplate/profile/serviceImpl/ProfileServiceImplTest.java @@ -14,6 +14,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.multipart.MultipartFile; import java.util.Optional; @@ -136,4 +137,6 @@ void shouldGetUserProfile() { verify(userRepository).findById(anyString()); } + + } \ No newline at end of file diff --git a/src/test/java/hng_java_boilerplate/profile/serviceImpl/UserProfileUnitTest.java b/src/test/java/hng_java_boilerplate/profile/serviceImpl/UserProfileUnitTest.java index fe358b2d5..a357ce0f6 100644 --- a/src/test/java/hng_java_boilerplate/profile/serviceImpl/UserProfileUnitTest.java +++ b/src/test/java/hng_java_boilerplate/profile/serviceImpl/UserProfileUnitTest.java @@ -2,6 +2,7 @@ import hng_java_boilerplate.exception.NotFoundException; import hng_java_boilerplate.profile.dto.request.UpdateUserProfileDto; +import hng_java_boilerplate.profile.dto.response.ProfilePictureResponse; import hng_java_boilerplate.profile.dto.response.ProfileUpdateResponseDto; import hng_java_boilerplate.profile.entity.Profile; import hng_java_boilerplate.profile.repository.ProfileRepository; @@ -15,6 +16,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockMultipartFile; import java.util.Optional; @@ -80,4 +83,33 @@ public void test_that_updateUserProfile_returns_error_with_status_400_when_user_ .hasMessage("User not found"); } + @Test + public void test_uploadProfileImage_returns_successful_response() { + MockMultipartFile file = new MockMultipartFile( + "file", "test.jpg", "image/jpeg", "dummy image content".getBytes() + ); + + ResponseEntity response = underTest.uploadProfileImage(file); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().isSuccess()).isTrue(); + assertThat(response.getBody().getMessage()).isEqualTo("Profile image uploaded successfully"); + assertThat(response.getBody().getImageUrl()).isNotBlank(); + } + + @Test + public void test_uploadProfileImage_returns_error_for_invalid_file() { + MockMultipartFile file = new MockMultipartFile( + "file", "test.txt", "text/plain", "invalid file content".getBytes() + ); + + ResponseEntity response = underTest.uploadProfileImage(file); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().isSuccess()).isFalse(); + assertThat(response.getBody().getMessage()).isEqualTo("Invalid file type or missing image. Only JPG or JPEG formats are allowed."); + } + }