From 3114aca5efcd12bf9f4bfdcde56f5ae34fd264b5 Mon Sep 17 00:00:00 2001 From: pykido Date: Tue, 27 Jan 2026 20:12:17 +0900 Subject: [PATCH 01/15] =?UTF-8?q?refactor=20:=20=ED=8C=80=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=ED=86=A0=EA=B8=80=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ContestCommandService.java | 8 +-- .../convenience/ContestConvenience.java | 7 ++ .../opus/modules/team/api/TeamController.java | 16 +++++ .../application/TeamLikeCommandService.java | 68 +++++++++++++++++++ .../dto/request/TeamLikeToggleRequest.java | 9 +++ .../dto/response/TeamLikeToggleResponse.java | 11 +++ .../opus/modules/team/domain/TeamLike.java | 3 + .../team/domain/dao/TeamLikeRepository.java | 11 +++ .../team/exception/TeamLikeException.java | 19 ++++++ .../team/exception/TeamLikeExceptionType.java | 29 ++++++++ 10 files changed, 174 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java create mode 100644 src/main/java/com/opus/opus/modules/team/application/dto/request/TeamLikeToggleRequest.java create mode 100644 src/main/java/com/opus/opus/modules/team/application/dto/response/TeamLikeToggleResponse.java create mode 100644 src/main/java/com/opus/opus/modules/team/domain/dao/TeamLikeRepository.java create mode 100644 src/main/java/com/opus/opus/modules/team/exception/TeamLikeException.java create mode 100644 src/main/java/com/opus/opus/modules/team/exception/TeamLikeExceptionType.java diff --git a/src/main/java/com/opus/opus/modules/contest/application/ContestCommandService.java b/src/main/java/com/opus/opus/modules/contest/application/ContestCommandService.java index d67655d3..9be92ca8 100644 --- a/src/main/java/com/opus/opus/modules/contest/application/ContestCommandService.java +++ b/src/main/java/com/opus/opus/modules/contest/application/ContestCommandService.java @@ -122,17 +122,11 @@ private void checkVoteRange(final VoteUpdateRequest voteRequest) { public void updateMaxVotesLimit(final Long contestId, final Integer maxVotesLimit) { final Contest contest = contestConvenience.getValidateExistContest(contestId); - validateNotInVotingPeriod(contest); + contestConvenience.validateNotInVotingPeriod(contest); contest.updateMaxVotesLimit(maxVotesLimit); } - private void validateNotInVotingPeriod(final Contest contest) { - if (contest.isVotingPeriod()) { - throw new ContestException(CANNOT_CHANGE_VOTES_DURING_VOTING_PERIOD); - } - } - private void checkWebpConverted(File existingFile) { if (!existingFile.getIsWebpConverted()) { throw new FileException(NOT_WEBP_CONVERTED); diff --git a/src/main/java/com/opus/opus/modules/contest/application/convenience/ContestConvenience.java b/src/main/java/com/opus/opus/modules/contest/application/convenience/ContestConvenience.java index 627bbe01..10b5e600 100644 --- a/src/main/java/com/opus/opus/modules/contest/application/convenience/ContestConvenience.java +++ b/src/main/java/com/opus/opus/modules/contest/application/convenience/ContestConvenience.java @@ -1,6 +1,7 @@ package com.opus.opus.modules.contest.application.convenience; +import static com.opus.opus.modules.contest.exception.ContestExceptionType.CANNOT_CHANGE_VOTES_DURING_VOTING_PERIOD; import static com.opus.opus.modules.contest.exception.ContestExceptionType.CATEGORY_HAS_CONTEST; import static com.opus.opus.modules.contest.exception.ContestExceptionType.CONTEST_NAME_ALREADY_EXIST; import static com.opus.opus.modules.contest.exception.ContestExceptionType.NOT_FOUND_CONTEST; @@ -47,4 +48,10 @@ public long countCurrentContests() { public List getCurrentContests() { return contestRepository.findAllByIsCurrentTrue(); } + + public void validateNotInVotingPeriod(final Contest contest) { + if (contest.isVotingPeriod()) { + throw new ContestException(CANNOT_CHANGE_VOTES_DURING_VOTING_PERIOD); + } + } } diff --git a/src/main/java/com/opus/opus/modules/team/api/TeamController.java b/src/main/java/com/opus/opus/modules/team/api/TeamController.java index 0d586aed..47282949 100644 --- a/src/main/java/com/opus/opus/modules/team/api/TeamController.java +++ b/src/main/java/com/opus/opus/modules/team/api/TeamController.java @@ -1,9 +1,14 @@ package com.opus.opus.modules.team.api; +import com.opus.opus.global.security.annotation.LoginMember; +import com.opus.opus.modules.member.domain.Member; import com.opus.opus.modules.team.application.TeamCommandService; +import com.opus.opus.modules.team.application.TeamLikeCommandService; import com.opus.opus.modules.team.application.TeamQueryService; import com.opus.opus.modules.team.application.dto.ImageResponse; import com.opus.opus.modules.team.application.dto.request.PreviewDeleteRequest; +import com.opus.opus.modules.team.application.dto.request.TeamLikeToggleRequest; +import com.opus.opus.modules.team.application.dto.response.TeamLikeToggleResponse; import jakarta.validation.Valid; import java.util.List; import lombok.RequiredArgsConstructor; @@ -14,6 +19,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -30,6 +36,7 @@ public class TeamController { private final TeamQueryService teamQueryService; private final TeamCommandService teamCommandService; + private final TeamLikeCommandService teamLikeCommandService; @GetMapping("/{teamId}/image/{imageId}") public ResponseEntity getPreviewImage(@PathVariable final Long teamId, @PathVariable final Long imageId) { @@ -103,4 +110,13 @@ public ResponseEntity deletePosterImage(@PathVariable final Long teamId) { teamCommandService.deletePosterImage(teamId); return ResponseEntity.noContent().build(); } + + @Secured({"ROLE_회원", "ROLE_관리자"}) + @PatchMapping("/{teamId}/likes") + public ResponseEntity toggleLike(@PathVariable Long teamId, + @RequestBody @Valid TeamLikeToggleRequest request, + @LoginMember Member member) { + TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), teamId, request.isLiked()); + return ResponseEntity.ok(response); + } } diff --git a/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java b/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java new file mode 100644 index 00000000..dee39d92 --- /dev/null +++ b/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java @@ -0,0 +1,68 @@ +package com.opus.opus.modules.team.application; + +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_LIKED; +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_UNLIKED; + +import com.opus.opus.modules.contest.application.convenience.ContestConvenience; +import com.opus.opus.modules.contest.domain.Contest; +import com.opus.opus.modules.team.application.convenience.TeamConvenience; +import com.opus.opus.modules.team.application.dto.response.TeamLikeToggleResponse; +import com.opus.opus.modules.team.domain.Team; +import com.opus.opus.modules.team.domain.TeamLike; +import com.opus.opus.modules.team.domain.dao.TeamLikeRepository; +import com.opus.opus.modules.team.exception.TeamLikeException; +import com.opus.opus.modules.team.exception.TeamLikeExceptionType; +import java.util.Objects; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +public class TeamLikeCommandService { + + private final TeamConvenience teamConvenience; + private final ContestConvenience contestConvenience; + private final TeamLikeRepository teamLikeRepository; + + public TeamLikeToggleResponse toggleLike(Long memberId, Long teamId, Boolean isLiked) { + Team team = teamConvenience.getValidateExistTeam(teamId); + Contest contest = contestConvenience.getValidateExistContest(team.getContestId()); + + contestConvenience.validateNotInVotingPeriod(contest); + + Optional teamLikeOptional = teamLikeRepository.findByMemberIdAndTeam(memberId, team); + return teamLikeOptional + .map(teamLike -> handleExistingLike(teamLike, isLiked)) + .orElseGet(() -> handleFirstTimeLike(memberId, team, isLiked)); + } + + private TeamLikeToggleResponse handleFirstTimeLike(Long memberId, Team team, Boolean isLiked) { + saveTeamLike(memberId, team, isLiked); + + String message = isLiked ? "좋아요가 처음 등록되었습니다." : "좋아요가 비활성화된 상태로 초기화되었습니다."; + return TeamLikeToggleResponse.of(team.getId(), isLiked, message); + } + + private TeamLikeToggleResponse handleExistingLike(TeamLike teamLike, Boolean isLiked) { + if (Objects.equals(teamLike.getIsLiked(), isLiked)) { + TeamLikeExceptionType exceptionType = isLiked ? ALREADY_LIKED : ALREADY_UNLIKED; + throw new TeamLikeException(exceptionType); + } + + teamLike.updateIsLiked(isLiked); + + String message = isLiked ? "좋아요가 등록되었습니다." : "좋아요가 취소되었습니다."; + return TeamLikeToggleResponse.of(teamLike.getTeam().getId(), isLiked, message); + } + + private void saveTeamLike(Long memberId, Team team, Boolean isLiked) { + teamLikeRepository.save(TeamLike.builder() + .memberId(memberId) + .team(team) + .isLiked(isLiked) + .build()); + } +} diff --git a/src/main/java/com/opus/opus/modules/team/application/dto/request/TeamLikeToggleRequest.java b/src/main/java/com/opus/opus/modules/team/application/dto/request/TeamLikeToggleRequest.java new file mode 100644 index 00000000..04e245aa --- /dev/null +++ b/src/main/java/com/opus/opus/modules/team/application/dto/request/TeamLikeToggleRequest.java @@ -0,0 +1,9 @@ +package com.opus.opus.modules.team.application.dto.request; + +import jakarta.validation.constraints.NotNull; + +public record TeamLikeToggleRequest( + @NotNull(message = "isLiked 값은 필수입니다.") + Boolean isLiked +) { +} diff --git a/src/main/java/com/opus/opus/modules/team/application/dto/response/TeamLikeToggleResponse.java b/src/main/java/com/opus/opus/modules/team/application/dto/response/TeamLikeToggleResponse.java new file mode 100644 index 00000000..1c6601db --- /dev/null +++ b/src/main/java/com/opus/opus/modules/team/application/dto/response/TeamLikeToggleResponse.java @@ -0,0 +1,11 @@ +package com.opus.opus.modules.team.application.dto.response; + +public record TeamLikeToggleResponse( + Long teamId, + Boolean isLiked, + String message +) { + public static TeamLikeToggleResponse of(Long teamId, Boolean isLiked, String message) { + return new TeamLikeToggleResponse(teamId, isLiked, message); + } +} diff --git a/src/main/java/com/opus/opus/modules/team/domain/TeamLike.java b/src/main/java/com/opus/opus/modules/team/domain/TeamLike.java index 1f116dfd..8bd03da7 100644 --- a/src/main/java/com/opus/opus/modules/team/domain/TeamLike.java +++ b/src/main/java/com/opus/opus/modules/team/domain/TeamLike.java @@ -41,4 +41,7 @@ public TeamLike(final Long memberId, final Team team, final Boolean isLiked) { this.isLiked = isLiked; } + public void updateIsLiked(final Boolean isLiked) { + this.isLiked = isLiked; + } } diff --git a/src/main/java/com/opus/opus/modules/team/domain/dao/TeamLikeRepository.java b/src/main/java/com/opus/opus/modules/team/domain/dao/TeamLikeRepository.java new file mode 100644 index 00000000..0ea4a37e --- /dev/null +++ b/src/main/java/com/opus/opus/modules/team/domain/dao/TeamLikeRepository.java @@ -0,0 +1,11 @@ +package com.opus.opus.modules.team.domain.dao; + +import com.opus.opus.modules.team.domain.Team; +import com.opus.opus.modules.team.domain.TeamLike; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TeamLikeRepository extends JpaRepository { + + Optional findByMemberIdAndTeam(Long memberId, Team team); +} diff --git a/src/main/java/com/opus/opus/modules/team/exception/TeamLikeException.java b/src/main/java/com/opus/opus/modules/team/exception/TeamLikeException.java new file mode 100644 index 00000000..49bb4e7c --- /dev/null +++ b/src/main/java/com/opus/opus/modules/team/exception/TeamLikeException.java @@ -0,0 +1,19 @@ +package com.opus.opus.modules.team.exception; + +import com.opus.opus.global.base.BaseException; +import com.opus.opus.global.base.BaseExceptionType; + +public class TeamLikeException extends BaseException { + + private final TeamLikeExceptionType exceptionType; + + public TeamLikeException(final TeamLikeExceptionType exceptionType) { + super(exceptionType.errorMessage()); + this.exceptionType = exceptionType; + } + + @Override + public BaseExceptionType exceptionType() { + return exceptionType; + } +} diff --git a/src/main/java/com/opus/opus/modules/team/exception/TeamLikeExceptionType.java b/src/main/java/com/opus/opus/modules/team/exception/TeamLikeExceptionType.java new file mode 100644 index 00000000..53de35cd --- /dev/null +++ b/src/main/java/com/opus/opus/modules/team/exception/TeamLikeExceptionType.java @@ -0,0 +1,29 @@ +package com.opus.opus.modules.team.exception; + +import com.opus.opus.global.base.BaseExceptionType; +import org.springframework.http.HttpStatus; + +public enum TeamLikeExceptionType implements BaseExceptionType { + + ALREADY_LIKED(HttpStatus.BAD_REQUEST, "이미 좋아요한 팀입니다."), + ALREADY_UNLIKED(HttpStatus.BAD_REQUEST, "이미 좋아요를 취소한 팀입니다."), + VOTE_PERIOD_NOW(HttpStatus.BAD_REQUEST, "투표 기간에는 좋아요를 할 수 없습니다."); + + private final HttpStatus httpStatus; + private final String message; + + TeamLikeExceptionType(final HttpStatus httpStatus, final String message) { + this.httpStatus = httpStatus; + this.message = message; + } + + @Override + public HttpStatus httpStatus() { + return httpStatus; + } + + @Override + public String errorMessage() { + return message; + } +} From 12ebbb782fd397452e7bcb005b4127392b350c79 Mon Sep 17 00:00:00 2001 From: pykido Date: Tue, 27 Jan 2026 20:21:15 +0900 Subject: [PATCH 02/15] =?UTF-8?q?refactor=20:=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/contest/application/ContestCommandService.java | 3 --- .../contest/application/convenience/ContestConvenience.java | 4 ++-- .../modules/contest/exception/ContestExceptionType.java | 2 +- .../opus/modules/team/exception/TeamLikeExceptionType.java | 3 +-- .../opus/contest/application/ContestCommandServiceTest.java | 6 +++--- .../com/opus/opus/restdocs/docs/ContestApiDocsTest.java | 4 ++-- 6 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/opus/opus/modules/contest/application/ContestCommandService.java b/src/main/java/com/opus/opus/modules/contest/application/ContestCommandService.java index 9be92ca8..992ba30a 100644 --- a/src/main/java/com/opus/opus/modules/contest/application/ContestCommandService.java +++ b/src/main/java/com/opus/opus/modules/contest/application/ContestCommandService.java @@ -4,7 +4,6 @@ import static com.opus.opus.modules.contest.exception.ContestExceptionType.*; import static com.opus.opus.modules.contest.exception.ContestExceptionType.ALREADY_CURRENT_CONTEST; import static com.opus.opus.modules.contest.exception.ContestExceptionType.ALREADY_NOT_CURRENT_CONTEST; -import static com.opus.opus.modules.contest.exception.ContestExceptionType.CANNOT_CHANGE_VOTES_DURING_VOTING_PERIOD; import static com.opus.opus.modules.contest.exception.ContestExceptionType.CURRENT_CONTEST_LIMIT_EXCEEDED; import static com.opus.opus.modules.file.domain.FileImageType.BANNER; import static com.opus.opus.modules.file.domain.ReferenceDomainType.CONTEST; @@ -17,12 +16,10 @@ import com.opus.opus.modules.contest.application.dto.request.VoteUpdateRequest; import com.opus.opus.modules.contest.application.dto.response.ContestCurrentToggleResponse; import com.opus.opus.modules.contest.application.dto.response.ContestResponse; -import com.opus.opus.modules.contest.application.dto.response.VotePeriodResponse; import com.opus.opus.modules.contest.domain.Contest; import com.opus.opus.modules.contest.domain.ContestCategory; import com.opus.opus.modules.contest.domain.dao.ContestRepository; import com.opus.opus.modules.contest.exception.ContestException; -import com.opus.opus.modules.contest.exception.ContestExceptionType; import com.opus.opus.modules.file.domain.File; import com.opus.opus.modules.file.domain.dao.FileRepository; import com.opus.opus.modules.file.exception.FileException; diff --git a/src/main/java/com/opus/opus/modules/contest/application/convenience/ContestConvenience.java b/src/main/java/com/opus/opus/modules/contest/application/convenience/ContestConvenience.java index 10b5e600..0726c0af 100644 --- a/src/main/java/com/opus/opus/modules/contest/application/convenience/ContestConvenience.java +++ b/src/main/java/com/opus/opus/modules/contest/application/convenience/ContestConvenience.java @@ -1,7 +1,7 @@ package com.opus.opus.modules.contest.application.convenience; -import static com.opus.opus.modules.contest.exception.ContestExceptionType.CANNOT_CHANGE_VOTES_DURING_VOTING_PERIOD; +import static com.opus.opus.modules.contest.exception.ContestExceptionType.NOT_ALLOWED_DURING_VOTING_PERIOD; import static com.opus.opus.modules.contest.exception.ContestExceptionType.CATEGORY_HAS_CONTEST; import static com.opus.opus.modules.contest.exception.ContestExceptionType.CONTEST_NAME_ALREADY_EXIST; import static com.opus.opus.modules.contest.exception.ContestExceptionType.NOT_FOUND_CONTEST; @@ -51,7 +51,7 @@ public List getCurrentContests() { public void validateNotInVotingPeriod(final Contest contest) { if (contest.isVotingPeriod()) { - throw new ContestException(CANNOT_CHANGE_VOTES_DURING_VOTING_PERIOD); + throw new ContestException(NOT_ALLOWED_DURING_VOTING_PERIOD); } } } diff --git a/src/main/java/com/opus/opus/modules/contest/exception/ContestExceptionType.java b/src/main/java/com/opus/opus/modules/contest/exception/ContestExceptionType.java index 3cc966f0..8ff5020a 100644 --- a/src/main/java/com/opus/opus/modules/contest/exception/ContestExceptionType.java +++ b/src/main/java/com/opus/opus/modules/contest/exception/ContestExceptionType.java @@ -12,7 +12,7 @@ public enum ContestExceptionType implements BaseExceptionType { CATEGORY_HAS_CONTEST(HttpStatus.CONFLICT, "해당 카테고리에 속한 대회가 존재합니다."), CONTEST_NAME_ALREADY_EXIST(HttpStatus.CONFLICT, "동일한 대회명이 있습니다."), VOTE_END_PRECEDE_VOTE_START(HttpStatus.BAD_REQUEST, "투표 종료가 투표 시작보다 빠를 수 없습니다."), - CANNOT_CHANGE_VOTES_DURING_VOTING_PERIOD(HttpStatus.BAD_REQUEST, "투표 진행중에는 최대 투표 개수를 변경할 수 없습니다.") + NOT_ALLOWED_DURING_VOTING_PERIOD(HttpStatus.BAD_REQUEST, "현재 투표 기간이므로 해당 작업을 수행할 수 없습니다.") ; private final HttpStatus httpStatus; diff --git a/src/main/java/com/opus/opus/modules/team/exception/TeamLikeExceptionType.java b/src/main/java/com/opus/opus/modules/team/exception/TeamLikeExceptionType.java index 53de35cd..0afe6ad1 100644 --- a/src/main/java/com/opus/opus/modules/team/exception/TeamLikeExceptionType.java +++ b/src/main/java/com/opus/opus/modules/team/exception/TeamLikeExceptionType.java @@ -6,8 +6,7 @@ public enum TeamLikeExceptionType implements BaseExceptionType { ALREADY_LIKED(HttpStatus.BAD_REQUEST, "이미 좋아요한 팀입니다."), - ALREADY_UNLIKED(HttpStatus.BAD_REQUEST, "이미 좋아요를 취소한 팀입니다."), - VOTE_PERIOD_NOW(HttpStatus.BAD_REQUEST, "투표 기간에는 좋아요를 할 수 없습니다."); + ALREADY_UNLIKED(HttpStatus.BAD_REQUEST, "이미 좋아요를 취소한 팀입니다."); private final HttpStatus httpStatus; private final String message; diff --git a/src/test/java/com/opus/opus/contest/application/ContestCommandServiceTest.java b/src/test/java/com/opus/opus/contest/application/ContestCommandServiceTest.java index 62725729..c2181a9e 100644 --- a/src/test/java/com/opus/opus/contest/application/ContestCommandServiceTest.java +++ b/src/test/java/com/opus/opus/contest/application/ContestCommandServiceTest.java @@ -1,6 +1,6 @@ package com.opus.opus.contest.application; -import static com.opus.opus.modules.contest.exception.ContestExceptionType.CANNOT_CHANGE_VOTES_DURING_VOTING_PERIOD; +import static com.opus.opus.modules.contest.exception.ContestExceptionType.NOT_ALLOWED_DURING_VOTING_PERIOD; import static com.opus.opus.modules.contest.exception.ContestExceptionType.NOT_FOUND_CONTEST; import static com.opus.opus.modules.contest.exception.ContestExceptionType.VOTE_END_PRECEDE_VOTE_START; import static org.assertj.core.api.Assertions.assertThat; @@ -98,7 +98,7 @@ void setUp() { assertThatThrownBy(() -> { contestCommandService.updateMaxVotesLimit(contest.getId(), MAX_VOTES_LIMIT); }).isInstanceOf(ContestException.class) - .hasMessage(CANNOT_CHANGE_VOTES_DURING_VOTING_PERIOD.errorMessage()); + .hasMessage(NOT_ALLOWED_DURING_VOTING_PERIOD.errorMessage()); } @Test @@ -125,4 +125,4 @@ void setUp() { final Contest updatedContest = contestRepository.findById(contest.getId()).orElseThrow(); // 변경 후 값 검증 assertThat(updatedContest.getMaxVotesLimit()).isEqualTo(MAX_VOTES_LIMIT); } -} \ No newline at end of file +} diff --git a/src/test/java/com/opus/opus/restdocs/docs/ContestApiDocsTest.java b/src/test/java/com/opus/opus/restdocs/docs/ContestApiDocsTest.java index f4382dc1..9941f183 100644 --- a/src/test/java/com/opus/opus/restdocs/docs/ContestApiDocsTest.java +++ b/src/test/java/com/opus/opus/restdocs/docs/ContestApiDocsTest.java @@ -1,6 +1,6 @@ package com.opus.opus.restdocs.docs; -import static com.opus.opus.modules.contest.exception.ContestExceptionType.CANNOT_CHANGE_VOTES_DURING_VOTING_PERIOD; +import static com.opus.opus.modules.contest.exception.ContestExceptionType.NOT_ALLOWED_DURING_VOTING_PERIOD; import static com.opus.opus.modules.contest.exception.ContestExceptionType.NOT_FOUND_CONTEST; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @@ -144,7 +144,7 @@ public class ContestApiDocsTest extends RestDocsTest { void 투표_진행_중_최대_투표_개수_변경_시_에러를_반환한다() throws Exception { final ContestVotesLimitRequest request = new ContestVotesLimitRequest(2); - willThrow(new ContestException(CANNOT_CHANGE_VOTES_DURING_VOTING_PERIOD)) + willThrow(new ContestException(NOT_ALLOWED_DURING_VOTING_PERIOD)) .given(contestCommandService) .updateMaxVotesLimit(any(), any()); From ab45b0063adcce69ed9773d0528ca2b3e566455b Mon Sep 17 00:00:00 2001 From: pykido Date: Tue, 27 Jan 2026 20:27:56 +0900 Subject: [PATCH 03/15] =?UTF-8?q?test=20:=20=ED=8C=80=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=20=ED=86=A0=EA=B8=80=20API=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/opus/opus/team/TeamFixture.java | 8 +- .../com/opus/opus/team/TeamLikeFixture.java | 15 ++ .../TeamLikeCommandServiceTest.java | 149 ++++++++++++++++++ 3 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/opus/opus/team/TeamLikeFixture.java create mode 100644 src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java diff --git a/src/test/java/com/opus/opus/team/TeamFixture.java b/src/test/java/com/opus/opus/team/TeamFixture.java index 3a5c34d2..7a90d5a7 100644 --- a/src/test/java/com/opus/opus/team/TeamFixture.java +++ b/src/test/java/com/opus/opus/team/TeamFixture.java @@ -5,7 +5,13 @@ public class TeamFixture { + private static final Long DEFAULT_CONTEST_ID = 1L; + public static Team createTeam() { + return createTeam(DEFAULT_CONTEST_ID); + } + + public static Team createTeam(final Long contestId) { return Team.builder() .teamName("팀 옵스") .projectName("옵스 프로젝트") @@ -14,7 +20,7 @@ public static Team createTeam() { .githubPath("http://github.com/example") .productionPath("http://production.example.com") .youTubePath("http://youtube.com/example") - .contestId(1L) + .contestId(contestId) .trackId(1L) .itemOrder(1) .teamMembers(new ArrayList<>()) diff --git a/src/test/java/com/opus/opus/team/TeamLikeFixture.java b/src/test/java/com/opus/opus/team/TeamLikeFixture.java new file mode 100644 index 00000000..33bc78c5 --- /dev/null +++ b/src/test/java/com/opus/opus/team/TeamLikeFixture.java @@ -0,0 +1,15 @@ +package com.opus.opus.team; + +import com.opus.opus.modules.team.domain.Team; +import com.opus.opus.modules.team.domain.TeamLike; + +public class TeamLikeFixture { + + public static TeamLike createTeamLike(final Team team, final Long memberId, final Boolean isLiked) { + return TeamLike.builder() + .team(team) + .memberId(memberId) + .isLiked(isLiked) + .build(); + } +} diff --git a/src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java b/src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java new file mode 100644 index 00000000..935acc9d --- /dev/null +++ b/src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java @@ -0,0 +1,149 @@ +package com.opus.opus.team.application; + +import static com.opus.opus.modules.contest.exception.ContestExceptionType.NOT_ALLOWED_DURING_VOTING_PERIOD; +import static com.opus.opus.modules.team.exception.TeamExceptionType.NOT_FOUND_TEAM; +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_LIKED; +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_UNLIKED; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.opus.opus.contest.ContestFixture; +import com.opus.opus.helper.IntegrationTest; +import com.opus.opus.member.MemberFixture; +import com.opus.opus.modules.contest.domain.Contest; +import com.opus.opus.modules.contest.domain.dao.ContestRepository; +import com.opus.opus.modules.contest.exception.ContestException; +import com.opus.opus.modules.member.domain.Member; +import com.opus.opus.modules.member.domain.dao.MemberRepository; +import com.opus.opus.modules.team.application.TeamLikeCommandService; +import com.opus.opus.modules.team.application.dto.response.TeamLikeToggleResponse; +import com.opus.opus.modules.team.domain.Team; +import com.opus.opus.modules.team.domain.TeamLike; +import com.opus.opus.modules.team.domain.dao.TeamLikeRepository; +import com.opus.opus.modules.team.domain.dao.TeamRepository; +import com.opus.opus.modules.team.exception.TeamException; +import com.opus.opus.modules.team.exception.TeamLikeException; +import com.opus.opus.team.TeamFixture; +import com.opus.opus.team.TeamLikeFixture; +import java.time.LocalDateTime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class TeamLikeCommandServiceTest extends IntegrationTest { + + @Autowired + private TeamLikeCommandService teamLikeCommandService; + + @Autowired + private TeamRepository teamRepository; + @Autowired + private MemberRepository memberRepository; + @Autowired + private TeamLikeRepository teamLikeRepository; + @Autowired + private ContestRepository contestRepository; + + private Contest contest; + private Team team; + private Member member; + + @BeforeEach + void setUp() { + Contest newContest = ContestFixture.createContest(); + newContest.updateVotePeriod(LocalDateTime.now().minusDays(10), LocalDateTime.now().minusDays(5)); // 투표 기간이 아닌 대회 설정 (좋아요는 투표 기간이 아닐 때만 가능) + contest = contestRepository.save(newContest); + + team = teamRepository.save(TeamFixture.createTeam(contest.getId())); + member = memberRepository.save(MemberFixture.createMember()); + } + + @Test + @DisplayName("[성공] 처음 좋아요하면 TeamLike가 생성되고 좋아요가 등록된다.") + void 처음_좋아요하면_TeamLike가_생성되고_좋아요가_등록된다() { + TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), team.getId(), true); + + assertThat(response.teamId()).isEqualTo(team.getId()); + assertThat(response.isLiked()).isTrue(); + assertThat(response.message()).isEqualTo("좋아요가 처음 등록되었습니다."); + + TeamLike savedLike = teamLikeRepository.findByMemberIdAndTeam(member.getId(), team).orElseThrow(); + assertThat(savedLike.getIsLiked()).isTrue(); + } + + @Test + @DisplayName("[성공] 처음 요청이 isLiked=false이면 비활성화 상태로 초기화된다.") + void 처음_요청이_isLiked_false이면_비활성화_상태로_초기화된다() { + TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), team.getId(), false); + + assertThat(response.isLiked()).isFalse(); + assertThat(response.message()).isEqualTo("좋아요가 비활성화된 상태로 초기화되었습니다."); + } + + @Test + @DisplayName("[성공] 기존 좋아요를 취소할 수 있다.") + void 기존_좋아요를_취소할_수_있다() { + teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), true)); + + TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), team.getId(), false); + + assertThat(response.isLiked()).isFalse(); + assertThat(response.message()).isEqualTo("좋아요가 취소되었습니다."); + } + + @Test + @DisplayName("[성공] 취소한 좋아요를 다시 등록할 수 있다.") + void 취소한_좋아요를_다시_등록할_수_있다() { + teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), false)); + + TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), team.getId(), true); + + assertThat(response.isLiked()).isTrue(); + assertThat(response.message()).isEqualTo("좋아요가 등록되었습니다."); + } + + @Test + @DisplayName("[실패] 이미 좋아요한 팀에 다시 좋아요하면 예외가 발생한다.") + void 이미_좋아요한_팀에_다시_좋아요하면_예외가_발생한다() { + teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), true)); + + assertThatThrownBy(() -> teamLikeCommandService.toggleLike(member.getId(), team.getId(), true)) + .isInstanceOf(TeamLikeException.class) + .hasMessage(ALREADY_LIKED.errorMessage()); + } + + @Test + @DisplayName("[실패] 이미 좋아요 취소한 팀에 다시 취소하면 예외가 발생한다.") + void 이미_좋아요_취소한_팀에_다시_취소하면_예외가_발생한다() { + teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), false)); + + assertThatThrownBy(() -> teamLikeCommandService.toggleLike(member.getId(), team.getId(), false)) + .isInstanceOf(TeamLikeException.class) + .hasMessage(ALREADY_UNLIKED.errorMessage()); + } + + @Test + @DisplayName("[실패] 존재하지 않는 팀에는 좋아요할 수 없다.") + void 존재하지_않는_팀에는_좋아요할_수_없다() { + final Long invalidTeamId = 999L; + + assertThatThrownBy(() -> teamLikeCommandService.toggleLike(member.getId(), invalidTeamId, true)) + .isInstanceOf(TeamException.class) + .hasMessage(NOT_FOUND_TEAM.errorMessage()); + } + + @Test + @DisplayName("[실패] 투표 기간에는 좋아요할 수 없다.") + void 투표_기간에는_좋아요할_수_없다() { + Contest votingContest = ContestFixture.createContest(); + votingContest.updateVotePeriod(LocalDateTime.now().minusDays(1), LocalDateTime.now().plusDays(1)); // 투표 기간인 대회 설정 + votingContest = contestRepository.save(votingContest); + + Team votingTeam = teamRepository.save(TeamFixture.createTeam(votingContest.getId())); + + assertThatThrownBy(() -> teamLikeCommandService.toggleLike(member.getId(), votingTeam.getId(), true)) + .isInstanceOf(ContestException.class) + .hasMessage(NOT_ALLOWED_DURING_VOTING_PERIOD.errorMessage()); + } +} From a441591f671748a3dc0f37e4dd40a292083fe855 Mon Sep 17 00:00:00 2001 From: pykido Date: Sat, 31 Jan 2026 23:49:49 +0900 Subject: [PATCH 04/15] =?UTF-8?q?refactor=20:=20=ED=88=AC=ED=91=9C=20?= =?UTF-8?q?=ED=86=A0=EA=B8=80=20API=20=EC=B0=B8=EA=B3=A0=ED=95=98=EC=97=AC?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../opus/modules/team/api/TeamController.java | 4 +-- .../application/TeamLikeCommandService.java | 35 +++++++++++-------- .../opus/modules/team/domain/TeamLike.java | 5 +++ .../team/exception/TeamLikeExceptionType.java | 4 ++- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/opus/opus/modules/team/api/TeamController.java b/src/main/java/com/opus/opus/modules/team/api/TeamController.java index 47282949..2374fa43 100644 --- a/src/main/java/com/opus/opus/modules/team/api/TeamController.java +++ b/src/main/java/com/opus/opus/modules/team/api/TeamController.java @@ -19,9 +19,9 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestPart; @@ -112,7 +112,7 @@ public ResponseEntity deletePosterImage(@PathVariable final Long teamId) { } @Secured({"ROLE_회원", "ROLE_관리자"}) - @PatchMapping("/{teamId}/likes") + @PutMapping("/{teamId}/likes") public ResponseEntity toggleLike(@PathVariable Long teamId, @RequestBody @Valid TeamLikeToggleRequest request, @LoginMember Member member) { diff --git a/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java b/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java index dee39d92..f335de08 100644 --- a/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java +++ b/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java @@ -2,6 +2,8 @@ import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_LIKED; import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_UNLIKED; +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.DUPLICATE_LIKE_REQUEST; +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.NOT_LIKED_YET; import com.opus.opus.modules.contest.application.convenience.ContestConvenience; import com.opus.opus.modules.contest.domain.Contest; @@ -11,10 +13,10 @@ import com.opus.opus.modules.team.domain.TeamLike; import com.opus.opus.modules.team.domain.dao.TeamLikeRepository; import com.opus.opus.modules.team.exception.TeamLikeException; -import com.opus.opus.modules.team.exception.TeamLikeExceptionType; import java.util.Objects; import java.util.Optional; import lombok.RequiredArgsConstructor; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -40,29 +42,34 @@ public TeamLikeToggleResponse toggleLike(Long memberId, Long teamId, Boolean isL } private TeamLikeToggleResponse handleFirstTimeLike(Long memberId, Team team, Boolean isLiked) { - saveTeamLike(memberId, team, isLiked); + if (!isLiked) { + throw new TeamLikeException(NOT_LIKED_YET); + } - String message = isLiked ? "좋아요가 처음 등록되었습니다." : "좋아요가 비활성화된 상태로 초기화되었습니다."; - return TeamLikeToggleResponse.of(team.getId(), isLiked, message); + saveTeamLike(memberId, team, true); + return TeamLikeToggleResponse.of(team.getId(), true, "좋아요가 등록되었습니다."); } - private TeamLikeToggleResponse handleExistingLike(TeamLike teamLike, Boolean isLiked) { + private TeamLikeToggleResponse handleExistingLike(final TeamLike teamLike, final Boolean isLiked) { if (Objects.equals(teamLike.getIsLiked(), isLiked)) { - TeamLikeExceptionType exceptionType = isLiked ? ALREADY_LIKED : ALREADY_UNLIKED; - throw new TeamLikeException(exceptionType); + throw new TeamLikeException(isLiked ? ALREADY_LIKED : ALREADY_UNLIKED); } teamLike.updateIsLiked(isLiked); - String message = isLiked ? "좋아요가 등록되었습니다." : "좋아요가 취소되었습니다."; - return TeamLikeToggleResponse.of(teamLike.getTeam().getId(), isLiked, message); + return TeamLikeToggleResponse.of(teamLike.getTeam().getId(), isLiked, isLiked ? "좋아요가 등록되었습니다." : "좋아요가 취소되었습니다."); } private void saveTeamLike(Long memberId, Team team, Boolean isLiked) { - teamLikeRepository.save(TeamLike.builder() - .memberId(memberId) - .team(team) - .isLiked(isLiked) - .build()); + try { + teamLikeRepository.save(TeamLike.builder() + .memberId(memberId) + .team(team) + .isLiked(isLiked) + .build()); + teamLikeRepository.flush(); + } catch (DataIntegrityViolationException e) { + throw new TeamLikeException(DUPLICATE_LIKE_REQUEST); + } } } diff --git a/src/main/java/com/opus/opus/modules/team/domain/TeamLike.java b/src/main/java/com/opus/opus/modules/team/domain/TeamLike.java index 8bd03da7..71889ec9 100644 --- a/src/main/java/com/opus/opus/modules/team/domain/TeamLike.java +++ b/src/main/java/com/opus/opus/modules/team/domain/TeamLike.java @@ -10,6 +10,8 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -18,6 +20,9 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(uniqueConstraints = { + @UniqueConstraint(name = "uk_team_like_member_team", columnNames = {"member_id", "team_id"}) +}) public class TeamLike extends BaseEntity { @Id diff --git a/src/main/java/com/opus/opus/modules/team/exception/TeamLikeExceptionType.java b/src/main/java/com/opus/opus/modules/team/exception/TeamLikeExceptionType.java index 0afe6ad1..8687cdba 100644 --- a/src/main/java/com/opus/opus/modules/team/exception/TeamLikeExceptionType.java +++ b/src/main/java/com/opus/opus/modules/team/exception/TeamLikeExceptionType.java @@ -6,7 +6,9 @@ public enum TeamLikeExceptionType implements BaseExceptionType { ALREADY_LIKED(HttpStatus.BAD_REQUEST, "이미 좋아요한 팀입니다."), - ALREADY_UNLIKED(HttpStatus.BAD_REQUEST, "이미 좋아요를 취소한 팀입니다."); + ALREADY_UNLIKED(HttpStatus.BAD_REQUEST, "이미 좋아요를 취소한 팀입니다."), + NOT_LIKED_YET(HttpStatus.BAD_REQUEST, "아직 좋아요하지 않은 팀입니다."), + DUPLICATE_LIKE_REQUEST(HttpStatus.CONFLICT, "이미 처리된 요청입니다."); private final HttpStatus httpStatus; private final String message; From 44eac9fc458a01af013185f14f987cd413a46ce8 Mon Sep 17 00:00:00 2001 From: pykido Date: Sat, 31 Jan 2026 23:55:33 +0900 Subject: [PATCH 05/15] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/opus/opus/team/TeamFixture.java | 4 +-- .../TeamLikeCommandServiceTest.java | 28 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/test/java/com/opus/opus/team/TeamFixture.java b/src/test/java/com/opus/opus/team/TeamFixture.java index 7a90d5a7..0d46d8ad 100644 --- a/src/test/java/com/opus/opus/team/TeamFixture.java +++ b/src/test/java/com/opus/opus/team/TeamFixture.java @@ -8,10 +8,10 @@ public class TeamFixture { private static final Long DEFAULT_CONTEST_ID = 1L; public static Team createTeam() { - return createTeam(DEFAULT_CONTEST_ID); + return createTeamWithContestId(DEFAULT_CONTEST_ID); } - public static Team createTeam(final Long contestId) { + public static Team createTeamWithContestId(final Long contestId) { return Team.builder() .teamName("팀 옵스") .projectName("옵스 프로젝트") diff --git a/src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java b/src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java index 935acc9d..6315d84d 100644 --- a/src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java +++ b/src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java @@ -4,6 +4,7 @@ import static com.opus.opus.modules.team.exception.TeamExceptionType.NOT_FOUND_TEAM; import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_LIKED; import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_UNLIKED; +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.NOT_LIKED_YET; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -52,10 +53,10 @@ public class TeamLikeCommandServiceTest extends IntegrationTest { @BeforeEach void setUp() { Contest newContest = ContestFixture.createContest(); - newContest.updateVotePeriod(LocalDateTime.now().minusDays(10), LocalDateTime.now().minusDays(5)); // 투표 기간이 아닌 대회 설정 (좋아요는 투표 기간이 아닐 때만 가능) + newContest.updateVotePeriod(LocalDateTime.now().minusDays(10), LocalDateTime.now().minusDays(5)); contest = contestRepository.save(newContest); - team = teamRepository.save(TeamFixture.createTeam(contest.getId())); + team = teamRepository.save(TeamFixture.createTeamWithContestId(contest.getId())); member = memberRepository.save(MemberFixture.createMember()); } @@ -66,21 +67,12 @@ void setUp() { assertThat(response.teamId()).isEqualTo(team.getId()); assertThat(response.isLiked()).isTrue(); - assertThat(response.message()).isEqualTo("좋아요가 처음 등록되었습니다."); + assertThat(response.message()).isEqualTo("좋아요가 등록되었습니다."); TeamLike savedLike = teamLikeRepository.findByMemberIdAndTeam(member.getId(), team).orElseThrow(); assertThat(savedLike.getIsLiked()).isTrue(); } - @Test - @DisplayName("[성공] 처음 요청이 isLiked=false이면 비활성화 상태로 초기화된다.") - void 처음_요청이_isLiked_false이면_비활성화_상태로_초기화된다() { - TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), team.getId(), false); - - assertThat(response.isLiked()).isFalse(); - assertThat(response.message()).isEqualTo("좋아요가 비활성화된 상태로 초기화되었습니다."); - } - @Test @DisplayName("[성공] 기존 좋아요를 취소할 수 있다.") void 기존_좋아요를_취소할_수_있다() { @@ -103,6 +95,14 @@ void setUp() { assertThat(response.message()).isEqualTo("좋아요가 등록되었습니다."); } + @Test + @DisplayName("[실패] 좋아요한 적 없는 팀에 취소 요청하면 예외가 발생한다.") + void 좋아요한_적_없는_팀에_취소_요청하면_예외가_발생한다() { + assertThatThrownBy(() -> teamLikeCommandService.toggleLike(member.getId(), team.getId(), false)) + .isInstanceOf(TeamLikeException.class) + .hasMessage(NOT_LIKED_YET.errorMessage()); + } + @Test @DisplayName("[실패] 이미 좋아요한 팀에 다시 좋아요하면 예외가 발생한다.") void 이미_좋아요한_팀에_다시_좋아요하면_예외가_발생한다() { @@ -137,10 +137,10 @@ void setUp() { @DisplayName("[실패] 투표 기간에는 좋아요할 수 없다.") void 투표_기간에는_좋아요할_수_없다() { Contest votingContest = ContestFixture.createContest(); - votingContest.updateVotePeriod(LocalDateTime.now().minusDays(1), LocalDateTime.now().plusDays(1)); // 투표 기간인 대회 설정 + votingContest.updateVotePeriod(LocalDateTime.now().minusDays(1), LocalDateTime.now().plusDays(1)); votingContest = contestRepository.save(votingContest); - Team votingTeam = teamRepository.save(TeamFixture.createTeam(votingContest.getId())); + Team votingTeam = teamRepository.save(TeamFixture.createTeamWithContestId(votingContest.getId())); assertThatThrownBy(() -> teamLikeCommandService.toggleLike(member.getId(), votingTeam.getId(), true)) .isInstanceOf(ContestException.class) From 90c910e102fed0936179556650eb5c89a3bddc07 Mon Sep 17 00:00:00 2001 From: pykido Date: Sun, 1 Feb 2026 00:01:34 +0900 Subject: [PATCH 06/15] =?UTF-8?q?docs=20:=20docs=20Test=20=EB=B0=8F=20docs?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/opus/opus/docs/asciidoc/opus.adoc | 3 +- .../opus/opus/docs/asciidoc/team-like.adoc | 159 ++++++++++++ .../com/opus/opus/restdocs/RestDocsTest.java | 4 + .../restdocs/docs/TeamLikeApiDocsTest.java | 244 ++++++++++++++++++ 4 files changed, 409 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/opus/opus/docs/asciidoc/team-like.adoc create mode 100644 src/test/java/com/opus/opus/restdocs/docs/TeamLikeApiDocsTest.java diff --git a/src/main/java/com/opus/opus/docs/asciidoc/opus.adoc b/src/main/java/com/opus/opus/docs/asciidoc/opus.adoc index 438881d1..276ce20e 100644 --- a/src/main/java/com/opus/opus/docs/asciidoc/opus.adoc +++ b/src/main/java/com/opus/opus/docs/asciidoc/opus.adoc @@ -13,13 +13,14 @@ endif::[] link:./member.html[회원 API] == 팀 관련 API - link:./team.html[팀 API] link:./team-comment.html[팀 댓글 API] link:./team-member.html[팀원 API] +link:./team-like.html[팀 좋아요 API] + == 공지 관련 API link:./notice.html[공지 API] diff --git a/src/main/java/com/opus/opus/docs/asciidoc/team-like.adoc b/src/main/java/com/opus/opus/docs/asciidoc/team-like.adoc new file mode 100644 index 00000000..e42c4b0f --- /dev/null +++ b/src/main/java/com/opus/opus/docs/asciidoc/team-like.adoc @@ -0,0 +1,159 @@ +ifndef::snippets[] +:snippets: ./build/generated-snippets +endif::[] + += TEAM LIKE API 문서 +:doctype: book +:icons: font +:source-highlighter: highlightjs +:toc: left +:toclevels: 3 +:sectnums: + +== API 목록 + +link:./opus.html[API 목록으로 돌아가기] + +== `PUT`: 팀 좋아요 토글 + +해당 팀에 대해 좋아요(찜) 상태를 토글합니다. + +* `isLiked: true` → 좋아요 등록 +* `isLiked: false` → 좋아요 취소 + + +NOTE: 좋아요를 통해 팀을 찜할 수 있습니다. + +NOTE: 투표 기간에는 투표만 가능하고, 투표 기간이 아닐 때만 좋아요가 가능합니다. + +.Path Parameters +include::{snippets}/toggle-team-like/path-parameters.adoc[] + +.HTTP Request Headers +include::{snippets}/toggle-team-like/request-headers.adoc[] + +.Request Fields +include::{snippets}/toggle-team-like/request-fields.adoc[] + +.Response Fields +include::{snippets}/toggle-team-like/response-fields.adoc[] + +=== 시나리오 1: TeamLike 데이터가 없는 경우 + +특정 멤버가 특정 팀에 대해 좋아요 API를 처음 호출하면, TeamLike 테이블에 데이터가 새로 생성됩니다. + +[cols="1,2,1"] +|=== +|Request isLiked |응답 메시지 |HTTP 상태 코드 + +|true +|좋아요가 등록되었습니다. +|200 OK + +|false +|아직 좋아요하지 않은 팀입니다. +|400 Bad Request +|=== + +isLiked: true → 좋아요 등록 + +include::{snippets}/toggle-team-like/http-request.adoc[] + +include::{snippets}/toggle-team-like/http-response.adoc[] + +=== 시나리오 2: TeamLike 데이터가 있는 경우 + +이미 해당 팀에 대한 좋아요 기록이 있는 경우, 상태에 따라 토글됩니다. + +[cols="1,1,2,1"] +|=== +|현재 isLiked |Request isLiked |응답 메시지 |HTTP 상태 코드 + +|true +|true +|이미 좋아요한 팀입니다. +|400 Bad Request + +|true +|false +|좋아요가 취소되었습니다. +|200 OK + +|false +|true +|좋아요가 등록되었습니다. +|200 OK + +|false +|false +|이미 좋아요를 취소한 팀입니다. +|400 Bad Request +|=== + +좋아요 취소 (isLiked: true → false) + +include::{snippets}/cancel-team-like/http-request.adoc[] + +include::{snippets}/cancel-team-like/http-response.adoc[] + +=== ⚠️ 실패 케이스 + +.❌ Case 1: 존재하지 않는 팀 + +[%collapsible] + +==== + +include::{snippets}/toggle-team-like-fail-not-found/http-request.adoc[] + +include::{snippets}/toggle-team-like-fail-not-found/http-response.adoc[] + +==== + +.❌ Case 2: 이미 좋아요한 팀 + +[%collapsible] + +==== + +include::{snippets}/toggle-team-like-fail-already-liked/http-request.adoc[] + +include::{snippets}/toggle-team-like-fail-already-liked/http-response.adoc[] + +==== + +.❌ Case 3: 이미 좋아요 취소한 팀 + +[%collapsible] + +==== + +include::{snippets}/toggle-team-like-fail-already-unliked/http-request.adoc[] + +include::{snippets}/toggle-team-like-fail-already-unliked/http-response.adoc[] + +==== + +.❌ Case 4: 좋아요한 적 없는 팀에 취소 요청 + +[%collapsible] + +==== + +include::{snippets}/toggle-team-like-fail-not-liked-yet/http-request.adoc[] + +include::{snippets}/toggle-team-like-fail-not-liked-yet/http-response.adoc[] + +==== + +.❌ Case 5: 투표 기간 중 좋아요 요청 + +[%collapsible] + +==== + +include::{snippets}/toggle-team-like-fail-voting-period/http-request.adoc[] + +include::{snippets}/toggle-team-like-fail-voting-period/http-response.adoc[] + +==== diff --git a/src/test/java/com/opus/opus/restdocs/RestDocsTest.java b/src/test/java/com/opus/opus/restdocs/RestDocsTest.java index 540299fd..89a9002d 100644 --- a/src/test/java/com/opus/opus/restdocs/RestDocsTest.java +++ b/src/test/java/com/opus/opus/restdocs/RestDocsTest.java @@ -22,6 +22,7 @@ import com.opus.opus.modules.notice.application.NoticeQueryService; import com.opus.opus.modules.team.api.TeamController; import com.opus.opus.modules.team.application.TeamCommandService; +import com.opus.opus.modules.team.application.TeamLikeCommandService; import com.opus.opus.modules.team.application.TeamQueryService; import com.opus.opus.modules.team.api.TeamMemberController; import com.opus.opus.modules.team.application.TeamMemberCommandService; @@ -85,6 +86,9 @@ public abstract class RestDocsTest extends ApiTestHelper { @MockitoBean protected ContestQueryService contestQueryService; + @MockitoBean + protected TeamLikeCommandService teamLikeCommandService; + // Setting @Autowired protected WebApplicationContext context; diff --git a/src/test/java/com/opus/opus/restdocs/docs/TeamLikeApiDocsTest.java b/src/test/java/com/opus/opus/restdocs/docs/TeamLikeApiDocsTest.java new file mode 100644 index 00000000..0b952fab --- /dev/null +++ b/src/test/java/com/opus/opus/restdocs/docs/TeamLikeApiDocsTest.java @@ -0,0 +1,244 @@ +package com.opus.opus.restdocs.docs; + +import static com.opus.opus.modules.contest.exception.ContestExceptionType.NOT_ALLOWED_DURING_VOTING_PERIOD; +import static com.opus.opus.modules.team.exception.TeamExceptionType.NOT_FOUND_TEAM; +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_LIKED; +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_UNLIKED; +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.NOT_LIKED_YET; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willThrow; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.test.util.ReflectionTestUtils.setField; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.opus.opus.member.MemberFixture; +import com.opus.opus.modules.contest.exception.ContestException; +import com.opus.opus.modules.member.domain.Member; +import com.opus.opus.modules.team.application.dto.request.TeamLikeToggleRequest; +import com.opus.opus.modules.team.application.dto.response.TeamLikeToggleResponse; +import com.opus.opus.modules.team.exception.TeamException; +import com.opus.opus.modules.team.exception.TeamLikeException; +import com.opus.opus.restdocs.RestDocsTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; + +public class TeamLikeApiDocsTest extends RestDocsTest { + + private static final String MEMBER_TOKEN = "Bearer member.access.token"; + + private Member member; + + @BeforeEach + void setUp() { + member = MemberFixture.createMember(); + setField(member, "id", 1L); + } + + @Test + @DisplayName("[성공] 팀에 좋아요를 등록한다.") + void 팀에_좋아요를_등록한다() throws Exception { + final TeamLikeToggleRequest request = new TeamLikeToggleRequest(true); + final TeamLikeToggleResponse response = new TeamLikeToggleResponse(1L, true, "좋아요가 등록되었습니다."); + + given(teamLikeCommandService.toggleLike(any(), any(), any())).willReturn(response); + + mockMvc.perform(put("/teams/{teamId}/likes", 1) + .header(HttpHeaders.AUTHORIZATION, MEMBER_TOKEN) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andDo(document("toggle-team-like", + pathParameters( + parameterWithName("teamId").description("좋아요할 팀의 ID") + ), + requestHeaders( + headerWithName(HttpHeaders.AUTHORIZATION).description("Bearer {accessToken}") + ), + requestFields( + booleanFieldWithPath("isLiked", "좋아요 여부 (true: 등록, false: 취소)") + ), + responseFields( + numberFieldWithPath("teamId", "팀 ID"), + booleanFieldWithPath("isLiked", "좋아요 상태"), + stringFieldWithPath("message", "응답 메시지") + ) + )); + } + + @Test + @DisplayName("[성공] 팀 좋아요를 취소한다.") + void 팀_좋아요를_취소한다() throws Exception { + final TeamLikeToggleRequest request = new TeamLikeToggleRequest(false); + final TeamLikeToggleResponse response = new TeamLikeToggleResponse(1L, false, "좋아요가 취소되었습니다."); + + given(teamLikeCommandService.toggleLike(any(), any(), any())).willReturn(response); + + mockMvc.perform(put("/teams/{teamId}/likes", 1) + .header(HttpHeaders.AUTHORIZATION, MEMBER_TOKEN) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andDo(document("cancel-team-like", + pathParameters( + parameterWithName("teamId").description("좋아요를 취소할 팀의 ID") + ), + requestHeaders( + headerWithName(HttpHeaders.AUTHORIZATION).description("Bearer {accessToken}") + ), + requestFields( + booleanFieldWithPath("isLiked", "좋아요 여부 (true: 등록, false: 취소)") + ), + responseFields( + numberFieldWithPath("teamId", "팀 ID"), + booleanFieldWithPath("isLiked", "좋아요 상태"), + stringFieldWithPath("message", "응답 메시지") + ) + )); + } + + @Test + @DisplayName("[실패] 존재하지 않는 팀에 좋아요 시 404 에러를 반환한다.") + void 존재하지_않는_팀에_좋아요_시_에러를_반환한다() throws Exception { + final TeamLikeToggleRequest request = new TeamLikeToggleRequest(true); + + willThrow(new TeamException(NOT_FOUND_TEAM)) + .given(teamLikeCommandService) + .toggleLike(any(), any(), any()); + + mockMvc.perform(put("/teams/{teamId}/likes", 999) + .header(HttpHeaders.AUTHORIZATION, MEMBER_TOKEN) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNotFound()) + .andDo(document("toggle-team-like-fail-not-found", + pathParameters( + parameterWithName("teamId").description("존재하지 않는 팀 ID") + ), + requestHeaders( + headerWithName(HttpHeaders.AUTHORIZATION).description("Bearer {accessToken}") + ), + requestFields( + booleanFieldWithPath("isLiked", "좋아요 여부") + ) + )); + } + + @Test + @DisplayName("[실패] 이미 좋아요한 팀에 다시 좋아요 시 400 에러를 반환한다.") + void 이미_좋아요한_팀에_다시_좋아요_시_에러를_반환한다() throws Exception { + final TeamLikeToggleRequest request = new TeamLikeToggleRequest(true); + + willThrow(new TeamLikeException(ALREADY_LIKED)) + .given(teamLikeCommandService) + .toggleLike(any(), any(), any()); + + mockMvc.perform(put("/teams/{teamId}/likes", 1) + .header(HttpHeaders.AUTHORIZATION, MEMBER_TOKEN) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andDo(document("toggle-team-like-fail-already-liked", + pathParameters( + parameterWithName("teamId").description("이미 좋아요한 팀 ID") + ), + requestHeaders( + headerWithName(HttpHeaders.AUTHORIZATION).description("Bearer {accessToken}") + ), + requestFields( + booleanFieldWithPath("isLiked", "좋아요 여부") + ) + )); + } + + @Test + @DisplayName("[실패] 이미 좋아요 취소한 팀에 다시 취소 시 400 에러를 반환한다.") + void 이미_좋아요_취소한_팀에_다시_취소_시_에러를_반환한다() throws Exception { + final TeamLikeToggleRequest request = new TeamLikeToggleRequest(false); + + willThrow(new TeamLikeException(ALREADY_UNLIKED)) + .given(teamLikeCommandService) + .toggleLike(any(), any(), any()); + + mockMvc.perform(put("/teams/{teamId}/likes", 1) + .header(HttpHeaders.AUTHORIZATION, MEMBER_TOKEN) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andDo(document("toggle-team-like-fail-already-unliked", + pathParameters( + parameterWithName("teamId").description("이미 좋아요 취소한 팀 ID") + ), + requestHeaders( + headerWithName(HttpHeaders.AUTHORIZATION).description("Bearer {accessToken}") + ), + requestFields( + booleanFieldWithPath("isLiked", "좋아요 여부") + ) + )); + } + + @Test + @DisplayName("[실패] 좋아요한 적 없는 팀에 취소 요청 시 400 에러를 반환한다.") + void 좋아요한_적_없는_팀에_취소_요청_시_에러를_반환한다() throws Exception { + final TeamLikeToggleRequest request = new TeamLikeToggleRequest(false); + + willThrow(new TeamLikeException(NOT_LIKED_YET)) + .given(teamLikeCommandService) + .toggleLike(any(), any(), any()); + + mockMvc.perform(put("/teams/{teamId}/likes", 1) + .header(HttpHeaders.AUTHORIZATION, MEMBER_TOKEN) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andDo(document("toggle-team-like-fail-not-liked-yet", + pathParameters( + parameterWithName("teamId").description("좋아요한 적 없는 팀 ID") + ), + requestHeaders( + headerWithName(HttpHeaders.AUTHORIZATION).description("Bearer {accessToken}") + ), + requestFields( + booleanFieldWithPath("isLiked", "좋아요 여부") + ) + )); + } + + @Test + @DisplayName("[실패] 투표 기간에 좋아요 시 400 에러를 반환한다.") + void 투표_기간에_좋아요_시_에러를_반환한다() throws Exception { + final TeamLikeToggleRequest request = new TeamLikeToggleRequest(true); + + willThrow(new ContestException(NOT_ALLOWED_DURING_VOTING_PERIOD)) + .given(teamLikeCommandService) + .toggleLike(any(), any(), any()); + + mockMvc.perform(put("/teams/{teamId}/likes", 1) + .header(HttpHeaders.AUTHORIZATION, MEMBER_TOKEN) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andDo(document("toggle-team-like-fail-voting-period", + pathParameters( + parameterWithName("teamId").description("좋아요하려는 팀 ID") + ), + requestHeaders( + headerWithName(HttpHeaders.AUTHORIZATION).description("Bearer {accessToken}") + ), + requestFields( + booleanFieldWithPath("isLiked", "좋아요 여부") + ) + )); + } +} From f2354a86f4be90f42210e7c7354472b4846cc421 Mon Sep 17 00:00:00 2001 From: pykido Date: Sun, 1 Feb 2026 00:09:08 +0900 Subject: [PATCH 07/15] =?UTF-8?q?refactor=20:=20=EA=B0=9C=ED=96=89=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../opus/modules/team/application/TeamLikeCommandService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java b/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java index f335de08..220b9cbc 100644 --- a/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java +++ b/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java @@ -27,6 +27,7 @@ public class TeamLikeCommandService { private final TeamConvenience teamConvenience; private final ContestConvenience contestConvenience; + private final TeamLikeRepository teamLikeRepository; public TeamLikeToggleResponse toggleLike(Long memberId, Long teamId, Boolean isLiked) { From 32e7bb31bb221ca9c23fccfb65852f4861c008ed Mon Sep 17 00:00:00 2001 From: pykido Date: Sun, 8 Feb 2026 20:24:45 +0900 Subject: [PATCH 08/15] =?UTF-8?q?refactor=20:=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=9C=A0=EB=8B=88=ED=81=AC=20=EC=A0=9C=EC=95=BD?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/schema.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 048602f9..c6a9c7b1 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -151,7 +151,8 @@ CREATE TABLE `team_like` ( `is_liked` bit(1) NOT NULL, `member_id` bigint NOT NULL, `team_id` bigint NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + UNIQUE KEY `uk_team_like_member_team` (`member_id`, `team_id`) ); CREATE TABLE `team_member` ( From 8279062173fa7eada45e15b7fe4d73e579d85ec9 Mon Sep 17 00:00:00 2001 From: pykido Date: Sun, 8 Feb 2026 20:39:47 +0900 Subject: [PATCH 09/15] =?UTF-8?q?docs=20:=20.adoc=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../opus/opus/docs/asciidoc/team-like.adoc | 39 +++++-------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/opus/opus/docs/asciidoc/team-like.adoc b/src/main/java/com/opus/opus/docs/asciidoc/team-like.adoc index e42c4b0f..43d3a97c 100644 --- a/src/main/java/com/opus/opus/docs/asciidoc/team-like.adoc +++ b/src/main/java/com/opus/opus/docs/asciidoc/team-like.adoc @@ -21,7 +21,6 @@ link:./opus.html[API 목록으로 돌아가기] * `isLiked: true` → 좋아요 등록 * `isLiked: false` → 좋아요 취소 - NOTE: 좋아요를 통해 팀을 찜할 수 있습니다. NOTE: 투표 기간에는 투표만 가능하고, 투표 기간이 아닐 때만 좋아요가 가능합니다. @@ -32,13 +31,21 @@ include::{snippets}/toggle-team-like/path-parameters.adoc[] .HTTP Request Headers include::{snippets}/toggle-team-like/request-headers.adoc[] +.HTTP Request +include::{snippets}/toggle-team-like/http-request.adoc[] + +.HTTP Response +include::{snippets}/toggle-team-like/http-response.adoc[] + .Request Fields include::{snippets}/toggle-team-like/request-fields.adoc[] .Response Fields include::{snippets}/toggle-team-like/response-fields.adoc[] -=== 시나리오 1: TeamLike 데이터가 없는 경우 +=== 시나리오별 응답 + +==== 시나리오 1: TeamLike 데이터가 없는 경우 특정 멤버가 특정 팀에 대해 좋아요 API를 처음 호출하면, TeamLike 테이블에 데이터가 새로 생성됩니다. @@ -55,13 +62,7 @@ include::{snippets}/toggle-team-like/response-fields.adoc[] |400 Bad Request |=== -isLiked: true → 좋아요 등록 - -include::{snippets}/toggle-team-like/http-request.adoc[] - -include::{snippets}/toggle-team-like/http-response.adoc[] - -=== 시나리오 2: TeamLike 데이터가 있는 경우 +==== 시나리오 2: TeamLike 데이터가 있는 경우 이미 해당 팀에 대한 좋아요 기록이 있는 경우, 상태에 따라 토글됩니다. @@ -90,70 +91,50 @@ include::{snippets}/toggle-team-like/http-response.adoc[] |400 Bad Request |=== -좋아요 취소 (isLiked: true → false) - -include::{snippets}/cancel-team-like/http-request.adoc[] - -include::{snippets}/cancel-team-like/http-response.adoc[] === ⚠️ 실패 케이스 .❌ Case 1: 존재하지 않는 팀 [%collapsible] - ==== - include::{snippets}/toggle-team-like-fail-not-found/http-request.adoc[] include::{snippets}/toggle-team-like-fail-not-found/http-response.adoc[] - ==== .❌ Case 2: 이미 좋아요한 팀 [%collapsible] - ==== - include::{snippets}/toggle-team-like-fail-already-liked/http-request.adoc[] include::{snippets}/toggle-team-like-fail-already-liked/http-response.adoc[] - ==== .❌ Case 3: 이미 좋아요 취소한 팀 [%collapsible] - ==== - include::{snippets}/toggle-team-like-fail-already-unliked/http-request.adoc[] include::{snippets}/toggle-team-like-fail-already-unliked/http-response.adoc[] - ==== .❌ Case 4: 좋아요한 적 없는 팀에 취소 요청 [%collapsible] - ==== - include::{snippets}/toggle-team-like-fail-not-liked-yet/http-request.adoc[] include::{snippets}/toggle-team-like-fail-not-liked-yet/http-response.adoc[] - ==== .❌ Case 5: 투표 기간 중 좋아요 요청 [%collapsible] - ==== - include::{snippets}/toggle-team-like-fail-voting-period/http-request.adoc[] include::{snippets}/toggle-team-like-fail-voting-period/http-response.adoc[] - ==== From 097e33f3d75c76bf547307dbf972ac5e45813440 Mon Sep 17 00:00:00 2001 From: pykido Date: Mon, 9 Feb 2026 15:51:19 +0900 Subject: [PATCH 10/15] =?UTF-8?q?refactor=20:=20TeamLike=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=A0=80=EC=9E=A5=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/TeamLikeCommandService.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java b/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java index 220b9cbc..70111c18 100644 --- a/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java +++ b/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java @@ -47,7 +47,7 @@ private TeamLikeToggleResponse handleFirstTimeLike(Long memberId, Team team, Boo throw new TeamLikeException(NOT_LIKED_YET); } - saveTeamLike(memberId, team, true); + saveTeamLike(memberId, team); return TeamLikeToggleResponse.of(team.getId(), true, "좋아요가 등록되었습니다."); } @@ -61,16 +61,11 @@ private TeamLikeToggleResponse handleExistingLike(final TeamLike teamLike, final return TeamLikeToggleResponse.of(teamLike.getTeam().getId(), isLiked, isLiked ? "좋아요가 등록되었습니다." : "좋아요가 취소되었습니다."); } - private void saveTeamLike(Long memberId, Team team, Boolean isLiked) { - try { - teamLikeRepository.save(TeamLike.builder() - .memberId(memberId) - .team(team) - .isLiked(isLiked) - .build()); - teamLikeRepository.flush(); - } catch (DataIntegrityViolationException e) { - throw new TeamLikeException(DUPLICATE_LIKE_REQUEST); - } + private void saveTeamLike(Long memberId, Team team) { + teamLikeRepository.save(TeamLike.builder() + .memberId(memberId) + .team(team) + .isLiked(true) + .build()); } } From 30c0d623597fc7b20c4dc8af89e113147aa8af72 Mon Sep 17 00:00:00 2001 From: pykido Date: Mon, 9 Feb 2026 15:55:06 +0900 Subject: [PATCH 11/15] =?UTF-8?q?refactor=20:=20final=20=EB=88=84=EB=9D=BD?= =?UTF-8?q?=EB=90=9C=20=EB=B6=80=EB=B6=84=20=EB=B6=99=EC=9D=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../opus/modules/team/api/TeamController.java | 8 ++++---- .../team/application/TeamLikeCommandService.java | 12 ++++++------ .../application/TeamLikeCommandServiceTest.java | 16 ++++++++-------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/opus/opus/modules/team/api/TeamController.java b/src/main/java/com/opus/opus/modules/team/api/TeamController.java index 2374fa43..f51ae1cd 100644 --- a/src/main/java/com/opus/opus/modules/team/api/TeamController.java +++ b/src/main/java/com/opus/opus/modules/team/api/TeamController.java @@ -113,10 +113,10 @@ public ResponseEntity deletePosterImage(@PathVariable final Long teamId) { @Secured({"ROLE_회원", "ROLE_관리자"}) @PutMapping("/{teamId}/likes") - public ResponseEntity toggleLike(@PathVariable Long teamId, - @RequestBody @Valid TeamLikeToggleRequest request, - @LoginMember Member member) { - TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), teamId, request.isLiked()); + public ResponseEntity toggleLike(@PathVariable final Long teamId, + @RequestBody @Valid final TeamLikeToggleRequest request, + @LoginMember final Member member) { + final TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), teamId, request.isLiked()); return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java b/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java index 70111c18..578c7db1 100644 --- a/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java +++ b/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java @@ -30,19 +30,19 @@ public class TeamLikeCommandService { private final TeamLikeRepository teamLikeRepository; - public TeamLikeToggleResponse toggleLike(Long memberId, Long teamId, Boolean isLiked) { - Team team = teamConvenience.getValidateExistTeam(teamId); - Contest contest = contestConvenience.getValidateExistContest(team.getContestId()); + public TeamLikeToggleResponse toggleLike(final Long memberId, final Long teamId, final Boolean isLiked) { + final Team team = teamConvenience.getValidateExistTeam(teamId); + final Contest contest = contestConvenience.getValidateExistContest(team.getContestId()); contestConvenience.validateNotInVotingPeriod(contest); - Optional teamLikeOptional = teamLikeRepository.findByMemberIdAndTeam(memberId, team); + final Optional teamLikeOptional = teamLikeRepository.findByMemberIdAndTeam(memberId, team); return teamLikeOptional .map(teamLike -> handleExistingLike(teamLike, isLiked)) .orElseGet(() -> handleFirstTimeLike(memberId, team, isLiked)); } - private TeamLikeToggleResponse handleFirstTimeLike(Long memberId, Team team, Boolean isLiked) { + private TeamLikeToggleResponse handleFirstTimeLike(final Long memberId, final Team team, final Boolean isLiked) { if (!isLiked) { throw new TeamLikeException(NOT_LIKED_YET); } @@ -61,7 +61,7 @@ private TeamLikeToggleResponse handleExistingLike(final TeamLike teamLike, final return TeamLikeToggleResponse.of(teamLike.getTeam().getId(), isLiked, isLiked ? "좋아요가 등록되었습니다." : "좋아요가 취소되었습니다."); } - private void saveTeamLike(Long memberId, Team team) { + private void saveTeamLike(final Long memberId, final Team team) { teamLikeRepository.save(TeamLike.builder() .memberId(memberId) .team(team) diff --git a/src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java b/src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java index 6315d84d..971a788e 100644 --- a/src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java +++ b/src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java @@ -52,7 +52,7 @@ public class TeamLikeCommandServiceTest extends IntegrationTest { @BeforeEach void setUp() { - Contest newContest = ContestFixture.createContest(); + final Contest newContest = ContestFixture.createContest(); newContest.updateVotePeriod(LocalDateTime.now().minusDays(10), LocalDateTime.now().minusDays(5)); contest = contestRepository.save(newContest); @@ -63,13 +63,13 @@ void setUp() { @Test @DisplayName("[성공] 처음 좋아요하면 TeamLike가 생성되고 좋아요가 등록된다.") void 처음_좋아요하면_TeamLike가_생성되고_좋아요가_등록된다() { - TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), team.getId(), true); + final TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), team.getId(), true); assertThat(response.teamId()).isEqualTo(team.getId()); assertThat(response.isLiked()).isTrue(); assertThat(response.message()).isEqualTo("좋아요가 등록되었습니다."); - TeamLike savedLike = teamLikeRepository.findByMemberIdAndTeam(member.getId(), team).orElseThrow(); + final TeamLike savedLike = teamLikeRepository.findByMemberIdAndTeam(member.getId(), team).orElseThrow(); assertThat(savedLike.getIsLiked()).isTrue(); } @@ -78,7 +78,7 @@ void setUp() { void 기존_좋아요를_취소할_수_있다() { teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), true)); - TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), team.getId(), false); + final TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), team.getId(), false); assertThat(response.isLiked()).isFalse(); assertThat(response.message()).isEqualTo("좋아요가 취소되었습니다."); @@ -89,7 +89,7 @@ void setUp() { void 취소한_좋아요를_다시_등록할_수_있다() { teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), false)); - TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), team.getId(), true); + final TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), team.getId(), true); assertThat(response.isLiked()).isTrue(); assertThat(response.message()).isEqualTo("좋아요가 등록되었습니다."); @@ -136,11 +136,11 @@ void setUp() { @Test @DisplayName("[실패] 투표 기간에는 좋아요할 수 없다.") void 투표_기간에는_좋아요할_수_없다() { - Contest votingContest = ContestFixture.createContest(); + final Contest votingContest = ContestFixture.createContest(); votingContest.updateVotePeriod(LocalDateTime.now().minusDays(1), LocalDateTime.now().plusDays(1)); - votingContest = contestRepository.save(votingContest); + final Contest savedVotingContest = contestRepository.save(votingContest); - Team votingTeam = teamRepository.save(TeamFixture.createTeamWithContestId(votingContest.getId())); + final Team votingTeam = teamRepository.save(TeamFixture.createTeamWithContestId(savedVotingContest.getId())); assertThatThrownBy(() -> teamLikeCommandService.toggleLike(member.getId(), votingTeam.getId(), true)) .isInstanceOf(ContestException.class) From cdce7bea18cc9beb7226f0547b6f638ec9fa920f Mon Sep 17 00:00:00 2001 From: pykido Date: Mon, 9 Feb 2026 22:45:50 +0900 Subject: [PATCH 12/15] =?UTF-8?q?refactor=20:=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC,=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=ED=86=B5=ED=95=A9=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../opus/modules/team/api/TeamController.java | 4 +- .../team/application/TeamCommandService.java | 54 +++++++ .../application/TeamLikeCommandService.java | 71 --------- .../com/opus/opus/restdocs/RestDocsTest.java | 4 - .../restdocs/docs/TeamLikeApiDocsTest.java | 14 +- .../application/TeamCommandServiceTest.java | 121 +++++++++++++- .../TeamLikeCommandServiceTest.java | 149 ------------------ 7 files changed, 181 insertions(+), 236 deletions(-) delete mode 100644 src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java delete mode 100644 src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java diff --git a/src/main/java/com/opus/opus/modules/team/api/TeamController.java b/src/main/java/com/opus/opus/modules/team/api/TeamController.java index f51ae1cd..38d45c13 100644 --- a/src/main/java/com/opus/opus/modules/team/api/TeamController.java +++ b/src/main/java/com/opus/opus/modules/team/api/TeamController.java @@ -3,7 +3,6 @@ import com.opus.opus.global.security.annotation.LoginMember; import com.opus.opus.modules.member.domain.Member; import com.opus.opus.modules.team.application.TeamCommandService; -import com.opus.opus.modules.team.application.TeamLikeCommandService; import com.opus.opus.modules.team.application.TeamQueryService; import com.opus.opus.modules.team.application.dto.ImageResponse; import com.opus.opus.modules.team.application.dto.request.PreviewDeleteRequest; @@ -36,7 +35,6 @@ public class TeamController { private final TeamQueryService teamQueryService; private final TeamCommandService teamCommandService; - private final TeamLikeCommandService teamLikeCommandService; @GetMapping("/{teamId}/image/{imageId}") public ResponseEntity getPreviewImage(@PathVariable final Long teamId, @PathVariable final Long imageId) { @@ -116,7 +114,7 @@ public ResponseEntity deletePosterImage(@PathVariable final Long teamId) { public ResponseEntity toggleLike(@PathVariable final Long teamId, @RequestBody @Valid final TeamLikeToggleRequest request, @LoginMember final Member member) { - final TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), teamId, request.isLiked()); + final TeamLikeToggleResponse response = teamCommandService.toggleLike(member.getId(), teamId, request.isLiked()); return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/opus/opus/modules/team/application/TeamCommandService.java b/src/main/java/com/opus/opus/modules/team/application/TeamCommandService.java index 194f4009..ded64d56 100644 --- a/src/main/java/com/opus/opus/modules/team/application/TeamCommandService.java +++ b/src/main/java/com/opus/opus/modules/team/application/TeamCommandService.java @@ -6,14 +6,26 @@ import static com.opus.opus.modules.file.domain.ReferenceDomainType.TEAM; import static com.opus.opus.modules.file.exception.FileExceptionType.EXCEED_PREVIEW_LIMIT; import static com.opus.opus.modules.file.exception.FileExceptionType.NOT_WEBP_CONVERTED; +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_LIKED; +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_UNLIKED; +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.NOT_LIKED_YET; import com.opus.opus.global.util.FileStorageUtil; +import com.opus.opus.modules.contest.application.convenience.ContestConvenience; +import com.opus.opus.modules.contest.domain.Contest; import com.opus.opus.modules.file.domain.File; import com.opus.opus.modules.file.domain.FileImageType; import com.opus.opus.modules.file.domain.dao.FileRepository; import com.opus.opus.modules.file.exception.FileException; import com.opus.opus.modules.team.application.convenience.TeamConvenience; +import com.opus.opus.modules.team.application.dto.response.TeamLikeToggleResponse; +import com.opus.opus.modules.team.domain.Team; +import com.opus.opus.modules.team.domain.TeamLike; +import com.opus.opus.modules.team.domain.dao.TeamLikeRepository; +import com.opus.opus.modules.team.exception.TeamLikeException; import java.util.List; +import java.util.Objects; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -28,6 +40,9 @@ public class TeamCommandService { private final FileRepository fileRepository; private final TeamConvenience teamConvenience; + private final ContestConvenience contestConvenience; + + private final TeamLikeRepository teamLikeRepository; public void savePreviewImages(final Long teamId, final List images) { @@ -65,6 +80,45 @@ public void deletePosterImage(final Long teamId) { deleteIfExists(teamId, POSTER); } + public TeamLikeToggleResponse toggleLike(final Long memberId, final Long teamId, final Boolean isLiked) { + final Team team = teamConvenience.getValidateExistTeam(teamId); + final Contest contest = contestConvenience.getValidateExistContest(team.getContestId()); + + contestConvenience.validateNotInVotingPeriod(contest); + + final Optional teamLikeOptional = teamLikeRepository.findByMemberIdAndTeam(memberId, team); + return teamLikeOptional + .map(teamLike -> handleExistingLike(teamLike, isLiked)) + .orElseGet(() -> handleFirstTimeLike(memberId, team, isLiked)); + } + + private TeamLikeToggleResponse handleFirstTimeLike(final Long memberId, final Team team, final Boolean isLiked) { + if (!isLiked) { + throw new TeamLikeException(NOT_LIKED_YET); + } + + saveTeamLike(memberId, team); + return TeamLikeToggleResponse.of(team.getId(), true, "좋아요가 등록되었습니다."); + } + + private TeamLikeToggleResponse handleExistingLike(final TeamLike teamLike, final Boolean isLiked) { + if (Objects.equals(teamLike.getIsLiked(), isLiked)) { + throw new TeamLikeException(isLiked ? ALREADY_LIKED : ALREADY_UNLIKED); + } + + teamLike.updateIsLiked(isLiked); + + return TeamLikeToggleResponse.of(teamLike.getTeam().getId(), isLiked, isLiked ? "좋아요가 등록되었습니다." : "좋아요가 취소되었습니다."); + } + + private void saveTeamLike(final Long memberId, final Team team) { + teamLikeRepository.save(TeamLike.builder() + .memberId(memberId) + .team(team) + .isLiked(true) + .build()); + } + private void deleteIfExists(final Long teamId, final FileImageType imageType) { fileRepository.findByReferenceIdAndReferenceTypeAndImageType(teamId, TEAM, imageType) .ifPresent(existingFile -> fileStorageUtil.deleteFile(existingFile.getId())); diff --git a/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java b/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java deleted file mode 100644 index 578c7db1..00000000 --- a/src/main/java/com/opus/opus/modules/team/application/TeamLikeCommandService.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.opus.opus.modules.team.application; - -import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_LIKED; -import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_UNLIKED; -import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.DUPLICATE_LIKE_REQUEST; -import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.NOT_LIKED_YET; - -import com.opus.opus.modules.contest.application.convenience.ContestConvenience; -import com.opus.opus.modules.contest.domain.Contest; -import com.opus.opus.modules.team.application.convenience.TeamConvenience; -import com.opus.opus.modules.team.application.dto.response.TeamLikeToggleResponse; -import com.opus.opus.modules.team.domain.Team; -import com.opus.opus.modules.team.domain.TeamLike; -import com.opus.opus.modules.team.domain.dao.TeamLikeRepository; -import com.opus.opus.modules.team.exception.TeamLikeException; -import java.util.Objects; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional -public class TeamLikeCommandService { - - private final TeamConvenience teamConvenience; - private final ContestConvenience contestConvenience; - - private final TeamLikeRepository teamLikeRepository; - - public TeamLikeToggleResponse toggleLike(final Long memberId, final Long teamId, final Boolean isLiked) { - final Team team = teamConvenience.getValidateExistTeam(teamId); - final Contest contest = contestConvenience.getValidateExistContest(team.getContestId()); - - contestConvenience.validateNotInVotingPeriod(contest); - - final Optional teamLikeOptional = teamLikeRepository.findByMemberIdAndTeam(memberId, team); - return teamLikeOptional - .map(teamLike -> handleExistingLike(teamLike, isLiked)) - .orElseGet(() -> handleFirstTimeLike(memberId, team, isLiked)); - } - - private TeamLikeToggleResponse handleFirstTimeLike(final Long memberId, final Team team, final Boolean isLiked) { - if (!isLiked) { - throw new TeamLikeException(NOT_LIKED_YET); - } - - saveTeamLike(memberId, team); - return TeamLikeToggleResponse.of(team.getId(), true, "좋아요가 등록되었습니다."); - } - - private TeamLikeToggleResponse handleExistingLike(final TeamLike teamLike, final Boolean isLiked) { - if (Objects.equals(teamLike.getIsLiked(), isLiked)) { - throw new TeamLikeException(isLiked ? ALREADY_LIKED : ALREADY_UNLIKED); - } - - teamLike.updateIsLiked(isLiked); - - return TeamLikeToggleResponse.of(teamLike.getTeam().getId(), isLiked, isLiked ? "좋아요가 등록되었습니다." : "좋아요가 취소되었습니다."); - } - - private void saveTeamLike(final Long memberId, final Team team) { - teamLikeRepository.save(TeamLike.builder() - .memberId(memberId) - .team(team) - .isLiked(true) - .build()); - } -} diff --git a/src/test/java/com/opus/opus/restdocs/RestDocsTest.java b/src/test/java/com/opus/opus/restdocs/RestDocsTest.java index 89a9002d..540299fd 100644 --- a/src/test/java/com/opus/opus/restdocs/RestDocsTest.java +++ b/src/test/java/com/opus/opus/restdocs/RestDocsTest.java @@ -22,7 +22,6 @@ import com.opus.opus.modules.notice.application.NoticeQueryService; import com.opus.opus.modules.team.api.TeamController; import com.opus.opus.modules.team.application.TeamCommandService; -import com.opus.opus.modules.team.application.TeamLikeCommandService; import com.opus.opus.modules.team.application.TeamQueryService; import com.opus.opus.modules.team.api.TeamMemberController; import com.opus.opus.modules.team.application.TeamMemberCommandService; @@ -86,9 +85,6 @@ public abstract class RestDocsTest extends ApiTestHelper { @MockitoBean protected ContestQueryService contestQueryService; - @MockitoBean - protected TeamLikeCommandService teamLikeCommandService; - // Setting @Autowired protected WebApplicationContext context; diff --git a/src/test/java/com/opus/opus/restdocs/docs/TeamLikeApiDocsTest.java b/src/test/java/com/opus/opus/restdocs/docs/TeamLikeApiDocsTest.java index 0b952fab..83450802 100644 --- a/src/test/java/com/opus/opus/restdocs/docs/TeamLikeApiDocsTest.java +++ b/src/test/java/com/opus/opus/restdocs/docs/TeamLikeApiDocsTest.java @@ -51,7 +51,7 @@ void setUp() { final TeamLikeToggleRequest request = new TeamLikeToggleRequest(true); final TeamLikeToggleResponse response = new TeamLikeToggleResponse(1L, true, "좋아요가 등록되었습니다."); - given(teamLikeCommandService.toggleLike(any(), any(), any())).willReturn(response); + given(teamCommandService.toggleLike(any(), any(), any())).willReturn(response); mockMvc.perform(put("/teams/{teamId}/likes", 1) .header(HttpHeaders.AUTHORIZATION, MEMBER_TOKEN) @@ -82,7 +82,7 @@ void setUp() { final TeamLikeToggleRequest request = new TeamLikeToggleRequest(false); final TeamLikeToggleResponse response = new TeamLikeToggleResponse(1L, false, "좋아요가 취소되었습니다."); - given(teamLikeCommandService.toggleLike(any(), any(), any())).willReturn(response); + given(teamCommandService.toggleLike(any(), any(), any())).willReturn(response); mockMvc.perform(put("/teams/{teamId}/likes", 1) .header(HttpHeaders.AUTHORIZATION, MEMBER_TOKEN) @@ -113,7 +113,7 @@ void setUp() { final TeamLikeToggleRequest request = new TeamLikeToggleRequest(true); willThrow(new TeamException(NOT_FOUND_TEAM)) - .given(teamLikeCommandService) + .given(teamCommandService) .toggleLike(any(), any(), any()); mockMvc.perform(put("/teams/{teamId}/likes", 999) @@ -140,7 +140,7 @@ void setUp() { final TeamLikeToggleRequest request = new TeamLikeToggleRequest(true); willThrow(new TeamLikeException(ALREADY_LIKED)) - .given(teamLikeCommandService) + .given(teamCommandService) .toggleLike(any(), any(), any()); mockMvc.perform(put("/teams/{teamId}/likes", 1) @@ -167,7 +167,7 @@ void setUp() { final TeamLikeToggleRequest request = new TeamLikeToggleRequest(false); willThrow(new TeamLikeException(ALREADY_UNLIKED)) - .given(teamLikeCommandService) + .given(teamCommandService) .toggleLike(any(), any(), any()); mockMvc.perform(put("/teams/{teamId}/likes", 1) @@ -194,7 +194,7 @@ void setUp() { final TeamLikeToggleRequest request = new TeamLikeToggleRequest(false); willThrow(new TeamLikeException(NOT_LIKED_YET)) - .given(teamLikeCommandService) + .given(teamCommandService) .toggleLike(any(), any(), any()); mockMvc.perform(put("/teams/{teamId}/likes", 1) @@ -221,7 +221,7 @@ void setUp() { final TeamLikeToggleRequest request = new TeamLikeToggleRequest(true); willThrow(new ContestException(NOT_ALLOWED_DURING_VOTING_PERIOD)) - .given(teamLikeCommandService) + .given(teamCommandService) .toggleLike(any(), any(), any()); mockMvc.perform(put("/teams/{teamId}/likes", 1) diff --git a/src/test/java/com/opus/opus/team/application/TeamCommandServiceTest.java b/src/test/java/com/opus/opus/team/application/TeamCommandServiceTest.java index 16580696..3d1afdf7 100644 --- a/src/test/java/com/opus/opus/team/application/TeamCommandServiceTest.java +++ b/src/test/java/com/opus/opus/team/application/TeamCommandServiceTest.java @@ -1,8 +1,13 @@ package com.opus.opus.team.application; +import static com.opus.opus.modules.contest.exception.ContestExceptionType.NOT_ALLOWED_DURING_VOTING_PERIOD; import static com.opus.opus.modules.file.domain.FileImageType.POSTER; import static com.opus.opus.modules.file.domain.ReferenceDomainType.TEAM; import static com.opus.opus.modules.team.exception.TeamExceptionType.NOT_FOUND_TEAM; +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_LIKED; +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_UNLIKED; +import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.NOT_LIKED_YET; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -11,16 +16,29 @@ import static org.mockito.Mockito.verify; import static org.springframework.test.util.ReflectionTestUtils.setField; +import com.opus.opus.contest.ContestFixture; import com.opus.opus.global.util.FileStorageUtil; import com.opus.opus.helper.IntegrationTest; +import com.opus.opus.member.MemberFixture; +import com.opus.opus.modules.contest.domain.Contest; +import com.opus.opus.modules.contest.domain.dao.ContestRepository; +import com.opus.opus.modules.contest.exception.ContestException; import com.opus.opus.modules.file.domain.File; import com.opus.opus.modules.file.domain.dao.FileRepository; +import com.opus.opus.modules.member.domain.Member; +import com.opus.opus.modules.member.domain.dao.MemberRepository; import com.opus.opus.modules.team.application.TeamCommandService; +import com.opus.opus.modules.team.application.dto.response.TeamLikeToggleResponse; import com.opus.opus.modules.team.domain.Team; +import com.opus.opus.modules.team.domain.TeamLike; +import com.opus.opus.modules.team.domain.dao.TeamLikeRepository; import com.opus.opus.modules.team.domain.dao.TeamRepository; import com.opus.opus.modules.team.exception.TeamException; +import com.opus.opus.modules.team.exception.TeamLikeException; import com.opus.opus.team.FileFixture; import com.opus.opus.team.TeamFixture; +import com.opus.opus.team.TeamLikeFixture; +import java.time.LocalDateTime; import java.util.ArrayList; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -36,15 +54,27 @@ public class TeamCommandServiceTest extends IntegrationTest { @Autowired private TeamRepository teamRepository; - + @Autowired + private MemberRepository memberRepository; + @Autowired + private TeamLikeRepository teamLikeRepository; + @Autowired + private ContestRepository contestRepository; @Autowired private FileRepository fileRepository; + private Contest contest; private Team team; + private Member member; @BeforeEach void setUp() { - team = teamRepository.save(TeamFixture.createTeam()); + final Contest newContest = ContestFixture.createContest(); + newContest.updateVotePeriod(LocalDateTime.now().minusDays(10), LocalDateTime.now().minusDays(5)); + contest = contestRepository.save(newContest); + + team = teamRepository.save(TeamFixture.createTeamWithContestId(contest.getId())); + member = memberRepository.save(MemberFixture.createMember()); } @Test @@ -120,4 +150,91 @@ void setUp() { verify(fileStorageUtil, times(1)).deleteFile(savedFile.getId()); verify(fileStorageUtil, times(1)).storeFile(any(), eq(team.getId()), eq(TEAM), eq(POSTER)); } + + @Test + @DisplayName("[성공] 처음 좋아요하면 TeamLike가 생성되고 좋아요가 등록된다.") + void 처음_좋아요하면_TeamLike가_생성되고_좋아요가_등록된다() { + final TeamLikeToggleResponse response = teamCommandService.toggleLike(member.getId(), team.getId(), true); + + assertThat(response.teamId()).isEqualTo(team.getId()); + assertThat(response.isLiked()).isTrue(); + assertThat(response.message()).isEqualTo("좋아요가 등록되었습니다."); + + final TeamLike savedLike = teamLikeRepository.findByMemberIdAndTeam(member.getId(), team).orElseThrow(); + assertThat(savedLike.getIsLiked()).isTrue(); + } + + @Test + @DisplayName("[성공] 기존 좋아요를 취소할 수 있다.") + void 기존_좋아요를_취소할_수_있다() { + teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), true)); + + final TeamLikeToggleResponse response = teamCommandService.toggleLike(member.getId(), team.getId(), false); + + assertThat(response.isLiked()).isFalse(); + assertThat(response.message()).isEqualTo("좋아요가 취소되었습니다."); + } + + @Test + @DisplayName("[성공] 취소한 좋아요를 다시 등록할 수 있다.") + void 취소한_좋아요를_다시_등록할_수_있다() { + teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), false)); + + final TeamLikeToggleResponse response = teamCommandService.toggleLike(member.getId(), team.getId(), true); + + assertThat(response.isLiked()).isTrue(); + assertThat(response.message()).isEqualTo("좋아요가 등록되었습니다."); + } + + @Test + @DisplayName("[실패] 좋아요한 적 없는 팀에 취소 요청하면 예외가 발생한다.") + void 좋아요한_적_없는_팀에_취소_요청하면_예외가_발생한다() { + assertThatThrownBy(() -> teamCommandService.toggleLike(member.getId(), team.getId(), false)) + .isInstanceOf(TeamLikeException.class) + .hasMessage(NOT_LIKED_YET.errorMessage()); + } + + @Test + @DisplayName("[실패] 이미 좋아요한 팀에 다시 좋아요하면 예외가 발생한다.") + void 이미_좋아요한_팀에_다시_좋아요하면_예외가_발생한다() { + teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), true)); + + assertThatThrownBy(() -> teamCommandService.toggleLike(member.getId(), team.getId(), true)) + .isInstanceOf(TeamLikeException.class) + .hasMessage(ALREADY_LIKED.errorMessage()); + } + + @Test + @DisplayName("[실패] 이미 좋아요 취소한 팀에 다시 취소하면 예외가 발생한다.") + void 이미_좋아요_취소한_팀에_다시_취소하면_예외가_발생한다() { + teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), false)); + + assertThatThrownBy(() -> teamCommandService.toggleLike(member.getId(), team.getId(), false)) + .isInstanceOf(TeamLikeException.class) + .hasMessage(ALREADY_UNLIKED.errorMessage()); + } + + @Test + @DisplayName("[실패] 존재하지 않는 팀에는 좋아요할 수 없다.") + void 존재하지_않는_팀에는_좋아요할_수_없다() { + final Long invalidTeamId = 999L; + + assertThatThrownBy(() -> teamCommandService.toggleLike(member.getId(), invalidTeamId, true)) + .isInstanceOf(TeamException.class) + .hasMessage(NOT_FOUND_TEAM.errorMessage()); + } + + @Test + @DisplayName("[실패] 투표 기간에는 좋아요할 수 없다.") + void 투표_기간에는_좋아요할_수_없다() { + final Contest votingContest = ContestFixture.createContest(); + votingContest.updateVotePeriod(LocalDateTime.now().minusDays(1), LocalDateTime.now().plusDays(1)); + final Contest savedVotingContest = contestRepository.save(votingContest); + + final Team votingTeam = teamRepository.save(TeamFixture.createTeamWithContestId(savedVotingContest.getId())); + + assertThatThrownBy(() -> teamCommandService.toggleLike(member.getId(), votingTeam.getId(), true)) + .isInstanceOf(ContestException.class) + .hasMessage(NOT_ALLOWED_DURING_VOTING_PERIOD.errorMessage()); + } } diff --git a/src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java b/src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java deleted file mode 100644 index 971a788e..00000000 --- a/src/test/java/com/opus/opus/team/application/TeamLikeCommandServiceTest.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.opus.opus.team.application; - -import static com.opus.opus.modules.contest.exception.ContestExceptionType.NOT_ALLOWED_DURING_VOTING_PERIOD; -import static com.opus.opus.modules.team.exception.TeamExceptionType.NOT_FOUND_TEAM; -import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_LIKED; -import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_UNLIKED; -import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.NOT_LIKED_YET; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.opus.opus.contest.ContestFixture; -import com.opus.opus.helper.IntegrationTest; -import com.opus.opus.member.MemberFixture; -import com.opus.opus.modules.contest.domain.Contest; -import com.opus.opus.modules.contest.domain.dao.ContestRepository; -import com.opus.opus.modules.contest.exception.ContestException; -import com.opus.opus.modules.member.domain.Member; -import com.opus.opus.modules.member.domain.dao.MemberRepository; -import com.opus.opus.modules.team.application.TeamLikeCommandService; -import com.opus.opus.modules.team.application.dto.response.TeamLikeToggleResponse; -import com.opus.opus.modules.team.domain.Team; -import com.opus.opus.modules.team.domain.TeamLike; -import com.opus.opus.modules.team.domain.dao.TeamLikeRepository; -import com.opus.opus.modules.team.domain.dao.TeamRepository; -import com.opus.opus.modules.team.exception.TeamException; -import com.opus.opus.modules.team.exception.TeamLikeException; -import com.opus.opus.team.TeamFixture; -import com.opus.opus.team.TeamLikeFixture; -import java.time.LocalDateTime; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -public class TeamLikeCommandServiceTest extends IntegrationTest { - - @Autowired - private TeamLikeCommandService teamLikeCommandService; - - @Autowired - private TeamRepository teamRepository; - @Autowired - private MemberRepository memberRepository; - @Autowired - private TeamLikeRepository teamLikeRepository; - @Autowired - private ContestRepository contestRepository; - - private Contest contest; - private Team team; - private Member member; - - @BeforeEach - void setUp() { - final Contest newContest = ContestFixture.createContest(); - newContest.updateVotePeriod(LocalDateTime.now().minusDays(10), LocalDateTime.now().minusDays(5)); - contest = contestRepository.save(newContest); - - team = teamRepository.save(TeamFixture.createTeamWithContestId(contest.getId())); - member = memberRepository.save(MemberFixture.createMember()); - } - - @Test - @DisplayName("[성공] 처음 좋아요하면 TeamLike가 생성되고 좋아요가 등록된다.") - void 처음_좋아요하면_TeamLike가_생성되고_좋아요가_등록된다() { - final TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), team.getId(), true); - - assertThat(response.teamId()).isEqualTo(team.getId()); - assertThat(response.isLiked()).isTrue(); - assertThat(response.message()).isEqualTo("좋아요가 등록되었습니다."); - - final TeamLike savedLike = teamLikeRepository.findByMemberIdAndTeam(member.getId(), team).orElseThrow(); - assertThat(savedLike.getIsLiked()).isTrue(); - } - - @Test - @DisplayName("[성공] 기존 좋아요를 취소할 수 있다.") - void 기존_좋아요를_취소할_수_있다() { - teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), true)); - - final TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), team.getId(), false); - - assertThat(response.isLiked()).isFalse(); - assertThat(response.message()).isEqualTo("좋아요가 취소되었습니다."); - } - - @Test - @DisplayName("[성공] 취소한 좋아요를 다시 등록할 수 있다.") - void 취소한_좋아요를_다시_등록할_수_있다() { - teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), false)); - - final TeamLikeToggleResponse response = teamLikeCommandService.toggleLike(member.getId(), team.getId(), true); - - assertThat(response.isLiked()).isTrue(); - assertThat(response.message()).isEqualTo("좋아요가 등록되었습니다."); - } - - @Test - @DisplayName("[실패] 좋아요한 적 없는 팀에 취소 요청하면 예외가 발생한다.") - void 좋아요한_적_없는_팀에_취소_요청하면_예외가_발생한다() { - assertThatThrownBy(() -> teamLikeCommandService.toggleLike(member.getId(), team.getId(), false)) - .isInstanceOf(TeamLikeException.class) - .hasMessage(NOT_LIKED_YET.errorMessage()); - } - - @Test - @DisplayName("[실패] 이미 좋아요한 팀에 다시 좋아요하면 예외가 발생한다.") - void 이미_좋아요한_팀에_다시_좋아요하면_예외가_발생한다() { - teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), true)); - - assertThatThrownBy(() -> teamLikeCommandService.toggleLike(member.getId(), team.getId(), true)) - .isInstanceOf(TeamLikeException.class) - .hasMessage(ALREADY_LIKED.errorMessage()); - } - - @Test - @DisplayName("[실패] 이미 좋아요 취소한 팀에 다시 취소하면 예외가 발생한다.") - void 이미_좋아요_취소한_팀에_다시_취소하면_예외가_발생한다() { - teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), false)); - - assertThatThrownBy(() -> teamLikeCommandService.toggleLike(member.getId(), team.getId(), false)) - .isInstanceOf(TeamLikeException.class) - .hasMessage(ALREADY_UNLIKED.errorMessage()); - } - - @Test - @DisplayName("[실패] 존재하지 않는 팀에는 좋아요할 수 없다.") - void 존재하지_않는_팀에는_좋아요할_수_없다() { - final Long invalidTeamId = 999L; - - assertThatThrownBy(() -> teamLikeCommandService.toggleLike(member.getId(), invalidTeamId, true)) - .isInstanceOf(TeamException.class) - .hasMessage(NOT_FOUND_TEAM.errorMessage()); - } - - @Test - @DisplayName("[실패] 투표 기간에는 좋아요할 수 없다.") - void 투표_기간에는_좋아요할_수_없다() { - final Contest votingContest = ContestFixture.createContest(); - votingContest.updateVotePeriod(LocalDateTime.now().minusDays(1), LocalDateTime.now().plusDays(1)); - final Contest savedVotingContest = contestRepository.save(votingContest); - - final Team votingTeam = teamRepository.save(TeamFixture.createTeamWithContestId(savedVotingContest.getId())); - - assertThatThrownBy(() -> teamLikeCommandService.toggleLike(member.getId(), votingTeam.getId(), true)) - .isInstanceOf(ContestException.class) - .hasMessage(NOT_ALLOWED_DURING_VOTING_PERIOD.errorMessage()); - } -} From c9aacb1e20a620a33187ad8f116e2aeaac1a4e91 Mon Sep 17 00:00:00 2001 From: pykido Date: Mon, 9 Feb 2026 22:49:06 +0900 Subject: [PATCH 13/15] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EA=B0=84=EB=8B=A8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/opus/opus/restdocs/docs/ContestApiDocsTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/opus/opus/restdocs/docs/ContestApiDocsTest.java b/src/test/java/com/opus/opus/restdocs/docs/ContestApiDocsTest.java index acb59a8c..e96a4a65 100644 --- a/src/test/java/com/opus/opus/restdocs/docs/ContestApiDocsTest.java +++ b/src/test/java/com/opus/opus/restdocs/docs/ContestApiDocsTest.java @@ -1,7 +1,6 @@ package com.opus.opus.restdocs.docs; import static com.opus.opus.modules.contest.exception.ContestExceptionType.NOT_ALLOWED_DURING_VOTING_PERIOD; -import static com.opus.opus.modules.contest.exception.ContestExceptionType.CANNOT_CHANGE_VOTES_DURING_VOTING_PERIOD; import static com.opus.opus.modules.contest.exception.ContestExceptionType.CONTEST_NAME_ALREADY_EXIST; import static com.opus.opus.modules.contest.exception.ContestExceptionType.NOT_FOUND_CONTEST; import static java.time.LocalDateTime.now; From e765c09825a6a73311cd6d080869d0b76e3716fe Mon Sep 17 00:00:00 2001 From: pykido Date: Wed, 11 Feb 2026 22:51:22 +0900 Subject: [PATCH 14/15] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EA=B0=84=EB=8B=A8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../opus/modules/team/api/TeamController.java | 18 +-- .../team/application/TeamCommandService.java | 29 +++-- .../application/TeamCommandServiceTest.java | 104 ++++++++---------- 3 files changed, 70 insertions(+), 81 deletions(-) diff --git a/src/main/java/com/opus/opus/modules/team/api/TeamController.java b/src/main/java/com/opus/opus/modules/team/api/TeamController.java index fae877e1..1f290c27 100644 --- a/src/main/java/com/opus/opus/modules/team/api/TeamController.java +++ b/src/main/java/com/opus/opus/modules/team/api/TeamController.java @@ -87,15 +87,6 @@ public ResponseEntity deleteThumbnailImage(@PathVariable final Long teamId return ResponseEntity.noContent().build(); } - @Secured({"ROLE_회원", "ROLE_관리자"}) - @PutMapping("/{teamId}/votes") - public ResponseEntity toggleVote(@PathVariable Long teamId, - @RequestBody @Valid TeamVoteToggleRequest request, - @LoginMember Member member) { - TeamVoteToggleResponse response = teamCommandService.toggleVote(member.getId(), teamId, request.isVoted()); - return ResponseEntity.ok(response); - } - @GetMapping("/{teamId}/image/posters") public ResponseEntity getPosterImage(@PathVariable final Long teamId) { final ImageResponse imageResponse = teamQueryService.getPosterImage(teamId); @@ -120,6 +111,15 @@ public ResponseEntity deletePosterImage(@PathVariable final Long teamId) { return ResponseEntity.noContent().build(); } + @Secured({"ROLE_회원", "ROLE_관리자"}) + @PutMapping("/{teamId}/votes") + public ResponseEntity toggleVote(@PathVariable Long teamId, + @RequestBody @Valid TeamVoteToggleRequest request, + @LoginMember Member member) { + TeamVoteToggleResponse response = teamCommandService.toggleVote(member.getId(), teamId, request.isVoted()); + return ResponseEntity.ok(response); + } + @Secured({"ROLE_회원", "ROLE_관리자"}) @PutMapping("/{teamId}/likes") public ResponseEntity toggleLike(@PathVariable final Long teamId, diff --git a/src/main/java/com/opus/opus/modules/team/application/TeamCommandService.java b/src/main/java/com/opus/opus/modules/team/application/TeamCommandService.java index cf8084fe..95abf93c 100644 --- a/src/main/java/com/opus/opus/modules/team/application/TeamCommandService.java +++ b/src/main/java/com/opus/opus/modules/team/application/TeamCommandService.java @@ -5,7 +5,6 @@ import static com.opus.opus.modules.file.domain.FileImageType.THUMBNAIL; import static com.opus.opus.modules.file.domain.ReferenceDomainType.TEAM; import static com.opus.opus.modules.file.exception.FileExceptionType.EXCEED_PREVIEW_LIMIT; -import static com.opus.opus.modules.file.exception.FileExceptionType.NOT_WEBP_CONVERTED; import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_LIKED; import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.ALREADY_UNLIKED; import static com.opus.opus.modules.team.exception.TeamLikeExceptionType.NOT_LIKED_YET; @@ -18,7 +17,6 @@ import com.opus.opus.global.util.FileStorageUtil; import com.opus.opus.modules.contest.application.convenience.ContestConvenience; import com.opus.opus.modules.contest.domain.Contest; -import com.opus.opus.modules.file.domain.File; import com.opus.opus.modules.file.domain.FileImageType; import com.opus.opus.modules.file.domain.dao.FileRepository; import com.opus.opus.modules.file.exception.FileException; @@ -29,7 +27,6 @@ import com.opus.opus.modules.team.domain.dao.TeamLikeRepository; import com.opus.opus.modules.team.exception.TeamLikeException; import com.opus.opus.modules.team.application.dto.response.TeamVoteToggleResponse; -import com.opus.opus.modules.team.domain.Team; import com.opus.opus.modules.team.domain.TeamVote; import com.opus.opus.modules.team.domain.dao.TeamVoteRepository; import com.opus.opus.modules.team.exception.TeamVoteException; @@ -54,7 +51,6 @@ public class TeamCommandService { private final ContestConvenience contestConvenience; private final TeamVoteRepository teamVoteRepository; - private final ContestConvenience contestConvenience; private final TeamLikeRepository teamLikeRepository; @@ -106,6 +102,19 @@ public TeamLikeToggleResponse toggleLike(final Long memberId, final Long teamId, .orElseGet(() -> handleFirstTimeLike(memberId, team, isLiked)); } + public TeamVoteToggleResponse toggleVote(Long memberId, Long teamId, Boolean isVoted) { + Team team = teamConvenience.getValidateExistTeam(teamId); + Contest contest = contestConvenience.getValidateExistContest(team.getContestId()); + + contestConvenience.validateVotingPeriod(contest); + + Optional teamVoteOptional = teamVoteRepository.findByMemberIdAndTeam(memberId, team); + + return teamVoteOptional.map(teamVote -> handleExistingVote(teamVote, isVoted, memberId, contest)) + .orElseGet(() -> handleFirstTimeVote(memberId, team, isVoted, contest)); + } + + private TeamLikeToggleResponse handleFirstTimeLike(final Long memberId, final Team team, final Boolean isLiked) { if (!isLiked) { throw new TeamLikeException(NOT_LIKED_YET); @@ -133,18 +142,6 @@ private void saveTeamLike(final Long memberId, final Team team) { .build()); } - public TeamVoteToggleResponse toggleVote(Long memberId, Long teamId, Boolean isVoted) { - Team team = teamConvenience.getValidateExistTeam(teamId); - Contest contest = contestConvenience.getValidateExistContest(team.getContestId()); - - contestConvenience.validateVotingPeriod(contest); - - Optional teamVoteOptional = teamVoteRepository.findByMemberIdAndTeam(memberId, team); - - return teamVoteOptional.map(teamVote -> handleExistingVote(teamVote, isVoted, memberId, contest)) - .orElseGet(() -> handleFirstTimeVote(memberId, team, isVoted, contest)); - } - private TeamVoteToggleResponse handleFirstTimeVote(Long memberId, Team team, Boolean isVoted, Contest contest) { if (!isVoted) { throw new TeamVoteException(NOT_VOTED_YET); diff --git a/src/test/java/com/opus/opus/team/application/TeamCommandServiceTest.java b/src/test/java/com/opus/opus/team/application/TeamCommandServiceTest.java index 97723cd1..4acc18ed 100644 --- a/src/test/java/com/opus/opus/team/application/TeamCommandServiceTest.java +++ b/src/test/java/com/opus/opus/team/application/TeamCommandServiceTest.java @@ -12,7 +12,6 @@ import static com.opus.opus.modules.team.exception.TeamVoteExceptionType.ALREADY_UNVOTED; import static com.opus.opus.modules.team.exception.TeamVoteExceptionType.ALREADY_VOTED; import static com.opus.opus.modules.team.exception.TeamVoteExceptionType.NOT_VOTED_YET; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -22,7 +21,6 @@ import static org.springframework.test.util.ReflectionTestUtils.setField; import com.opus.opus.contest.ContestFixture; -import com.opus.opus.global.util.FileStorageUtil; import com.opus.opus.helper.IntegrationTest; import com.opus.opus.member.MemberFixture; import com.opus.opus.modules.contest.domain.Contest; @@ -49,14 +47,11 @@ import com.opus.opus.team.TeamVoteFixture; import java.time.LocalDateTime; import com.opus.opus.team.TeamLikeFixture; -import java.time.LocalDateTime; -import java.util.ArrayList; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.test.context.bean.override.mockito.MockitoBean; public class TeamCommandServiceTest extends IntegrationTest { @@ -72,32 +67,29 @@ public class TeamCommandServiceTest extends IntegrationTest { @Autowired private ContestRepository contestRepository; @Autowired - private MemberRepository memberRepository; - @Autowired private TeamLikeRepository teamLikeRepository; @Autowired - private ContestRepository contestRepository; - @Autowired private FileRepository fileRepository; - private Contest contest; - private Team team; + private Contest notVotingContest; + private Contest votingContest; + private Team notVotingTeam; + private Team votingTeam; private Member member; @BeforeEach void setUp() { - final Contest newContest = ContestFixture.createContest(); - newContest.updateVotePeriod(LocalDateTime.now().minusDays(10), LocalDateTime.now().minusDays(5)); - contest = contestRepository.save(newContest); + final Contest newNotVotingContest = ContestFixture.createContest(); + newNotVotingContest.updateVotePeriod(LocalDateTime.now().minusDays(10), LocalDateTime.now().minusDays(5)); + notVotingContest = contestRepository.save(newNotVotingContest); // 투표 기간이 지난 대회 생성 + notVotingTeam = teamRepository.save(TeamFixture.createTeamWithContestId(notVotingContest.getId())); - team = teamRepository.save(TeamFixture.createTeamWithContestId(contest.getId())); - member = memberRepository.save(MemberFixture.createMember()); - Contest newContest = ContestFixture.createContest(); - newContest.updateVotePeriod(LocalDateTime.now().minusDays(1), LocalDateTime.now().plusDays(1)); - newContest.updateMaxVotesLimit(2); - contest = contestRepository.save(newContest); + final Contest newVotingContest = ContestFixture.createContest(); + newVotingContest.updateVotePeriod(LocalDateTime.now().minusDays(1), LocalDateTime.now().plusDays(5)); + newVotingContest.updateMaxVotesLimit(2); + votingContest = contestRepository.save(newVotingContest); // 투표 가능한 대회 생성 + votingTeam = teamRepository.save(TeamFixture.createTeamWithContestId(votingContest.getId())); // 투표 가능한 팀 생성 - team = teamRepository.save(TeamFixture.createTeamWithContestId(contest.getId())); member = memberRepository.save(MemberFixture.createMember()); } @@ -109,10 +101,10 @@ void setUp() { "content".getBytes()); // when - teamCommandService.savePosterImage(team.getId(), image); + teamCommandService.savePosterImage(notVotingTeam.getId(), image); // then - verify(fileStorageUtil, times(1)).storeFile(any(), eq(team.getId()), eq(TEAM), eq(POSTER)); + verify(fileStorageUtil, times(1)).storeFile(any(), eq(notVotingTeam.getId()), eq(TEAM), eq(POSTER)); } @Test @@ -134,13 +126,13 @@ void setUp() { void 팀_포스터_이미지를_삭제한다() { // given final File file = FileFixture.createTeamPosterFile(); - setField(file, "referenceId", team.getId()); + setField(file, "referenceId", notVotingTeam.getId()); final File savedFile = fileRepository.save(file); savedFile.updateIsWebpConverted(true); fileRepository.saveAndFlush(savedFile); // when - teamCommandService.deletePosterImage(team.getId()); + teamCommandService.deletePosterImage(notVotingTeam.getId()); // then verify(fileStorageUtil, times(1)).deleteFile(savedFile.getId()); @@ -150,7 +142,7 @@ void setUp() { @DisplayName("[성공] 팀 포스터 이미지가 없어도 삭제 요청 시 예외가 발생하지 않는다.") void 팀_포스터_이미지가_없어도_삭제_요청_시_예외가_발생하지_않는다() { // when - teamCommandService.deletePosterImage(team.getId()); + teamCommandService.deletePosterImage(notVotingTeam.getId()); // then verify(fileStorageUtil, never()).deleteFile(any()); @@ -161,41 +153,41 @@ void setUp() { void 팀_포스터_이미지가_이미_존재하면_기존_이미지를_삭제하고_새로_저장한다() { // given final File existingFile = FileFixture.createTeamPosterFile(); - setField(existingFile, "referenceId", team.getId()); + setField(existingFile, "referenceId", notVotingTeam.getId()); final File savedFile = fileRepository.save(existingFile); final MockMultipartFile newImage = new MockMultipartFile("image", "new_poster.jpg", "image/jpeg", "new_content".getBytes()); // when - teamCommandService.savePosterImage(team.getId(), newImage); + teamCommandService.savePosterImage(notVotingTeam.getId(), newImage); // then verify(fileStorageUtil, times(1)).deleteFile(savedFile.getId()); - verify(fileStorageUtil, times(1)).storeFile(any(), eq(team.getId()), eq(TEAM), eq(POSTER)); + verify(fileStorageUtil, times(1)).storeFile(any(), eq(notVotingTeam.getId()), eq(TEAM), eq(POSTER)); } @Test @DisplayName("[성공] 처음 투표하면 TeamVote가 생성되고 투표가 등록된다.") void 처음_투표하면_TeamVote가_생성되고_투표가_등록된다() { - TeamVoteToggleResponse response = teamCommandService.toggleVote(member.getId(), team.getId(), true); + TeamVoteToggleResponse response = teamCommandService.toggleVote(member.getId(), votingTeam.getId(), true); - assertThat(response.teamId()).isEqualTo(team.getId()); + assertThat(response.teamId()).isEqualTo(votingTeam.getId()); assertThat(response.isVoted()).isTrue(); assertThat(response.message()).isEqualTo("투표가 등록되었습니다."); assertThat(response.remainingVotesCount()).isEqualTo(1L); assertThat(response.maxVotesLimit()).isEqualTo(2L); - TeamVote savedVote = teamVoteRepository.findByMemberIdAndTeam(member.getId(), team).orElseThrow(); + TeamVote savedVote = teamVoteRepository.findByMemberIdAndTeam(member.getId(), votingTeam).orElseThrow(); assertThat(savedVote.getIsVoted()).isTrue(); } @Test @DisplayName("[성공] 기존 투표를 취소할 수 있다.") void 기존_투표를_취소할_수_있다() { - teamVoteRepository.save(TeamVoteFixture.createTeamVote(team, member.getId(), true)); + teamVoteRepository.save(TeamVoteFixture.createTeamVote(votingTeam, member.getId(), true)); - TeamVoteToggleResponse response = teamCommandService.toggleVote(member.getId(), team.getId(), false); + TeamVoteToggleResponse response = teamCommandService.toggleVote(member.getId(), votingTeam.getId(), false); assertThat(response.isVoted()).isFalse(); assertThat(response.message()).isEqualTo("투표가 취소되었습니다."); @@ -205,9 +197,9 @@ void setUp() { @Test @DisplayName("[성공] 취소한 투표를 다시 등록할 수 있다.") void 취소한_투표를_다시_등록할_수_있다() { - teamVoteRepository.save(TeamVoteFixture.createTeamVote(team, member.getId(), false)); + teamVoteRepository.save(TeamVoteFixture.createTeamVote(votingTeam, member.getId(), false)); - TeamVoteToggleResponse response = teamCommandService.toggleVote(member.getId(), team.getId(), true); + TeamVoteToggleResponse response = teamCommandService.toggleVote(member.getId(), votingTeam.getId(), true); assertThat(response.isVoted()).isTrue(); assertThat(response.message()).isEqualTo("투표가 등록되었습니다."); @@ -217,7 +209,7 @@ void setUp() { @Test @DisplayName("[실패] 투표한 적 없는 팀에 취소 요청하면 예외가 발생한다.") void 투표한_적_없는_팀에_취소_요청하면_예외가_발생한다() { - assertThatThrownBy(() -> teamCommandService.toggleVote(member.getId(), team.getId(), false)) + assertThatThrownBy(() -> teamCommandService.toggleVote(member.getId(), votingTeam.getId(), false)) .isInstanceOf(TeamVoteException.class) .hasMessage(NOT_VOTED_YET.errorMessage()); } @@ -225,9 +217,9 @@ void setUp() { @Test @DisplayName("[실패] 이미 투표한 팀에 다시 투표하면 예외가 발생한다.") void 이미_투표한_팀에_다시_투표하면_예외가_발생한다() { - teamVoteRepository.save(TeamVoteFixture.createTeamVote(team, member.getId(), true)); + teamVoteRepository.save(TeamVoteFixture.createTeamVote(votingTeam, member.getId(), true)); - assertThatThrownBy(() -> teamCommandService.toggleVote(member.getId(), team.getId(), true)) + assertThatThrownBy(() -> teamCommandService.toggleVote(member.getId(), votingTeam.getId(), true)) .isInstanceOf(TeamVoteException.class) .hasMessage(ALREADY_VOTED.errorMessage()); } @@ -235,9 +227,9 @@ void setUp() { @Test @DisplayName("[실패] 이미 투표 취소한 팀에 다시 취소하면 예외가 발생한다.") void 이미_투표_취소한_팀에_다시_취소하면_예외가_발생한다() { - teamVoteRepository.save(TeamVoteFixture.createTeamVote(team, member.getId(), false)); + teamVoteRepository.save(TeamVoteFixture.createTeamVote(votingTeam, member.getId(), false)); - assertThatThrownBy(() -> teamCommandService.toggleVote(member.getId(), team.getId(), false)) + assertThatThrownBy(() -> teamCommandService.toggleVote(member.getId(), votingTeam.getId(), false)) .isInstanceOf(TeamVoteException.class) .hasMessage(ALREADY_UNVOTED.errorMessage()); } @@ -270,10 +262,10 @@ void setUp() { @Test @DisplayName("[실패] 최대 투표 수를 초과하면 예외가 발생한다.") void 최대_투표_수를_초과하면_예외가_발생한다() { - Team secondTeam = teamRepository.save(TeamFixture.createTeamWithContestId(contest.getId())); - Team thirdTeam = teamRepository.save(TeamFixture.createTeamWithContestId(contest.getId())); + Team secondTeam = teamRepository.save(TeamFixture.createTeamWithContestId(votingContest.getId())); + Team thirdTeam = teamRepository.save(TeamFixture.createTeamWithContestId(votingContest.getId())); - teamVoteRepository.save(TeamVoteFixture.createTeamVote(team, member.getId(), true)); + teamVoteRepository.save(TeamVoteFixture.createTeamVote(votingTeam, member.getId(), true)); teamVoteRepository.save(TeamVoteFixture.createTeamVote(secondTeam, member.getId(), true)); assertThatThrownBy(() -> teamCommandService.toggleVote(member.getId(), thirdTeam.getId(), true)) @@ -284,22 +276,22 @@ void setUp() { @Test @DisplayName("[성공] 처음 좋아요하면 TeamLike가 생성되고 좋아요가 등록된다.") void 처음_좋아요하면_TeamLike가_생성되고_좋아요가_등록된다() { - final TeamLikeToggleResponse response = teamCommandService.toggleLike(member.getId(), team.getId(), true); + final TeamLikeToggleResponse response = teamCommandService.toggleLike(member.getId(), notVotingTeam.getId(), true); - assertThat(response.teamId()).isEqualTo(team.getId()); + assertThat(response.teamId()).isEqualTo(notVotingTeam.getId()); assertThat(response.isLiked()).isTrue(); assertThat(response.message()).isEqualTo("좋아요가 등록되었습니다."); - final TeamLike savedLike = teamLikeRepository.findByMemberIdAndTeam(member.getId(), team).orElseThrow(); + final TeamLike savedLike = teamLikeRepository.findByMemberIdAndTeam(member.getId(), notVotingTeam).orElseThrow(); assertThat(savedLike.getIsLiked()).isTrue(); } @Test @DisplayName("[성공] 기존 좋아요를 취소할 수 있다.") void 기존_좋아요를_취소할_수_있다() { - teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), true)); + teamLikeRepository.save(TeamLikeFixture.createTeamLike(notVotingTeam, member.getId(), true)); - final TeamLikeToggleResponse response = teamCommandService.toggleLike(member.getId(), team.getId(), false); + final TeamLikeToggleResponse response = teamCommandService.toggleLike(member.getId(), notVotingTeam.getId(), false); assertThat(response.isLiked()).isFalse(); assertThat(response.message()).isEqualTo("좋아요가 취소되었습니다."); @@ -308,9 +300,9 @@ void setUp() { @Test @DisplayName("[성공] 취소한 좋아요를 다시 등록할 수 있다.") void 취소한_좋아요를_다시_등록할_수_있다() { - teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), false)); + teamLikeRepository.save(TeamLikeFixture.createTeamLike(notVotingTeam, member.getId(), false)); - final TeamLikeToggleResponse response = teamCommandService.toggleLike(member.getId(), team.getId(), true); + final TeamLikeToggleResponse response = teamCommandService.toggleLike(member.getId(), notVotingTeam.getId(), true); assertThat(response.isLiked()).isTrue(); assertThat(response.message()).isEqualTo("좋아요가 등록되었습니다."); @@ -319,7 +311,7 @@ void setUp() { @Test @DisplayName("[실패] 좋아요한 적 없는 팀에 취소 요청하면 예외가 발생한다.") void 좋아요한_적_없는_팀에_취소_요청하면_예외가_발생한다() { - assertThatThrownBy(() -> teamCommandService.toggleLike(member.getId(), team.getId(), false)) + assertThatThrownBy(() -> teamCommandService.toggleLike(member.getId(), notVotingTeam.getId(), false)) .isInstanceOf(TeamLikeException.class) .hasMessage(NOT_LIKED_YET.errorMessage()); } @@ -327,9 +319,9 @@ void setUp() { @Test @DisplayName("[실패] 이미 좋아요한 팀에 다시 좋아요하면 예외가 발생한다.") void 이미_좋아요한_팀에_다시_좋아요하면_예외가_발생한다() { - teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), true)); + teamLikeRepository.save(TeamLikeFixture.createTeamLike(notVotingTeam, member.getId(), true)); - assertThatThrownBy(() -> teamCommandService.toggleLike(member.getId(), team.getId(), true)) + assertThatThrownBy(() -> teamCommandService.toggleLike(member.getId(), notVotingTeam.getId(), true)) .isInstanceOf(TeamLikeException.class) .hasMessage(ALREADY_LIKED.errorMessage()); } @@ -337,9 +329,9 @@ void setUp() { @Test @DisplayName("[실패] 이미 좋아요 취소한 팀에 다시 취소하면 예외가 발생한다.") void 이미_좋아요_취소한_팀에_다시_취소하면_예외가_발생한다() { - teamLikeRepository.save(TeamLikeFixture.createTeamLike(team, member.getId(), false)); + teamLikeRepository.save(TeamLikeFixture.createTeamLike(notVotingTeam, member.getId(), false)); - assertThatThrownBy(() -> teamCommandService.toggleLike(member.getId(), team.getId(), false)) + assertThatThrownBy(() -> teamCommandService.toggleLike(member.getId(), notVotingTeam.getId(), false)) .isInstanceOf(TeamLikeException.class) .hasMessage(ALREADY_UNLIKED.errorMessage()); } From 2e5d027ef6fdc709de4b320747fdd2af46a27da1 Mon Sep 17 00:00:00 2001 From: pykido Date: Wed, 11 Feb 2026 22:57:52 +0900 Subject: [PATCH 15/15] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EA=B0=84=EB=8B=A8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/TeamCommandServiceTest.java | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/test/java/com/opus/opus/team/application/TeamCommandServiceTest.java b/src/test/java/com/opus/opus/team/application/TeamCommandServiceTest.java index 4acc18ed..cf50a4b3 100644 --- a/src/test/java/com/opus/opus/team/application/TeamCommandServiceTest.java +++ b/src/test/java/com/opus/opus/team/application/TeamCommandServiceTest.java @@ -75,10 +75,14 @@ public class TeamCommandServiceTest extends IntegrationTest { private Contest votingContest; private Team notVotingTeam; private Team votingTeam; + private Team generalTeam; private Member member; @BeforeEach void setUp() { + generalTeam = teamRepository.save(TeamFixture.createTeam()); + member = memberRepository.save(MemberFixture.createMember()); + final Contest newNotVotingContest = ContestFixture.createContest(); newNotVotingContest.updateVotePeriod(LocalDateTime.now().minusDays(10), LocalDateTime.now().minusDays(5)); notVotingContest = contestRepository.save(newNotVotingContest); // 투표 기간이 지난 대회 생성 @@ -89,30 +93,26 @@ void setUp() { newVotingContest.updateMaxVotesLimit(2); votingContest = contestRepository.save(newVotingContest); // 투표 가능한 대회 생성 votingTeam = teamRepository.save(TeamFixture.createTeamWithContestId(votingContest.getId())); // 투표 가능한 팀 생성 - - member = memberRepository.save(MemberFixture.createMember()); } @Test @DisplayName("[성공] 팀 포스터 이미지를 저장한다.") void 팀_포스터_이미지를_저장한다() { // given - final MockMultipartFile image = new MockMultipartFile("image", "poster.jpg", "image/jpeg", - "content".getBytes()); + final MockMultipartFile image = new MockMultipartFile("image", "poster.jpg", "image/jpeg", "content".getBytes()); // when - teamCommandService.savePosterImage(notVotingTeam.getId(), image); + teamCommandService.savePosterImage(generalTeam.getId(), image); // then - verify(fileStorageUtil, times(1)).storeFile(any(), eq(notVotingTeam.getId()), eq(TEAM), eq(POSTER)); + verify(fileStorageUtil, times(1)).storeFile(any(), eq(generalTeam.getId()), eq(TEAM), eq(POSTER)); } @Test @DisplayName("[실패] 팀이 존재하지 않으면 포스터 이미지를 저장할 수 없다.") void 팀이_존재하지_않으면_포스터_이미지를_저장할_수_없다() { // given - final MockMultipartFile image = new MockMultipartFile("image", "poster.jpg", "image/jpeg", - "content".getBytes()); + final MockMultipartFile image = new MockMultipartFile("image", "poster.jpg", "image/jpeg", "content".getBytes()); final long notExistTeamId = 999L; // when & then @@ -126,13 +126,13 @@ void setUp() { void 팀_포스터_이미지를_삭제한다() { // given final File file = FileFixture.createTeamPosterFile(); - setField(file, "referenceId", notVotingTeam.getId()); + setField(file, "referenceId", generalTeam.getId()); final File savedFile = fileRepository.save(file); savedFile.updateIsWebpConverted(true); fileRepository.saveAndFlush(savedFile); // when - teamCommandService.deletePosterImage(notVotingTeam.getId()); + teamCommandService.deletePosterImage(generalTeam.getId()); // then verify(fileStorageUtil, times(1)).deleteFile(savedFile.getId()); @@ -142,7 +142,7 @@ void setUp() { @DisplayName("[성공] 팀 포스터 이미지가 없어도 삭제 요청 시 예외가 발생하지 않는다.") void 팀_포스터_이미지가_없어도_삭제_요청_시_예외가_발생하지_않는다() { // when - teamCommandService.deletePosterImage(notVotingTeam.getId()); + teamCommandService.deletePosterImage(generalTeam.getId()); // then verify(fileStorageUtil, never()).deleteFile(any()); @@ -153,18 +153,17 @@ void setUp() { void 팀_포스터_이미지가_이미_존재하면_기존_이미지를_삭제하고_새로_저장한다() { // given final File existingFile = FileFixture.createTeamPosterFile(); - setField(existingFile, "referenceId", notVotingTeam.getId()); + setField(existingFile, "referenceId", generalTeam.getId()); final File savedFile = fileRepository.save(existingFile); - final MockMultipartFile newImage = new MockMultipartFile("image", "new_poster.jpg", "image/jpeg", - "new_content".getBytes()); + final MockMultipartFile newImage = new MockMultipartFile("image", "new_poster.jpg", "image/jpeg", "new_content".getBytes()); // when - teamCommandService.savePosterImage(notVotingTeam.getId(), newImage); + teamCommandService.savePosterImage(generalTeam.getId(), newImage); // then verify(fileStorageUtil, times(1)).deleteFile(savedFile.getId()); - verify(fileStorageUtil, times(1)).storeFile(any(), eq(notVotingTeam.getId()), eq(TEAM), eq(POSTER)); + verify(fileStorageUtil, times(1)).storeFile(any(), eq(generalTeam.getId()), eq(TEAM), eq(POSTER)); } @Test