diff --git a/build.gradle.kts b/build.gradle.kts index 2aa81e4b..e7a3f595 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,8 +33,11 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-validation") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-data-redis") + implementation("mysql:mysql-connector-java:8.0.33") compileOnly("org.projectlombok:lombok") + testCompileOnly("org.projectlombok:lombok") + testAnnotationProcessor("org.projectlombok:lombok") runtimeOnly("com.h2database:h2") annotationProcessor("org.projectlombok:lombok") testImplementation("org.springframework.boot:spring-boot-starter-test") diff --git a/src/main/java/com/example/log4u/Log4UApplication.java b/src/main/java/com/example/log4u/Log4UApplication.java index 57fa72f5..d54d143b 100644 --- a/src/main/java/com/example/log4u/Log4UApplication.java +++ b/src/main/java/com/example/log4u/Log4UApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@EnableJpaAuditing @SpringBootApplication public class Log4UApplication { diff --git a/src/main/java/com/example/log4u/common/entity/BaseEntity.java b/src/main/java/com/example/log4u/common/entity/BaseEntity.java new file mode 100644 index 00000000..6f5adb62 --- /dev/null +++ b/src/main/java/com/example/log4u/common/entity/BaseEntity.java @@ -0,0 +1,28 @@ +package com.example.log4u.common.entity; + +import java.time.LocalDateTime; + +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +abstract public class BaseEntity { + + @CreatedDate + @Column(nullable = false, updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(nullable = false) + private LocalDateTime updatedAt; + + private String deleteYn = "N"; +} diff --git a/src/main/java/com/example/log4u/domain/comment/testController/TestController.java b/src/main/java/com/example/log4u/domain/comment/testController/TestController.java index 268a29cd..f8cecdf9 100644 --- a/src/main/java/com/example/log4u/domain/comment/testController/TestController.java +++ b/src/main/java/com/example/log4u/domain/comment/testController/TestController.java @@ -30,5 +30,4 @@ public String testIllegalArgument() { public String testLog4uException() { throw new NotFoundCommentException(); // 또는 임의의 ServiceException } - } diff --git a/src/main/java/com/example/log4u/domain/diary/entity/Diary.java b/src/main/java/com/example/log4u/domain/diary/entity/Diary.java new file mode 100644 index 00000000..9f558d7f --- /dev/null +++ b/src/main/java/com/example/log4u/domain/diary/entity/Diary.java @@ -0,0 +1,58 @@ +package com.example.log4u.domain.diary.entity; + +import com.example.log4u.common.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class Diary extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long diaryId; + + //JPA 연관관계 사용 X + // 외래키 방식을 사용 O + @Column(nullable = false) + private Long userId; + + @Column(nullable = false) + private String title; + + private String thumbnailUrl; + + @Column(nullable = false) + private String content; + + @Column(nullable = false) + private Double latitude; + + @Column(nullable = false) + private Double longitude; + + @Column(nullable = false) + private Long likeCount; + + public Long incrementLikeCount() { + this.likeCount++; + return this.likeCount; + } + + public Long decreaseLikeCount() { + this.likeCount--; + return this.likeCount; + } +} diff --git a/src/main/java/com/example/log4u/domain/diary/exception/DiaryErrorCode.java b/src/main/java/com/example/log4u/domain/diary/exception/DiaryErrorCode.java new file mode 100644 index 00000000..def651be --- /dev/null +++ b/src/main/java/com/example/log4u/domain/diary/exception/DiaryErrorCode.java @@ -0,0 +1,29 @@ +package com.example.log4u.domain.diary.exception; + +import org.springframework.http.HttpStatus; + +import com.example.log4u.common.exception.base.ErrorCode; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum DiaryErrorCode implements ErrorCode { + + NOT_FOUND_DIARY(HttpStatus.NOT_FOUND, "다이어리를 찾을 수 없습니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getErrorMessage() { + return message; + } +} + diff --git a/src/main/java/com/example/log4u/domain/diary/exception/DiaryException.java b/src/main/java/com/example/log4u/domain/diary/exception/DiaryException.java new file mode 100644 index 00000000..b8f325f8 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/diary/exception/DiaryException.java @@ -0,0 +1,11 @@ +package com.example.log4u.domain.diary.exception; + +import com.example.log4u.common.exception.base.ErrorCode; +import com.example.log4u.common.exception.base.ServiceException; + +public class DiaryException extends ServiceException { + public DiaryException(ErrorCode errorCode) { + super(errorCode); + } +} + diff --git a/src/main/java/com/example/log4u/domain/diary/exception/NotFoundDiaryException.java b/src/main/java/com/example/log4u/domain/diary/exception/NotFoundDiaryException.java new file mode 100644 index 00000000..452f1141 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/diary/exception/NotFoundDiaryException.java @@ -0,0 +1,7 @@ +package com.example.log4u.domain.diary.exception; + +public class NotFoundDiaryException extends DiaryException { + public NotFoundDiaryException() { + super(DiaryErrorCode.NOT_FOUND_DIARY); + } +} diff --git a/src/main/java/com/example/log4u/domain/diary/repository/DiaryRepository.java b/src/main/java/com/example/log4u/domain/diary/repository/DiaryRepository.java new file mode 100644 index 00000000..fbd9d14b --- /dev/null +++ b/src/main/java/com/example/log4u/domain/diary/repository/DiaryRepository.java @@ -0,0 +1,9 @@ +package com.example.log4u.domain.diary.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.log4u.domain.diary.entity.Diary; + +public interface DiaryRepository extends JpaRepository +{ +} diff --git a/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java b/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java new file mode 100644 index 00000000..48b95d34 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java @@ -0,0 +1,37 @@ +package com.example.log4u.domain.diary.service; + +import org.springframework.stereotype.Service; + +import com.example.log4u.domain.diary.entity.Diary; +import com.example.log4u.domain.diary.exception.NotFoundDiaryException; +import com.example.log4u.domain.diary.repository.DiaryRepository; + +import lombok.RequiredArgsConstructor; + + +@Service +@RequiredArgsConstructor +public class DiaryService { + + private final DiaryRepository diaryRepository; + + public Diary getDiary(Long diaryId) { + return diaryRepository.findById(diaryId) + .orElseThrow(NotFoundDiaryException::new); + } + + public Long incrementLikeCount(Long diaryId) { + Diary diary = getDiary(diaryId); + return diary.incrementLikeCount(); + } + + public Long decreaseLikeCount(Long diaryId) { + Diary diary = getDiary(diaryId); + return diary.decreaseLikeCount(); + } + + public Long getLikeCount(Long diaryId) { + Diary diary = getDiary(diaryId); + return diary.getLikeCount(); + } +} diff --git a/src/main/java/com/example/log4u/domain/like/controller/LikeController.java b/src/main/java/com/example/log4u/domain/like/controller/LikeController.java new file mode 100644 index 00000000..e0922c73 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/controller/LikeController.java @@ -0,0 +1,43 @@ +package com.example.log4u.domain.like.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.log4u.domain.like.dto.request.LikeAddRequestDto; +import com.example.log4u.domain.like.dto.response.LikeAddResponseDto; +import com.example.log4u.domain.like.dto.response.LikeCancelResponseDto; +import com.example.log4u.domain.like.service.LikeService; + +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@Tag(name = "좋아요 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/likes") +public class LikeController { + + private final LikeService likeService; + + @PostMapping + public ResponseEntity addLike(@Valid @RequestBody LikeAddRequestDto requestDto) { + Long userId = 1L; // 실제 구현에서는 토큰에서 추출 + + LikeAddResponseDto response = likeService.addLike(userId, requestDto); + return ResponseEntity.ok(response); + } + + @DeleteMapping("/{diaryId}") + public ResponseEntity cancelLike(@PathVariable Long diaryId){ + Long userId = 1L;// 실제 구현에서는 토큰에서 추출 + + LikeCancelResponseDto response = likeService.cancelLike(userId, diaryId); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/example/log4u/domain/like/dto/request/LikeAddRequestDto.java b/src/main/java/com/example/log4u/domain/like/dto/request/LikeAddRequestDto.java new file mode 100644 index 00000000..e38ef6f6 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/dto/request/LikeAddRequestDto.java @@ -0,0 +1,16 @@ +package com.example.log4u.domain.like.dto.request; + +import com.example.log4u.domain.like.entity.Like; + +public record LikeAddRequestDto( + Long diaryId + +) { + + public Like toEntity(Long userId) { + return Like.builder() + .userId(userId) + .diaryId(diaryId) + .build(); + } +} diff --git a/src/main/java/com/example/log4u/domain/like/dto/response/LikeAddResponseDto.java b/src/main/java/com/example/log4u/domain/like/dto/response/LikeAddResponseDto.java new file mode 100644 index 00000000..6d040813 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/dto/response/LikeAddResponseDto.java @@ -0,0 +1,10 @@ +package com.example.log4u.domain.like.dto.response; + +public record LikeAddResponseDto( + boolean liked, + Long likeCount) { + + public static LikeAddResponseDto of(boolean liked, Long likeCount) { + return new LikeAddResponseDto(liked, likeCount); + } +} diff --git a/src/main/java/com/example/log4u/domain/like/dto/response/LikeCancelResponseDto.java b/src/main/java/com/example/log4u/domain/like/dto/response/LikeCancelResponseDto.java new file mode 100644 index 00000000..5c778678 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/dto/response/LikeCancelResponseDto.java @@ -0,0 +1,10 @@ +package com.example.log4u.domain.like.dto.response; + +public record LikeCancelResponseDto( + boolean liked, + Long likeCount) { + + public static LikeCancelResponseDto of(boolean liked, Long likeCount) { + return new LikeCancelResponseDto(liked, likeCount); + } +} diff --git a/src/main/java/com/example/log4u/domain/like/entity/Like.java b/src/main/java/com/example/log4u/domain/like/entity/Like.java new file mode 100644 index 00000000..6b449d4e --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/entity/Like.java @@ -0,0 +1,34 @@ +package com.example.log4u.domain.like.entity; + +import com.example.log4u.common.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +@Table(name = "likes") +public class Like extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long likeId; + + @Column(nullable = false) + private Long userId; + + @Column(nullable = false) + private Long diaryId; +} diff --git a/src/main/java/com/example/log4u/domain/like/exception/DuplicateLikeException.java b/src/main/java/com/example/log4u/domain/like/exception/DuplicateLikeException.java new file mode 100644 index 00000000..46096185 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/exception/DuplicateLikeException.java @@ -0,0 +1,7 @@ +package com.example.log4u.domain.like.exception; + +public class DuplicateLikeException extends LikeException { + public DuplicateLikeException() { + super(LikeErrorCode.DUPLICATE_LIKE); + } +} diff --git a/src/main/java/com/example/log4u/domain/like/exception/LikeErrorCode.java b/src/main/java/com/example/log4u/domain/like/exception/LikeErrorCode.java new file mode 100644 index 00000000..bd11f6d6 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/exception/LikeErrorCode.java @@ -0,0 +1,31 @@ +package com.example.log4u.domain.like.exception; + +import org.springframework.http.HttpStatus; + +import com.example.log4u.common.exception.base.ErrorCode; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum LikeErrorCode implements ErrorCode { + + NOT_FOUND_LIKE(HttpStatus.NOT_FOUND, "좋아요 정보를 찾을 수 없습니다."), + DUPLICATE_LIKE(HttpStatus.BAD_REQUEST, "이미 좋아요를 눌렀습니다."); + + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getErrorMessage() { + return message; + } +} + diff --git a/src/main/java/com/example/log4u/domain/like/exception/LikeException.java b/src/main/java/com/example/log4u/domain/like/exception/LikeException.java new file mode 100644 index 00000000..ffca40df --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/exception/LikeException.java @@ -0,0 +1,10 @@ +package com.example.log4u.domain.like.exception; + +import com.example.log4u.common.exception.base.ErrorCode; +import com.example.log4u.common.exception.base.ServiceException; + +public class LikeException extends ServiceException { + public LikeException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/example/log4u/domain/like/repository/LikeRepository.java b/src/main/java/com/example/log4u/domain/like/repository/LikeRepository.java new file mode 100644 index 00000000..01997377 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/repository/LikeRepository.java @@ -0,0 +1,14 @@ +package com.example.log4u.domain.like.repository; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.log4u.domain.like.entity.Like; + +public interface LikeRepository extends JpaRepository { + boolean existsByUserIdAndDiaryId(Long userId, Long diaryId); + + Optional findByUserIdAndDiaryId(Long userId, Long diaryId); +} diff --git a/src/main/java/com/example/log4u/domain/like/service/LikeService.java b/src/main/java/com/example/log4u/domain/like/service/LikeService.java new file mode 100644 index 00000000..4f368cb7 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/service/LikeService.java @@ -0,0 +1,55 @@ +package com.example.log4u.domain.like.service; + +import java.util.Optional; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.example.log4u.domain.diary.service.DiaryService; +import com.example.log4u.domain.like.dto.request.LikeAddRequestDto; +import com.example.log4u.domain.like.dto.response.LikeAddResponseDto; +import com.example.log4u.domain.like.dto.response.LikeCancelResponseDto; +import com.example.log4u.domain.like.entity.Like; +import com.example.log4u.domain.like.exception.DuplicateLikeException; +import com.example.log4u.domain.like.repository.LikeRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class LikeService { + + private final LikeRepository likeRepository; + private final DiaryService diaryService; + + @Transactional + public LikeAddResponseDto addLike(Long userId, LikeAddRequestDto requestDto) { + validateDuplicateLike(userId, requestDto.diaryId()); + + Like like = requestDto.toEntity(userId); + likeRepository.save(like); + + Long likeCount = diaryService.incrementLikeCount(requestDto.diaryId()); + return LikeAddResponseDto.of(true, likeCount); + } + + @Transactional + public LikeCancelResponseDto cancelLike(Long userId, Long diaryId) { + return likeRepository.findByUserIdAndDiaryId(userId, diaryId) + .map(like -> { + likeRepository.delete(like); + Long likeCount = diaryService.decreaseLikeCount(diaryId); + return LikeCancelResponseDto.of(false, likeCount); + }) + .orElseGet(() -> { + Long currentCount = diaryService.getLikeCount(diaryId); + return LikeCancelResponseDto.of(false, currentCount); + }); + } + + private void validateDuplicateLike(Long userId, Long diaryId) { + if (likeRepository.existsByUserIdAndDiaryId(userId, diaryId)) { + throw new DuplicateLikeException(); + } + } +} diff --git a/src/main/java/com/example/log4u/domain/user/entity/SocialType.java b/src/main/java/com/example/log4u/domain/user/entity/SocialType.java new file mode 100644 index 00000000..84f2002f --- /dev/null +++ b/src/main/java/com/example/log4u/domain/user/entity/SocialType.java @@ -0,0 +1,7 @@ +package com.example.log4u.domain.user.entity; + +public enum SocialType { + KAKAO, + GOOGLE, + NAVER, +} diff --git a/src/main/java/com/example/log4u/domain/user/entity/User.java b/src/main/java/com/example/log4u/domain/user/entity/User.java new file mode 100644 index 00000000..b20e0d9d --- /dev/null +++ b/src/main/java/com/example/log4u/domain/user/entity/User.java @@ -0,0 +1,46 @@ +package com.example.log4u.domain.user.entity; + +import com.example.log4u.common.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class User extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long userId; + + @Column(nullable = false) + private String nickname; + + @Column(nullable = false) + private Long providerId; + + @Column(nullable = false) + private String email; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private SocialType socialType; + + private String statusMessage; + + @Column(nullable = false) + private boolean isPremium; +} diff --git a/src/test/java/com/example/log4u/domain/like/service/LikeServiceTest.java b/src/test/java/com/example/log4u/domain/like/service/LikeServiceTest.java new file mode 100644 index 00000000..36c14b2c --- /dev/null +++ b/src/test/java/com/example/log4u/domain/like/service/LikeServiceTest.java @@ -0,0 +1,149 @@ +package com.example.log4u.domain.like.service; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.example.log4u.domain.diary.entity.Diary; +import com.example.log4u.domain.diary.exception.NotFoundDiaryException; +import com.example.log4u.domain.diary.service.DiaryService; +import com.example.log4u.domain.like.dto.request.LikeAddRequestDto; +import com.example.log4u.domain.like.dto.response.LikeAddResponseDto; +import com.example.log4u.domain.like.dto.response.LikeCancelResponseDto; +import com.example.log4u.domain.like.entity.Like; +import com.example.log4u.domain.like.exception.DuplicateLikeException; +import com.example.log4u.domain.like.repository.LikeRepository; +import com.example.log4u.domain.user.entity.User; +import com.example.log4u.fixture.DiaryFixture; +import com.example.log4u.fixture.LikeFixture; +import com.example.log4u.fixture.UserFixture; + +@DisplayName("좋아요 API 단위 테스트") +@ExtendWith(MockitoExtension.class) +public class LikeServiceTest { + + @InjectMocks + private LikeService likeService; + + @Mock + private LikeRepository likeRepository; + + @Mock + private DiaryService diaryService; + + @Test + @DisplayName("성공 테스트: 좋아요 추가 ") + void likeSuccess() { + // given + Long userId = 1L; + Long diaryId = 123L; + LikeAddRequestDto requestDto = new LikeAddRequestDto(diaryId); + + Like like = LikeFixture.createLikeFixture(123243L, userId, diaryId); + Long updatedLikeCount = 11L; + + given(likeRepository.existsByUserIdAndDiaryId(userId, diaryId)).willReturn(false); + given(likeRepository.save(any(Like.class))).willReturn(like); + given(diaryService.incrementLikeCount(diaryId)).willReturn(updatedLikeCount); + + // when + LikeAddResponseDto response = likeService.addLike(userId, requestDto); + + // then + verify(likeRepository).save(any(Like.class)); + assertThat(response.liked()).isTrue(); + assertThat(response.likeCount()).isEqualTo(updatedLikeCount); + } + + @Test + @DisplayName("예외 테스트: 좋아요 추가 - 존재하지 않는 다이어리에 좋아요 요청") + void likeFail_whenDiaryNotFound() { + // given + Long userId = 1L; + Long diaryId = 100L; + LikeAddRequestDto requestDto = new LikeAddRequestDto(diaryId); + + given(likeRepository.existsByUserIdAndDiaryId(userId, diaryId)).willReturn(false); + given(diaryService.incrementLikeCount(diaryId)).willThrow(new NotFoundDiaryException()); + + // when & then + assertThrows(NotFoundDiaryException.class, () -> { + likeService.addLike(userId, requestDto); + }); + + verify(likeRepository).save(any(Like.class)); + } + + @Test + @DisplayName("예외 테스트: 좋아요 추가 - 이미 누른 좋아요 또 요청") + void likeFail_whenAlreadyLiked() { + // given + Long userId = 1L; + Long diaryId = 100L; + LikeAddRequestDto requestDto = new LikeAddRequestDto(diaryId); + + given(likeRepository.existsByUserIdAndDiaryId(userId, diaryId)).willReturn(true); + + // when & then + assertThrows(DuplicateLikeException.class, () -> { + likeService.addLike(userId, requestDto); + }); + + verify(likeRepository, never()).save(any(Like.class)); + } + + @DisplayName("성공 테스트: 좋아요 취소") + @Test + void cancelLike_Success() { + Long userId = 1L; + Long diaryId = 123L; + + Like like = LikeFixture.createLikeFixture(123243L, userId, diaryId); + Long updatedLikeCount = 11L; + + given(likeRepository.findByUserIdAndDiaryId(userId, diaryId)).willReturn(Optional.of(like)); + doNothing().when(likeRepository).delete(like); + given(diaryService.decreaseLikeCount(diaryId)).willReturn(updatedLikeCount); + + // when + LikeCancelResponseDto response = likeService.cancelLike(userId, diaryId); + + // then + verify(likeRepository).delete(like); + verify(diaryService).decreaseLikeCount(diaryId); + + assertThat(response.liked()).isFalse(); + assertThat(response.likeCount()).isEqualTo(updatedLikeCount); + } + + @DisplayName("성공 테스트: 좋아요 취소 - 존재하지 않는 좋아요에 대해 동일한 결과 반환)") + @Test + void cancelLike_NoLikeExists() { + // given + Long userId = 1L; + Long diaryId = 100L; + Long currentCount = 5L; + + given(likeRepository.findByUserIdAndDiaryId(userId, diaryId)).willReturn(Optional.empty()); + given(diaryService.getLikeCount(diaryId)).willReturn(currentCount); + + // when + LikeCancelResponseDto response = likeService.cancelLike(userId, diaryId); + + // then + verify(likeRepository, never()).delete(any()); + verify(diaryService).getLikeCount(diaryId); + + assertThat(response.liked()).isFalse(); + assertThat(response.likeCount()).isEqualTo(currentCount); + } +} diff --git a/src/test/java/com/example/log4u/fixture/DiaryFixture.java b/src/test/java/com/example/log4u/fixture/DiaryFixture.java new file mode 100644 index 00000000..c53dbbe7 --- /dev/null +++ b/src/test/java/com/example/log4u/fixture/DiaryFixture.java @@ -0,0 +1,19 @@ +package com.example.log4u.fixture; + +import com.example.log4u.domain.diary.entity.Diary; + +public class DiaryFixture { + + public static Diary createDiaryFixture() { + return Diary.builder() + .diaryId(1L) + .userId(1L) + .title("테스트 다이어리") + .thumbnailUrl("thumbnail.jpg") + .content("다이어리 내용입니다.") + .latitude(37.1234) + .longitude(127.5678) + .likeCount(11L) + .build(); + } +} diff --git a/src/test/java/com/example/log4u/fixture/LikeFixture.java b/src/test/java/com/example/log4u/fixture/LikeFixture.java new file mode 100644 index 00000000..b57481e8 --- /dev/null +++ b/src/test/java/com/example/log4u/fixture/LikeFixture.java @@ -0,0 +1,14 @@ +package com.example.log4u.fixture; + +import com.example.log4u.domain.like.entity.Like; + +public class LikeFixture { + + public static Like createLikeFixture(Long likeId, Long userId, Long diaryId) { + return Like.builder() + .likeId(likeId) + .userId(userId) + .diaryId(diaryId) + .build(); + } +} diff --git a/src/test/java/com/example/log4u/fixture/UserFixture.java b/src/test/java/com/example/log4u/fixture/UserFixture.java new file mode 100644 index 00000000..db87a26f --- /dev/null +++ b/src/test/java/com/example/log4u/fixture/UserFixture.java @@ -0,0 +1,44 @@ +package com.example.log4u.fixture; + +import com.example.log4u.domain.user.entity.SocialType; +import com.example.log4u.domain.user.entity.User; + +public class UserFixture { + + public static User createUserFixture() { + return User.builder() + .userId(1L) + .nickname("testUser") + .providerId(123L) + .email("test@example.com") + .socialType(SocialType.KAKAO) + .statusMessage("상태 메시지") + .isPremium(false) + .build(); + } + + public static User createUserFixture(Long userId) { + return User.builder() + .userId(userId) + .nickname("testUser" + userId) + .providerId(100L + userId) + .email("test" + userId + "@example.com") + .socialType(SocialType.KAKAO) + .statusMessage("상태 메시지 " + userId) + .isPremium(false) + .build(); + } + + public static User createPremiumUserFixture(Long userId) { + return User.builder() + .userId(userId) + .nickname("premiumUser" + userId) + .providerId(1000L + userId) + .email("premium" + userId + "@example.com") + .socialType(SocialType.KAKAO) + .statusMessage("프리미엄 사용자") + .isPremium(true) + .build(); + } +} +