diff --git a/build.gradle b/build.gradle index 2df1dc43..f0bf6975 100644 --- a/build.gradle +++ b/build.gradle @@ -49,6 +49,8 @@ dependencies { // lombok compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + testCompileOnly 'org.projectlombok:lombok:1.18.24' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.24' // rest docs asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' diff --git a/src/docs/asciidoc/docs.adoc b/src/docs/asciidoc/docs.adoc index 3a5ebd08..7ebb0205 100644 --- a/src/docs/asciidoc/docs.adoc +++ b/src/docs/asciidoc/docs.adoc @@ -35,3 +35,6 @@ include::themecolor.adoc[] == 친구 include::friend.adoc[] + +== 골 +include::goal.adoc[] diff --git a/src/docs/asciidoc/goal.adoc b/src/docs/asciidoc/goal.adoc new file mode 100644 index 00000000..7bfb456a --- /dev/null +++ b/src/docs/asciidoc/goal.adoc @@ -0,0 +1,17 @@ +=== 새로운 골 추가 +==== 요청 +operation::goal-controller-test/골_생성을_요청하면_새로운_골을_생성한다[snippets='http-request,request-headers,request-fields'] +==== 응답 +operation::goal-controller-test/골_생성을_요청하면_새로운_골을_생성한다[snippets='http-response,response-body'] + +=== 단일 골 정보 조회 +==== 요청 +operation::goal-controller-test/골_아이디로_조회하면_해당_골의_정보를_반환한다[snippets='http-request,path-parameters,request-headers'] +==== 응답 +operation::goal-controller-test/골_아이디로_조회하면_해당_골의_정보를_반환한다[snippets='http-response,response-body,response-fields'] + +=== 현재 로그인한 사용자가 참여한 모든 골 조회 +==== 요청 +operation::goal-controller-test/현재_로그인한_사용자가_참여한_모든_골을_조회한다[snippets='http-request,request-headers'] +==== 응답 +operation::goal-controller-test/현재_로그인한_사용자가_참여한_모든_골을_조회한다[snippets='http-response,response-body,response-fields'] diff --git a/src/main/java/com/backend/blooming/exception/ExceptionMessage.java b/src/main/java/com/backend/blooming/exception/ExceptionMessage.java index facfe3b1..e1696f9c 100644 --- a/src/main/java/com/backend/blooming/exception/ExceptionMessage.java +++ b/src/main/java/com/backend/blooming/exception/ExceptionMessage.java @@ -31,7 +31,16 @@ public enum ExceptionMessage { ALREADY_REQUESTED_FRIEND("이미 친구를 요청한 사용자입니다."), NOT_FOUND_FRIEND_REQUEST("해당 친구 요청을 조회할 수 없습니다."), FRIEND_ACCEPTANCE_FORBIDDEN("친구 요청을 수락할 권한이 없습니다."), - DELETE_FRIEND_FORBIDDEN("친구를 삭제할 권한이 없습니다."); + DELETE_FRIEND_FORBIDDEN("친구를 삭제할 권한이 없습니다."), + + // 골 추가 + GOAL_NOT_FOUND("골 정보를 찾을 수 없습니다."), + GOAL_TEAM_NOT_FOUND("골 팀 정보를 찾을 수 없습니다."), + INVALID_GOAL_START_DAY("시작 날짜가 현재 날짜 이전입니다."), + INVALID_GOAL_END_DAY("종료 날짜가 현재 날짜 이전입니다."), + INVALID_GOAL_PERIOD("시작 날짜가 종료 날짜 이후입니다."), + INVALID_GOAL_DAYS("골 날짜 수가 범위 밖입니다.(범위: 1~100)"), + INVALID_USERS_SIZE("골에 참여하는 친구가 5명 초과입니다."); private final String message; } diff --git a/src/main/java/com/backend/blooming/exception/GlobalExceptionHandler.java b/src/main/java/com/backend/blooming/exception/GlobalExceptionHandler.java index 8579ec3e..ce837360 100644 --- a/src/main/java/com/backend/blooming/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/backend/blooming/exception/GlobalExceptionHandler.java @@ -8,6 +8,9 @@ import com.backend.blooming.friend.application.exception.DeleteFriendForbiddenException; import com.backend.blooming.friend.application.exception.FriendAcceptanceForbiddenException; import com.backend.blooming.friend.application.exception.NotFoundFriendRequestException; +import com.backend.blooming.goal.application.exception.InvalidGoalException; +import com.backend.blooming.goal.application.exception.NotFoundGoalException; +import com.backend.blooming.goal.application.exception.NotFoundGoalTeamException; import com.backend.blooming.themecolor.domain.exception.UnsupportedThemeColorException; import com.backend.blooming.user.application.exception.NotFoundUserException; import org.springframework.http.HttpHeaders; @@ -109,6 +112,36 @@ public ResponseEntity handleNotFoundUserException( .body(new ExceptionResponse(exception.getMessage())); } + @ExceptionHandler(InvalidGoalException.class) + public ResponseEntity handleInvalidGoalException( + final InvalidGoalException exception + ) { + logger.warn(String.format(LOG_MESSAGE_FORMAT, exception.getClass().getSimpleName(), exception.getMessage())); + + return ResponseEntity.status(HttpStatus.FORBIDDEN) + .body(new ExceptionResponse(exception.getMessage())); + } + + @ExceptionHandler(NotFoundGoalException.class) + public ResponseEntity handleNotFoundGoalException( + final NotFoundGoalException exception + ) { + logger.warn(String.format(LOG_MESSAGE_FORMAT, exception.getClass().getSimpleName(), exception.getMessage())); + + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(new ExceptionResponse(exception.getMessage())); + } + + @ExceptionHandler(NotFoundGoalTeamException.class) + public ResponseEntity handleNotFoundGoalTeamException( + final NotFoundGoalTeamException exception + ) { + logger.warn(String.format(LOG_MESSAGE_FORMAT, exception.getClass().getSimpleName(), exception.getMessage())); + + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(new ExceptionResponse(exception.getMessage())); + } + @ExceptionHandler(AlreadyRequestedFriendException.class) public ResponseEntity handleAlreadyRequestedFriendException( final AlreadyRequestedFriendException exception diff --git a/src/main/java/com/backend/blooming/goal/application/GoalService.java b/src/main/java/com/backend/blooming/goal/application/GoalService.java new file mode 100644 index 00000000..b8595d06 --- /dev/null +++ b/src/main/java/com/backend/blooming/goal/application/GoalService.java @@ -0,0 +1,70 @@ +package com.backend.blooming.goal.application; + +import com.backend.blooming.goal.application.dto.CreateGoalDto; +import com.backend.blooming.goal.application.dto.ReadAllGoalDto; +import com.backend.blooming.goal.application.dto.ReadGoalDetailDto; +import com.backend.blooming.goal.application.exception.NotFoundGoalException; +import com.backend.blooming.goal.domain.Goal; +import com.backend.blooming.goal.infrastructure.repository.GoalRepository; +import com.backend.blooming.user.application.exception.NotFoundUserException; +import com.backend.blooming.user.domain.User; +import com.backend.blooming.user.infrastructure.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class GoalService { + + private final GoalRepository goalRepository; + private final UserRepository userRepository; + + public Long createGoal(final CreateGoalDto createGoalDto) { + final Goal goal = persistGoal(createGoalDto); + + return goal.getId(); + } + + private Goal persistGoal(final CreateGoalDto createGoalDto) { + final User user = getValidUser(createGoalDto.managerId()); + final List users = createGoalDto.teamUserIds() + .stream() + .map(this::getValidUser) + .toList(); + + final Goal goal = Goal.builder() + .name(createGoalDto.name()) + .memo(createGoalDto.memo()) + .startDate(createGoalDto.startDate()) + .endDate(createGoalDto.endDate()) + .managerId(user.getId()) + .users(users) + .build(); + + return goalRepository.save(goal); + } + + private User getValidUser(final Long userId) { + return userRepository.findByIdAndDeletedIsFalse(userId) + .orElseThrow(NotFoundUserException::new); + } + + @Transactional(readOnly = true) + public ReadGoalDetailDto readGoalDetailById(final Long goalId) { + final Goal goal = goalRepository.findByIdAndDeletedIsFalse(goalId) + .orElseThrow(NotFoundGoalException::new); + + return ReadGoalDetailDto.from(goal); + } + + @Transactional(readOnly = true) + public ReadAllGoalDto readAllGoalByUserId(final Long userId) { + final List goals = goalRepository.findAllByUserIdAndDeletedIsFalse(userId); + + return ReadAllGoalDto.from(goals); + } +} diff --git a/src/main/java/com/backend/blooming/goal/application/dto/CreateGoalDto.java b/src/main/java/com/backend/blooming/goal/application/dto/CreateGoalDto.java new file mode 100644 index 00000000..426fba4b --- /dev/null +++ b/src/main/java/com/backend/blooming/goal/application/dto/CreateGoalDto.java @@ -0,0 +1,29 @@ +package com.backend.blooming.goal.application.dto; + +import com.backend.blooming.goal.presentation.dto.request.CreateGoalRequest; + +import java.time.LocalDate; +import java.util.List; + +public record CreateGoalDto( + String name, + String memo, + LocalDate startDate, + LocalDate endDate, + Long managerId, + List teamUserIds +) { + + public static CreateGoalDto of( + final CreateGoalRequest request, + final Long managerId) { + return new CreateGoalDto( + request.name(), + request.memo(), + request.startDate(), + request.endDate(), + managerId, + request.teamUserIds() + ); + } +} diff --git a/src/main/java/com/backend/blooming/goal/application/dto/ReadAllGoalDto.java b/src/main/java/com/backend/blooming/goal/application/dto/ReadAllGoalDto.java new file mode 100644 index 00000000..093b38de --- /dev/null +++ b/src/main/java/com/backend/blooming/goal/application/dto/ReadAllGoalDto.java @@ -0,0 +1,55 @@ +package com.backend.blooming.goal.application.dto; + +import com.backend.blooming.goal.domain.Goal; +import com.backend.blooming.goal.domain.GoalTeam; +import com.backend.blooming.themecolor.domain.ThemeColor; + +import java.time.LocalDate; +import java.util.List; + +public record ReadAllGoalDto(List goalInfoDtos) { + + public static ReadAllGoalDto from(final List goals) { + final List goalInfoDtos = goals.stream() + .map(GoalInfoDto::from) + .toList(); + + return new ReadAllGoalDto(goalInfoDtos); + } + + public record GoalInfoDto( + Long id, + String name, + LocalDate startDate, + LocalDate endDate, + long days, + List goalTeamWithUserInfoDtos) { + + public static GoalInfoDto from(final Goal goal) { + final List goalTeamWithUserInfoDtos = goal.getTeams() + .stream() + .map(GoalTeamWithUserInfoDto::from) + .toList(); + + return new GoalInfoDto( + goal.getId(), + goal.getName(), + goal.getGoalTerm().getStartDate(), + goal.getGoalTerm().getEndDate(), + goal.getGoalTerm().getDays(), + goalTeamWithUserInfoDtos + ); + } + + public record GoalTeamWithUserInfoDto(Long id, String name, ThemeColor color) { + + public static GoalTeamWithUserInfoDto from(final GoalTeam goalTeam) { + return new GoalTeamWithUserInfoDto( + goalTeam.getUser().getId(), + goalTeam.getUser().getName(), + goalTeam.getUser().getColor() + ); + } + } + } +} diff --git a/src/main/java/com/backend/blooming/goal/application/dto/ReadGoalDetailDto.java b/src/main/java/com/backend/blooming/goal/application/dto/ReadGoalDetailDto.java new file mode 100644 index 00000000..3ae216ea --- /dev/null +++ b/src/main/java/com/backend/blooming/goal/application/dto/ReadGoalDetailDto.java @@ -0,0 +1,50 @@ +package com.backend.blooming.goal.application.dto; + +import com.backend.blooming.goal.domain.Goal; +import com.backend.blooming.goal.domain.GoalTeam; +import com.backend.blooming.themecolor.domain.ThemeColor; + +import java.time.LocalDate; +import java.util.List; + +public record ReadGoalDetailDto( + Long id, + String name, + String memo, + LocalDate startDate, + LocalDate endDate, + long days, + Long managerId, + List GoalTeamWithUserInfo +) { + + public static ReadGoalDetailDto from(final Goal goal) { + final List goalTeamWithUserInfoDtos = goal.getTeams() + .stream() + .map(GoalTeamWithUserInfoDto::from) + .toList(); + + return new ReadGoalDetailDto( + goal.getId(), + goal.getName(), + goal.getMemo(), + goal.getGoalTerm().getStartDate(), + goal.getGoalTerm().getEndDate(), + goal.getGoalTerm().getDays(), + goal.getManagerId(), + goalTeamWithUserInfoDtos + ); + } + + public record GoalTeamWithUserInfoDto(Long id, String name, ThemeColor color, String statusMessage) { + + public static GoalTeamWithUserInfoDto from(final GoalTeam goalTeam) { + return new GoalTeamWithUserInfoDto( + goalTeam.getId(), + goalTeam.getUser().getName(), + goalTeam.getUser().getColor(), + goalTeam.getUser().getStatusMessage() + ); + } + } +} diff --git a/src/main/java/com/backend/blooming/goal/application/exception/InvalidGoalException.java b/src/main/java/com/backend/blooming/goal/application/exception/InvalidGoalException.java new file mode 100644 index 00000000..1186bdfc --- /dev/null +++ b/src/main/java/com/backend/blooming/goal/application/exception/InvalidGoalException.java @@ -0,0 +1,46 @@ +package com.backend.blooming.goal.application.exception; + +import com.backend.blooming.exception.BloomingException; +import com.backend.blooming.exception.ExceptionMessage; + +public class InvalidGoalException extends BloomingException { + + private InvalidGoalException(final ExceptionMessage exceptionMessage) { + super(exceptionMessage); + } + + public static class InvalidInvalidGoalStartDay extends InvalidGoalException { + + public InvalidInvalidGoalStartDay() { + super(ExceptionMessage.INVALID_GOAL_START_DAY); + } + } + + public static class InvalidInvalidGoalEndDay extends InvalidGoalException { + + public InvalidInvalidGoalEndDay() { + super(ExceptionMessage.INVALID_GOAL_END_DAY); + } + } + + public static class InvalidInvalidGoalPeriod extends InvalidGoalException { + + public InvalidInvalidGoalPeriod() { + super(ExceptionMessage.INVALID_GOAL_PERIOD); + } + } + + public static class InvalidInvalidGoalDays extends InvalidGoalException { + + public InvalidInvalidGoalDays() { + super(ExceptionMessage.INVALID_GOAL_DAYS); + } + } + + public static class InvalidInvalidUsersSize extends InvalidGoalException { + + public InvalidInvalidUsersSize() { + super(ExceptionMessage.INVALID_GOAL_DAYS); + } + } +} diff --git a/src/main/java/com/backend/blooming/goal/application/exception/NotFoundGoalException.java b/src/main/java/com/backend/blooming/goal/application/exception/NotFoundGoalException.java new file mode 100644 index 00000000..a401193a --- /dev/null +++ b/src/main/java/com/backend/blooming/goal/application/exception/NotFoundGoalException.java @@ -0,0 +1,11 @@ +package com.backend.blooming.goal.application.exception; + +import com.backend.blooming.exception.BloomingException; +import com.backend.blooming.exception.ExceptionMessage; + +public class NotFoundGoalException extends BloomingException { + + public NotFoundGoalException() { + super(ExceptionMessage.GOAL_NOT_FOUND); + } +} diff --git a/src/main/java/com/backend/blooming/goal/application/exception/NotFoundGoalTeamException.java b/src/main/java/com/backend/blooming/goal/application/exception/NotFoundGoalTeamException.java new file mode 100644 index 00000000..382ed63b --- /dev/null +++ b/src/main/java/com/backend/blooming/goal/application/exception/NotFoundGoalTeamException.java @@ -0,0 +1,11 @@ +package com.backend.blooming.goal.application.exception; + +import com.backend.blooming.exception.BloomingException; +import com.backend.blooming.exception.ExceptionMessage; + +public class NotFoundGoalTeamException extends BloomingException { + + public NotFoundGoalTeamException() { + super(ExceptionMessage.GOAL_NOT_FOUND); + } +} diff --git a/src/main/java/com/backend/blooming/goal/domain/Goal.java b/src/main/java/com/backend/blooming/goal/domain/Goal.java new file mode 100644 index 00000000..2a821f15 --- /dev/null +++ b/src/main/java/com/backend/blooming/goal/domain/Goal.java @@ -0,0 +1,91 @@ +package com.backend.blooming.goal.domain; + +import com.backend.blooming.common.entity.BaseTimeEntity; +import com.backend.blooming.goal.application.exception.InvalidGoalException; +import com.backend.blooming.user.domain.User; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@EqualsAndHashCode(of = "id", callSuper = false) +@ToString(exclude = "teams") +public class Goal extends BaseTimeEntity { + + private final static String MEMO_DEFAULT = ""; + private final static int TEAMS_MAXIMUM_LENGTH = 5; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String name; + + @Column(nullable = false, columnDefinition = "text") + private String memo; + + @Embedded + private GoalTerm goalTerm; + + @Column(nullable = false) + private Long managerId; + + @OneToMany(mappedBy = "goal", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) + private List teams = new ArrayList<>(TEAMS_MAXIMUM_LENGTH); + + @Column(nullable = false) + private boolean deleted = false; + + @Builder + private Goal( + final String name, + final String memo, + final LocalDate startDate, + final LocalDate endDate, + final Long managerId, + final List users + ) { + this.name = name; + this.memo = processDefaultMemo(memo); + this.goalTerm = new GoalTerm(startDate, endDate); + this.managerId = managerId; + createGoalTeams(users); + } + + private String processDefaultMemo(final String memo) { + if (memo == null || memo.isEmpty()) { + return MEMO_DEFAULT; + } + return memo; + } + + private void createGoalTeams(final List users) { + validateUsersSize(users); + users.forEach(user -> new GoalTeam(user, this)); + } + + private void validateUsersSize(final List users) { + if (users.size() > 5) { + throw new InvalidGoalException.InvalidInvalidUsersSize(); + } + } +} diff --git a/src/main/java/com/backend/blooming/goal/domain/GoalTeam.java b/src/main/java/com/backend/blooming/goal/domain/GoalTeam.java new file mode 100644 index 00000000..f1b79965 --- /dev/null +++ b/src/main/java/com/backend/blooming/goal/domain/GoalTeam.java @@ -0,0 +1,47 @@ +package com.backend.blooming.goal.domain; + +import com.backend.blooming.common.entity.BaseTimeEntity; +import com.backend.blooming.user.domain.User; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@EqualsAndHashCode(of = "id", callSuper = false) +@ToString(exclude = {"user", "goal"}) +public class GoalTeam extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", foreignKey = @ForeignKey(name = "fk_goal_team_goal"), nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "goal_id", foreignKey = @ForeignKey(name = "fk_goal_team_user"), nullable = false) + private Goal goal; + + @Column(nullable = false) + private boolean deleted = false; + + public GoalTeam(final User user, final Goal goal) { + this.user = user; + this.goal = goal; + this.goal.getTeams().add(this); + } +} diff --git a/src/main/java/com/backend/blooming/goal/domain/GoalTerm.java b/src/main/java/com/backend/blooming/goal/domain/GoalTerm.java new file mode 100644 index 00000000..6d2453e7 --- /dev/null +++ b/src/main/java/com/backend/blooming/goal/domain/GoalTerm.java @@ -0,0 +1,70 @@ +package com.backend.blooming.goal.domain; + +import com.backend.blooming.goal.application.exception.InvalidGoalException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.validation.constraints.FutureOrPresent; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; + +@Embeddable +@Getter +@EqualsAndHashCode +@ToString +public class GoalTerm { + + private static final int GOAL_DAYS_MINIMUM = 1; + private static final int GOAL_DAYS_MAXIMUM = 100; + private static final int COUNT_GOAL_DAYS = 1; + + @Column(nullable = false) + private LocalDate startDate; + + @Column(nullable = false) + private LocalDate endDate; + + @Column(nullable = false) + private long days; + + public GoalTerm() { + } + + public GoalTerm(final LocalDate startDate, final LocalDate endDate) { + validateGoalDatePeriod(startDate, endDate); + this.startDate = startDate; + this.endDate = endDate; + this.days = getValidGoalDays(startDate, endDate); + } + + private void validateGoalDatePeriod( + final LocalDate startDate, + final LocalDate endDate) { + final LocalDate nowDate = LocalDate.now(); + + if (startDate.isBefore(nowDate)) { + throw new InvalidGoalException.InvalidInvalidGoalStartDay(); + } + if (endDate.isBefore(nowDate)) { + throw new InvalidGoalException.InvalidInvalidGoalEndDay(); + } + if (endDate.isBefore(startDate)) { + throw new InvalidGoalException.InvalidInvalidGoalPeriod(); + } + } + + private long getValidGoalDays( + final LocalDate startDate, + final LocalDate endDate) { + final long goalDays = ChronoUnit.DAYS.between(startDate, endDate) + COUNT_GOAL_DAYS; + + if (goalDays < GOAL_DAYS_MINIMUM || goalDays > GOAL_DAYS_MAXIMUM) { + throw new InvalidGoalException.InvalidInvalidGoalDays(); + } + + return goalDays; + } +} diff --git a/src/main/java/com/backend/blooming/goal/infrastructure/repository/GoalRepository.java b/src/main/java/com/backend/blooming/goal/infrastructure/repository/GoalRepository.java new file mode 100644 index 00000000..111acc48 --- /dev/null +++ b/src/main/java/com/backend/blooming/goal/infrastructure/repository/GoalRepository.java @@ -0,0 +1,23 @@ +package com.backend.blooming.goal.infrastructure.repository; + +import com.backend.blooming.goal.domain.Goal; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; +import java.util.Optional; + +public interface GoalRepository extends JpaRepository { + + Optional findByIdAndDeletedIsFalse(final Long goalId); + + @Query(""" + SELECT g + FROM Goal g + JOIN FETCH g.teams gt + JOIN FETCH gt.user gtu + WHERE (gtu.id = :userId AND g.deleted = FALSE) + ORDER BY g.goalTerm.startDate DESC + """) + List findAllByUserIdAndDeletedIsFalse(final Long userId); +} diff --git a/src/main/java/com/backend/blooming/goal/presentation/GoalController.java b/src/main/java/com/backend/blooming/goal/presentation/GoalController.java new file mode 100644 index 00000000..b84e3e4f --- /dev/null +++ b/src/main/java/com/backend/blooming/goal/presentation/GoalController.java @@ -0,0 +1,57 @@ +package com.backend.blooming.goal.presentation; + +import com.backend.blooming.authentication.presentation.anotaion.Authenticated; +import com.backend.blooming.authentication.presentation.argumentresolver.AuthenticatedUser; +import com.backend.blooming.goal.application.GoalService; +import com.backend.blooming.goal.application.dto.CreateGoalDto; +import com.backend.blooming.goal.application.dto.ReadAllGoalDto; +import com.backend.blooming.goal.application.dto.ReadGoalDetailDto; +import com.backend.blooming.goal.presentation.dto.request.CreateGoalRequest; +import com.backend.blooming.goal.presentation.dto.response.ReadAllGoalResponse; +import com.backend.blooming.goal.presentation.dto.response.ReadGoalResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.net.URI; + +@RestController +@RequestMapping("/goals") +@RequiredArgsConstructor +public class GoalController { + + private final GoalService goalService; + + @PostMapping(headers = "X-API-VERSION=1") + public ResponseEntity createGoal( + @RequestBody @Valid final CreateGoalRequest request, + @Authenticated final AuthenticatedUser authenticatedUser) { + final CreateGoalDto createGoalDto = CreateGoalDto.of(request, authenticatedUser.userId()); + final Long goalId = goalService.createGoal(createGoalDto); + + return ResponseEntity.created(URI.create("/goals/" + goalId)).build(); + } + + @GetMapping(value = "/{goalId}", headers = "X-API-VERSION=1") + public ResponseEntity readGoalById(@PathVariable("goalId") final Long goalId) { + final ReadGoalDetailDto readGoalDetailDto = goalService.readGoalDetailById(goalId); + final ReadGoalResponse response = ReadGoalResponse.from(readGoalDetailDto); + + return ResponseEntity.ok(response); + } + + @GetMapping(value = "/all", headers = "X-API-VERSION=1") + public ResponseEntity readAllGoalUserAttend( + @Authenticated final AuthenticatedUser authenticatedUser) { + final ReadAllGoalDto readAllGoalDtos = goalService.readAllGoalByUserId(authenticatedUser.userId()); + final ReadAllGoalResponse response = ReadAllGoalResponse.from(readAllGoalDtos); + + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/backend/blooming/goal/presentation/dto/request/CreateGoalRequest.java b/src/main/java/com/backend/blooming/goal/presentation/dto/request/CreateGoalRequest.java new file mode 100644 index 00000000..21436c25 --- /dev/null +++ b/src/main/java/com/backend/blooming/goal/presentation/dto/request/CreateGoalRequest.java @@ -0,0 +1,25 @@ +package com.backend.blooming.goal.presentation.dto.request; + +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.NotEmpty; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; +import java.util.List; + +public record CreateGoalRequest( + + @NotEmpty(message = "제목을 입력해주세요.") + String name, + String memo, + + @DateTimeFormat(pattern = "yyyy-MM-dd") + @FutureOrPresent + LocalDate startDate, + + @DateTimeFormat(pattern = "yyyy-MM-dd") + @FutureOrPresent + LocalDate endDate, + List teamUserIds +) { +} diff --git a/src/main/java/com/backend/blooming/goal/presentation/dto/response/ReadAllGoalResponse.java b/src/main/java/com/backend/blooming/goal/presentation/dto/response/ReadAllGoalResponse.java new file mode 100644 index 00000000..422295eb --- /dev/null +++ b/src/main/java/com/backend/blooming/goal/presentation/dto/response/ReadAllGoalResponse.java @@ -0,0 +1,55 @@ +package com.backend.blooming.goal.presentation.dto.response; + +import com.backend.blooming.goal.application.dto.ReadAllGoalDto; + +import java.time.LocalDate; +import java.util.List; + +public record ReadAllGoalResponse(List goals) { + + public static ReadAllGoalResponse from(final ReadAllGoalDto readAllGoalDto) { + final List goalInfoResponses = readAllGoalDto.goalInfoDtos() + .stream() + .map(GoalInfoResponse::from) + .toList(); + + return new ReadAllGoalResponse(goalInfoResponses); + } + + public record GoalInfoResponse( + Long id, + String name, + LocalDate startDate, + LocalDate endDate, + long days, + List goalTeamWithUserInfos) { + + public static GoalInfoResponse from(final ReadAllGoalDto.GoalInfoDto goalInfoDto) { + final List goalTeamWithUserInfoResponses = goalInfoDto.goalTeamWithUserInfoDtos() + .stream() + .map(GoalTeamWithUserInfoResponse::from) + .toList(); + + return new GoalInfoResponse( + goalInfoDto.id(), + goalInfoDto.name(), + goalInfoDto.startDate(), + goalInfoDto.endDate(), + goalInfoDto.days(), + goalTeamWithUserInfoResponses); + } + } + + public record GoalTeamWithUserInfoResponse( + Long id, + String name, + String colorCode) { + + public static GoalTeamWithUserInfoResponse from(final ReadAllGoalDto.GoalInfoDto.GoalTeamWithUserInfoDto goalTeamWithUserInfoDto) { + return new GoalTeamWithUserInfoResponse( + goalTeamWithUserInfoDto.id(), + goalTeamWithUserInfoDto.name(), + goalTeamWithUserInfoDto.color().getCode()); + } + } +} diff --git a/src/main/java/com/backend/blooming/goal/presentation/dto/response/ReadGoalResponse.java b/src/main/java/com/backend/blooming/goal/presentation/dto/response/ReadGoalResponse.java new file mode 100644 index 00000000..da33c879 --- /dev/null +++ b/src/main/java/com/backend/blooming/goal/presentation/dto/response/ReadGoalResponse.java @@ -0,0 +1,57 @@ +package com.backend.blooming.goal.presentation.dto.response; + +import com.backend.blooming.goal.application.dto.ReadGoalDetailDto; +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.time.LocalDate; +import java.util.List; + +public record ReadGoalResponse( + + Long id, + String name, + String memo, + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") + LocalDate startDate, + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") + LocalDate endDate, + long days, + Long managerId, + List goalTeamWithUserInfo +) { + + public static ReadGoalResponse from(final ReadGoalDetailDto readGoalDetailDto) { + + final List goalTeamWithUserInfoResponses = readGoalDetailDto.GoalTeamWithUserInfo() + .stream() + .map(GoalTeamWithUserInfoResponse::from) + .toList(); + + return new ReadGoalResponse( + readGoalDetailDto.id(), + readGoalDetailDto.name(), + readGoalDetailDto.memo(), + readGoalDetailDto.startDate(), + readGoalDetailDto.endDate(), + readGoalDetailDto.days(), + readGoalDetailDto.managerId(), + goalTeamWithUserInfoResponses + ); + } + + public record GoalTeamWithUserInfoResponse(Long id, String name, + String colorCode, + String statusMessage) { + + public static GoalTeamWithUserInfoResponse from(final ReadGoalDetailDto.GoalTeamWithUserInfoDto goalTeamWithUserInfoDto) { + + return new GoalTeamWithUserInfoResponse( + goalTeamWithUserInfoDto.id(), + goalTeamWithUserInfoDto.name(), + goalTeamWithUserInfoDto.color().getCode(), + goalTeamWithUserInfoDto.statusMessage()); + } + } +} diff --git a/src/main/resources/static/docs/authentication.html b/src/main/resources/static/docs/authentication.html index ee56c150..f10dc7bf 100644 --- a/src/main/resources/static/docs/authentication.html +++ b/src/main/resources/static/docs/authentication.html @@ -663,7 +663,7 @@

응답

diff --git a/src/main/resources/static/docs/docs.html b/src/main/resources/static/docs/docs.html index 4b16fdd1..e5dcc62e 100644 --- a/src/main/resources/static/docs/docs.html +++ b/src/main/resources/static/docs/docs.html @@ -475,6 +475,13 @@

블루밍 API 문서

  • 친구 요청 거절/취소
  • +
  • + +
  • @@ -1851,11 +1858,513 @@
    +

    +
    +
    +

    새로운 골 추가

    +
    +

    요청

    +
    +
    HTTP request
    +
    +
    +
    POST /goals HTTP/1.1
    +Content-Type: application/json;charset=UTF-8
    +X-API-VERSION: 1
    +Authorization: Bearer access_token
    +
    +{
    +  "name" : "골 제목",
    +  "memo" : "골 메모",
    +  "startDate" : "2024-01-21",
    +  "endDate" : "2024-03-01",
    +  "teamUserIds" : [ 1, 2, 3 ]
    +}
    +
    +
    +
    +
    +
    Request headers
    + ++++ + + + + + + + + + + + + + + + + +
    NameDescription

    X-API-VERSION

    요청 버전

    Authorization

    액세스 토큰

    +
    +
    +
    Request fields
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PathTypeDescription

    name

    String

    골 제목

    memo

    String

    골 메모

    startDate

    String

    골 시작날짜

    endDate

    String

    골 종료날짜

    teamUserIds

    Array

    골 팀 사용자 아이디

    +
    +
    +
    +

    응답

    +
    +
    HTTP response
    +
    +
    +
    HTTP/1.1 201 Created
    +Location: /goals/1
    +
    +
    +
    +
    +
    Response body
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    단일 골 정보 조회

    +
    +

    요청

    +
    +
    HTTP request
    +
    +
    +
    GET /goals/1 HTTP/1.1
    +X-API-VERSION: 1
    +Authorization: Bearer access_token
    +
    +
    +
    +
    +
    Path parameters
    + + ++++ + + + + + + + + + + + + +
    Table 2. /goals/{goalId}
    ParameterDescription

    goalId

    조회할 골 아이디

    +
    +
    +
    Request headers
    + ++++ + + + + + + + + + + + + + + + + +
    NameDescription

    X-API-VERSION

    요청 버전

    Authorization

    액세스 토큰

    +
    +
    +
    +

    응답

    +
    +
    HTTP response
    +
    +
    +
    HTTP/1.1 200 OK
    +Content-Type: application/json
    +
    +{
    +  "id" : 1,
    +  "name" : "테스트 골1",
    +  "memo" : "테스트 골 메모1",
    +  "startDate" : "2024-01-21",
    +  "endDate" : "2024-01-31",
    +  "days" : 11,
    +  "managerId" : 1,
    +  "goalTeamWithUserInfo" : [ {
    +    "id" : 1,
    +    "name" : "테스트 유저1",
    +    "colorCode" : "#f8c8c4",
    +    "statusMessage" : "테스트 상태메시지1"
    +  }, {
    +    "id" : 2,
    +    "name" : "테스트 유저2",
    +    "colorCode" : "#a1b3d7",
    +    "statusMessage" : "테스트 상태메시지2"
    +  } ]
    +}
    +
    +
    +
    +
    +
    Response body
    +
    +
    +
    {
    +  "id" : 1,
    +  "name" : "테스트 골1",
    +  "memo" : "테스트 골 메모1",
    +  "startDate" : "2024-01-21",
    +  "endDate" : "2024-01-31",
    +  "days" : 11,
    +  "managerId" : 1,
    +  "goalTeamWithUserInfo" : [ {
    +    "id" : 1,
    +    "name" : "테스트 유저1",
    +    "colorCode" : "#f8c8c4",
    +    "statusMessage" : "테스트 상태메시지1"
    +  }, {
    +    "id" : 2,
    +    "name" : "테스트 유저2",
    +    "colorCode" : "#a1b3d7",
    +    "statusMessage" : "테스트 상태메시지2"
    +  } ]
    +}
    +
    +
    +
    +
    +
    Response fields
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PathTypeDescription

    id

    Number

    골 아이디

    name

    String

    골 제목

    memo

    String

    골 메모

    startDate

    String

    골 시작날짜

    endDate

    String

    골 종료날짜

    days

    Number

    골 날짜 수

    managerId

    Number

    골 관리자 아이디

    goalTeamWithUserInfo.[].id

    Number

    골 참여자 아이디

    goalTeamWithUserInfo.[].name

    String

    골 참여자 이름

    goalTeamWithUserInfo.[].colorCode

    String

    골 참여자 색상

    goalTeamWithUserInfo.[].statusMessage

    String

    골 참여자 상태메시지

    +
    +
    +
    +
    +

    현재 로그인한 사용자가 참여한 모든 골 조회

    +
    +

    요청

    +
    +
    HTTP request
    +
    +
    +
    GET /goals/all HTTP/1.1
    +X-API-VERSION: 1
    +Authorization: Bearer access_token
    +
    +
    +
    +
    +
    Request headers
    + ++++ + + + + + + + + + + + + + + + + +
    NameDescription

    X-API-VERSION

    요청 버전

    Authorization

    액세스 토큰

    +
    +
    +
    +

    응답

    +
    +
    HTTP response
    +
    +
    +
    HTTP/1.1 200 OK
    +Content-Type: application/json
    +
    +{
    +  "goals" : [ {
    +    "id" : 1,
    +    "name" : "테스트 골1",
    +    "startDate" : "2024-01-21",
    +    "endDate" : "2024-01-31",
    +    "days" : 11,
    +    "goalTeamWithUserInfos" : [ {
    +      "id" : 1,
    +      "name" : "테스트 유저1",
    +      "colorCode" : "#f8c8c4"
    +    }, {
    +      "id" : 2,
    +      "name" : "테스트 유저2",
    +      "colorCode" : "#a1b3d7"
    +    } ]
    +  }, {
    +    "id" : 2,
    +    "name" : "테스트 골2",
    +    "startDate" : "2024-01-21",
    +    "endDate" : "2024-02-10",
    +    "days" : 21,
    +    "goalTeamWithUserInfos" : [ {
    +      "id" : 1,
    +      "name" : "테스트 유저1",
    +      "colorCode" : "#f8c8c4"
    +    }, {
    +      "id" : 2,
    +      "name" : "테스트 유저2",
    +      "colorCode" : "#a1b3d7"
    +    } ]
    +  } ]
    +}
    +
    +
    +
    +
    +
    Response body
    +
    +
    +
    {
    +  "goals" : [ {
    +    "id" : 1,
    +    "name" : "테스트 골1",
    +    "startDate" : "2024-01-21",
    +    "endDate" : "2024-01-31",
    +    "days" : 11,
    +    "goalTeamWithUserInfos" : [ {
    +      "id" : 1,
    +      "name" : "테스트 유저1",
    +      "colorCode" : "#f8c8c4"
    +    }, {
    +      "id" : 2,
    +      "name" : "테스트 유저2",
    +      "colorCode" : "#a1b3d7"
    +    } ]
    +  }, {
    +    "id" : 2,
    +    "name" : "테스트 골2",
    +    "startDate" : "2024-01-21",
    +    "endDate" : "2024-02-10",
    +    "days" : 21,
    +    "goalTeamWithUserInfos" : [ {
    +      "id" : 1,
    +      "name" : "테스트 유저1",
    +      "colorCode" : "#f8c8c4"
    +    }, {
    +      "id" : 2,
    +      "name" : "테스트 유저2",
    +      "colorCode" : "#a1b3d7"
    +    } ]
    +  } ]
    +}
    +
    +
    +
    +
    +
    Response fields
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PathTypeDescription

    goals.[].id

    Number

    골 아이디

    goals.[].name

    String

    골 제목

    goals.[].startDate

    String

    골 시작날짜

    goals.[].endDate

    String

    골 종료날짜

    goals.[].days

    Number

    골 날짜 수

    goals.[].goalTeamWithUserInfos.[].id

    Number

    골 참여자 아이디

    goals.[].goalTeamWithUserInfos.[].name

    String

    골 참여자 이름

    goals.[].goalTeamWithUserInfos.[].colorCode

    String

    골 참여자 색상

    +
    +
    +
    +
    + diff --git a/src/main/resources/static/docs/friend.html b/src/main/resources/static/docs/friend.html index 74505840..590fcfb3 100644 --- a/src/main/resources/static/docs/friend.html +++ b/src/main/resources/static/docs/friend.html @@ -1075,7 +1075,7 @@
    HTTP response
    diff --git a/src/main/resources/static/docs/goal.html b/src/main/resources/static/docs/goal.html new file mode 100644 index 00000000..118ed7d2 --- /dev/null +++ b/src/main/resources/static/docs/goal.html @@ -0,0 +1,948 @@ + + + + + + + +새로운 골 추가 + + + + + +
    +
    +

    새로운 골 추가

    +
    +

    요청

    +
    +
    HTTP request
    +
    +
    +
    POST /goals HTTP/1.1
    +Content-Type: application/json;charset=UTF-8
    +X-API-VERSION: 1
    +Authorization: Bearer access_token
    +
    +{
    +  "name" : "골 제목",
    +  "memo" : "골 메모",
    +  "startDate" : "2024-01-21",
    +  "endDate" : "2024-03-01",
    +  "teamUserIds" : [ 1, 2, 3 ]
    +}
    +
    +
    +
    +
    +
    Request headers
    + ++++ + + + + + + + + + + + + + + + + +
    NameDescription

    X-API-VERSION

    요청 버전

    Authorization

    액세스 토큰

    +
    +
    +
    Request fields
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PathTypeDescription

    name

    String

    골 제목

    memo

    String

    골 메모

    startDate

    String

    골 시작날짜

    endDate

    String

    골 종료날짜

    teamUserIds

    Array

    골 팀 사용자 아이디

    +
    +
    +
    +

    응답

    +
    +
    HTTP response
    +
    +
    +
    HTTP/1.1 201 Created
    +Location: /goals/1
    +
    +
    +
    +
    +
    Response body
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    단일 골 정보 조회

    +
    +

    요청

    +
    +
    HTTP request
    +
    +
    +
    GET /goals/1 HTTP/1.1
    +X-API-VERSION: 1
    +Authorization: Bearer access_token
    +
    +
    +
    +
    +
    Path parameters
    + + ++++ + + + + + + + + + + + + +
    Table 1. /goals/{goalId}
    ParameterDescription

    goalId

    조회할 골 아이디

    +
    +
    +
    Request headers
    + ++++ + + + + + + + + + + + + + + + + +
    NameDescription

    X-API-VERSION

    요청 버전

    Authorization

    액세스 토큰

    +
    +
    +
    +

    응답

    +
    +
    HTTP response
    +
    +
    +
    HTTP/1.1 200 OK
    +Content-Type: application/json
    +
    +{
    +  "id" : 1,
    +  "name" : "테스트 골1",
    +  "memo" : "테스트 골 메모1",
    +  "startDate" : "2024-01-21",
    +  "endDate" : "2024-01-31",
    +  "days" : 11,
    +  "managerId" : 1,
    +  "goalTeamWithUserInfo" : [ {
    +    "id" : 1,
    +    "name" : "테스트 유저1",
    +    "colorCode" : "#f8c8c4",
    +    "statusMessage" : "테스트 상태메시지1"
    +  }, {
    +    "id" : 2,
    +    "name" : "테스트 유저2",
    +    "colorCode" : "#a1b3d7",
    +    "statusMessage" : "테스트 상태메시지2"
    +  } ]
    +}
    +
    +
    +
    +
    +
    Response body
    +
    +
    +
    {
    +  "id" : 1,
    +  "name" : "테스트 골1",
    +  "memo" : "테스트 골 메모1",
    +  "startDate" : "2024-01-21",
    +  "endDate" : "2024-01-31",
    +  "days" : 11,
    +  "managerId" : 1,
    +  "goalTeamWithUserInfo" : [ {
    +    "id" : 1,
    +    "name" : "테스트 유저1",
    +    "colorCode" : "#f8c8c4",
    +    "statusMessage" : "테스트 상태메시지1"
    +  }, {
    +    "id" : 2,
    +    "name" : "테스트 유저2",
    +    "colorCode" : "#a1b3d7",
    +    "statusMessage" : "테스트 상태메시지2"
    +  } ]
    +}
    +
    +
    +
    +
    +
    Response fields
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PathTypeDescription

    id

    Number

    골 아이디

    name

    String

    골 제목

    memo

    String

    골 메모

    startDate

    String

    골 시작날짜

    endDate

    String

    골 종료날짜

    days

    Number

    골 날짜 수

    managerId

    Number

    골 관리자 아이디

    goalTeamWithUserInfo.[].id

    Number

    골 참여자 아이디

    goalTeamWithUserInfo.[].name

    String

    골 참여자 이름

    goalTeamWithUserInfo.[].colorCode

    String

    골 참여자 색상

    goalTeamWithUserInfo.[].statusMessage

    String

    골 참여자 상태메시지

    +
    +
    +
    +
    +

    현재 로그인한 사용자가 참여한 모든 골 조회

    +
    +

    요청

    +
    +
    HTTP request
    +
    +
    +
    GET /goals/all HTTP/1.1
    +X-API-VERSION: 1
    +Authorization: Bearer access_token
    +
    +
    +
    +
    +
    Request headers
    + ++++ + + + + + + + + + + + + + + + + +
    NameDescription

    X-API-VERSION

    요청 버전

    Authorization

    액세스 토큰

    +
    +
    +
    +

    응답

    +
    +
    HTTP response
    +
    +
    +
    HTTP/1.1 200 OK
    +Content-Type: application/json
    +
    +{
    +  "goals" : [ {
    +    "id" : 1,
    +    "name" : "테스트 골1",
    +    "startDate" : "2024-01-21",
    +    "endDate" : "2024-01-31",
    +    "days" : 11,
    +    "goalTeamWithUserInfos" : [ {
    +      "id" : 1,
    +      "name" : "테스트 유저1",
    +      "colorCode" : "#f8c8c4"
    +    }, {
    +      "id" : 2,
    +      "name" : "테스트 유저2",
    +      "colorCode" : "#a1b3d7"
    +    } ]
    +  }, {
    +    "id" : 2,
    +    "name" : "테스트 골2",
    +    "startDate" : "2024-01-21",
    +    "endDate" : "2024-02-10",
    +    "days" : 21,
    +    "goalTeamWithUserInfos" : [ {
    +      "id" : 1,
    +      "name" : "테스트 유저1",
    +      "colorCode" : "#f8c8c4"
    +    }, {
    +      "id" : 2,
    +      "name" : "테스트 유저2",
    +      "colorCode" : "#a1b3d7"
    +    } ]
    +  } ]
    +}
    +
    +
    +
    +
    +
    Response body
    +
    +
    +
    {
    +  "goals" : [ {
    +    "id" : 1,
    +    "name" : "테스트 골1",
    +    "startDate" : "2024-01-21",
    +    "endDate" : "2024-01-31",
    +    "days" : 11,
    +    "goalTeamWithUserInfos" : [ {
    +      "id" : 1,
    +      "name" : "테스트 유저1",
    +      "colorCode" : "#f8c8c4"
    +    }, {
    +      "id" : 2,
    +      "name" : "테스트 유저2",
    +      "colorCode" : "#a1b3d7"
    +    } ]
    +  }, {
    +    "id" : 2,
    +    "name" : "테스트 골2",
    +    "startDate" : "2024-01-21",
    +    "endDate" : "2024-02-10",
    +    "days" : 21,
    +    "goalTeamWithUserInfos" : [ {
    +      "id" : 1,
    +      "name" : "테스트 유저1",
    +      "colorCode" : "#f8c8c4"
    +    }, {
    +      "id" : 2,
    +      "name" : "테스트 유저2",
    +      "colorCode" : "#a1b3d7"
    +    } ]
    +  } ]
    +}
    +
    +
    +
    +
    +
    Response fields
    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PathTypeDescription

    goals.[].id

    Number

    골 아이디

    goals.[].name

    String

    골 제목

    goals.[].startDate

    String

    골 시작날짜

    goals.[].endDate

    String

    골 종료날짜

    goals.[].days

    Number

    골 날짜 수

    goals.[].goalTeamWithUserInfos.[].id

    Number

    골 참여자 아이디

    goals.[].goalTeamWithUserInfos.[].name

    String

    골 참여자 이름

    goals.[].goalTeamWithUserInfos.[].colorCode

    String

    골 참여자 색상

    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/src/main/resources/static/docs/themecolor.html b/src/main/resources/static/docs/themecolor.html index de2dab50..3e0e6e0a 100644 --- a/src/main/resources/static/docs/themecolor.html +++ b/src/main/resources/static/docs/themecolor.html @@ -524,7 +524,7 @@

    응답

    diff --git a/src/main/resources/static/docs/user.html b/src/main/resources/static/docs/user.html index 28991e64..82fa6b4f 100644 --- a/src/main/resources/static/docs/user.html +++ b/src/main/resources/static/docs/user.html @@ -823,7 +823,7 @@

    응답

    diff --git a/src/test/java/com/backend/blooming/goal/application/GoalServiceTest.java b/src/test/java/com/backend/blooming/goal/application/GoalServiceTest.java new file mode 100644 index 00000000..20407596 --- /dev/null +++ b/src/test/java/com/backend/blooming/goal/application/GoalServiceTest.java @@ -0,0 +1,96 @@ +package com.backend.blooming.goal.application; + +import com.backend.blooming.configuration.IsolateDatabase; +import com.backend.blooming.goal.application.dto.ReadAllGoalDto; +import com.backend.blooming.goal.application.dto.ReadGoalDetailDto; +import com.backend.blooming.goal.application.exception.NotFoundGoalException; +import com.backend.blooming.user.application.exception.NotFoundUserException; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@IsolateDatabase +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class GoalServiceTest extends GoalServiceTestFixture { + + @Autowired + private GoalService goalService; + + @Test + void 새로운_골을_생성한다() { + // when + final Long goalId = goalService.createGoal(유효한_골_생성_dto); + + // then + assertThat(goalId).isPositive(); + } + + @Test + void 골_생성시_존재하지_않는_사용자가_관리자인_경우_예외를_발생한다() { + // when + assertThatThrownBy(() -> goalService.createGoal(존재하지_않는_사용자가_관리자인_골_생성_dto)) + .isInstanceOf(NotFoundUserException.class); + } + + @Test + void 골_생성시_존재하지_않는_사용자가_참여자로_있는_경우_예외를_발생한다() { + // when + assertThatThrownBy(() -> goalService.createGoal(존재하지_않는_사용자가_참여자로_있는_골_생성_dto)) + .isInstanceOf(NotFoundUserException.class); + } + + @Test + void 골_아이디로_해당_골_정보를_조회한다() { + // when + final ReadGoalDetailDto result = goalService.readGoalDetailById(유효한_골_아이디); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(result.name()).isEqualTo(골_제목); + softAssertions.assertThat(result.memo()).isEqualTo(골_메모); + softAssertions.assertThat(result.startDate()).isEqualTo(골_시작일); + softAssertions.assertThat(result.endDate()).isEqualTo(골_종료일); + softAssertions.assertThat(result.days()).isEqualTo(골_날짜수); + softAssertions.assertThat(result.managerId()).isEqualTo(유효한_사용자_아이디); + softAssertions.assertThat(result.GoalTeamWithUserInfo().get(0).id()).isEqualTo(유효한_골_dto.GoalTeamWithUserInfo().get(0).id()); + softAssertions.assertThat(result.GoalTeamWithUserInfo().get(0).name()).isEqualTo(유효한_골_dto.GoalTeamWithUserInfo().get(0).name()); + softAssertions.assertThat(result.GoalTeamWithUserInfo().get(0).color()).isEqualTo(유효한_골_dto.GoalTeamWithUserInfo().get(0).color()); + softAssertions.assertThat(result.GoalTeamWithUserInfo().get(0).statusMessage()).isEqualTo(유효한_골_dto.GoalTeamWithUserInfo().get(0).statusMessage()); + softAssertions.assertThat(result.GoalTeamWithUserInfo().get(1).id()).isEqualTo(유효한_골_dto.GoalTeamWithUserInfo().get(1).id()); + }); + } + + @Test + void 존재하지_않는_골_아이디를_조회한_경우_예외를_발생한다() { + // when & then + assertThatThrownBy(() -> goalService.readGoalDetailById(존재하지_않는_골_아이디)) + .isInstanceOf(NotFoundGoalException.class); + } + + @Test + void 현재_로그인한_사용자가_참여한_모든_골_정보를_조회한다() { + // when + final ReadAllGoalDto result = goalService.readAllGoalByUserId(유효한_사용자_아이디); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(result.goalInfoDtos()).hasSize(3); + softAssertions.assertThat(result.goalInfoDtos().get(0).id()).isEqualTo(유효한_골.getId()); + softAssertions.assertThat(result.goalInfoDtos().get(0).name()).isEqualTo(유효한_골.getName()); + softAssertions.assertThat(result.goalInfoDtos().get(0).startDate()).isEqualTo(유효한_골.getGoalTerm().getStartDate()); + softAssertions.assertThat(result.goalInfoDtos().get(0).endDate()).isEqualTo(유효한_골.getGoalTerm().getEndDate()); + softAssertions.assertThat(result.goalInfoDtos().get(0).days()).isEqualTo(유효한_골.getGoalTerm().getDays()); + softAssertions.assertThat(result.goalInfoDtos().get(0).goalTeamWithUserInfoDtos().get(0).id()).isEqualTo(유효한_사용자_아이디); + softAssertions.assertThat(result.goalInfoDtos().get(0).goalTeamWithUserInfoDtos().get(0).name()).isEqualTo(유효한_골.getTeams().get(0).getUser().getName()); + softAssertions.assertThat(result.goalInfoDtos().get(0).goalTeamWithUserInfoDtos().get(0).color()).isEqualTo(유효한_골.getTeams().get(0).getUser().getColor()); + softAssertions.assertThat(result.goalInfoDtos().get(1).id()).isEqualTo(유효한_골2.getId()); + softAssertions.assertThat(result.goalInfoDtos().get(2).id()).isEqualTo(유효한_골3.getId()); + }); + } +} diff --git a/src/test/java/com/backend/blooming/goal/application/GoalServiceTestFixture.java b/src/test/java/com/backend/blooming/goal/application/GoalServiceTestFixture.java new file mode 100644 index 00000000..897766b0 --- /dev/null +++ b/src/test/java/com/backend/blooming/goal/application/GoalServiceTestFixture.java @@ -0,0 +1,192 @@ +package com.backend.blooming.goal.application; + +import com.backend.blooming.authentication.infrastructure.oauth.OAuthType; +import com.backend.blooming.goal.application.dto.CreateGoalDto; +import com.backend.blooming.goal.application.dto.ReadAllGoalDto; +import com.backend.blooming.goal.application.dto.ReadGoalDetailDto; +import com.backend.blooming.goal.domain.Goal; +import com.backend.blooming.goal.infrastructure.repository.GoalRepository; +import com.backend.blooming.goal.presentation.dto.request.CreateGoalRequest; +import com.backend.blooming.themecolor.domain.ThemeColor; +import com.backend.blooming.user.domain.Email; +import com.backend.blooming.user.domain.Name; +import com.backend.blooming.user.domain.User; +import com.backend.blooming.user.infrastructure.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("NonAsciiCharacters") +@DirtiesContext +public class GoalServiceTestFixture { + + @Autowired + private UserRepository userRepository; + + @Autowired + private GoalRepository goalRepository; + + protected Long 유효한_사용자_아이디; + protected String 골_제목 = "골 제목"; + protected String 골_메모 = "골 메모"; + protected LocalDate 골_시작일 = LocalDate.now(); + protected LocalDate 골_종료일 = LocalDate.now().plusDays(40); + protected long 골_날짜수 = 41L; + protected List 골_팀에_등록된_사용자_아이디_목록 = new ArrayList<>(); + protected CreateGoalDto 유효한_골_생성_dto; + protected Goal 유효한_골; + protected Goal 유효한_골2; + protected Goal 유효한_골3; + protected ReadGoalDetailDto 유효한_골_dto; + protected CreateGoalDto 존재하지_않는_사용자가_관리자인_골_생성_dto; + protected CreateGoalDto 존재하지_않는_사용자가_참여자로_있는_골_생성_dto; + protected CreateGoalDto 골_시작날짜가_현재보다_이전인_골_생성_dto; + protected CreateGoalDto 골_종료날짜가_현재보다_이전인_골_생성_dto; + protected CreateGoalDto 골_종료날짜가_시작날짜보다_이전인_골_생성_dto; + protected CreateGoalDto 골_날짜수가_100_초과인_골_생성_dto; + protected Long 존재하지_않는_골_아이디 = 997L; + protected Long 유효한_골_아이디; + protected List 유효한_사용자_목록 = new ArrayList<>(); + protected List 참여한_골_목록 = new ArrayList<>(); + protected ReadAllGoalDto 사용자가_참여한_골_목록; + + @BeforeEach + void setUp() { + User 유효한_사용자; + User 유효한_사용자_2; + Long 존재하지_않는_사용자_아이디 = 998L; + List 존재하지_않는_사용자가_있는_사용자_아이디_목록 = new ArrayList<>(); + CreateGoalRequest 유효한_골_생성_요청_dto; + CreateGoalRequest 존재하지_않는_사용자가_관리자인_골_생성_요청_dto; + + 유효한_사용자 = User.builder() + .oAuthId("아이디") + .oAuthType(OAuthType.KAKAO) + .email(new Email("test@gmail.com")) + .name(new Name("테스트")) + .color(ThemeColor.BABY_BLUE) + .statusMessage("상태메시지") + .build(); + + 유효한_사용자_2 = User.builder() + .oAuthId("아이디2") + .oAuthType(OAuthType.KAKAO) + .email(new Email("test2@gmail.com")) + .name(new Name("테스트2")) + .color(ThemeColor.BABY_BLUE) + .statusMessage("상태메시지2") + .build(); + + userRepository.saveAll(List.of(유효한_사용자, 유효한_사용자_2)); + 유효한_사용자_목록.addAll(List.of(유효한_사용자, 유효한_사용자_2)); + 유효한_사용자_아이디 = 유효한_사용자.getId(); + + 유효한_골 = Goal.builder() + .name(골_제목) + .memo(골_메모) + .startDate(골_시작일) + .endDate(골_종료일) + .managerId(유효한_사용자_아이디) + .users(유효한_사용자_목록) + .build(); + + 유효한_골2 = Goal.builder() + .name("골 제목2") + .memo("골 메모2") + .startDate(골_시작일) + .endDate(LocalDate.now().plusDays(30)) + .managerId(유효한_사용자_아이디) + .users(유효한_사용자_목록) + .build(); + + 유효한_골3 = Goal.builder() + .name("골 제목3") + .memo("골 메모3") + .startDate(골_시작일) + .endDate(LocalDate.now().plusDays(60)) + .managerId(유효한_사용자_아이디) + .users(유효한_사용자_목록) + .build(); + + goalRepository.saveAll(List.of(유효한_골, 유효한_골2, 유효한_골3)); + 유효한_골_아이디 = 유효한_골.getId(); + + 골_팀에_등록된_사용자_아이디_목록.addAll(List.of(유효한_사용자.getId(), 유효한_사용자_2.getId())); + 참여한_골_목록.addAll(List.of(유효한_골, 유효한_골2, 유효한_골3)); + + 유효한_골_생성_요청_dto = new CreateGoalRequest( + 골_제목, + 골_메모, + 골_시작일, + 골_종료일, + 골_팀에_등록된_사용자_아이디_목록 + ); + + 유효한_골_생성_dto = CreateGoalDto.of(유효한_골_생성_요청_dto, 유효한_사용자_아이디); + + 존재하지_않는_사용자가_관리자인_골_생성_요청_dto = new CreateGoalRequest( + 골_제목, + 골_메모, + 골_시작일, + 골_종료일, + 골_팀에_등록된_사용자_아이디_목록 + ); + + 존재하지_않는_사용자가_관리자인_골_생성_dto = CreateGoalDto.of(존재하지_않는_사용자가_관리자인_골_생성_요청_dto, 존재하지_않는_사용자_아이디); + + 존재하지_않는_사용자가_있는_사용자_아이디_목록.add(존재하지_않는_사용자_아이디); + + 존재하지_않는_사용자가_참여자로_있는_골_생성_dto = new CreateGoalDto( + 골_제목, + 골_메모, + 골_시작일, + 골_종료일, + 유효한_사용자_아이디, + 존재하지_않는_사용자가_있는_사용자_아이디_목록 + ); + + 골_시작날짜가_현재보다_이전인_골_생성_dto = new CreateGoalDto( + 골_제목, + 골_메모, + LocalDate.now().minusDays(2), + 골_종료일, + 유효한_사용자_아이디, + 골_팀에_등록된_사용자_아이디_목록 + ); + + 골_종료날짜가_현재보다_이전인_골_생성_dto = new CreateGoalDto( + 골_제목, + 골_메모, + 골_시작일, + LocalDate.now().minusDays(2), + 유효한_사용자_아이디, + 골_팀에_등록된_사용자_아이디_목록 + ); + + 골_종료날짜가_시작날짜보다_이전인_골_생성_dto = new CreateGoalDto( + 골_제목, + 골_메모, + LocalDate.now().plusDays(4), + LocalDate.now().plusDays(2), + 유효한_사용자_아이디, + 골_팀에_등록된_사용자_아이디_목록 + ); + + 골_날짜수가_100_초과인_골_생성_dto = new CreateGoalDto( + 골_제목, + 골_메모, + LocalDate.now(), + LocalDate.now().plusDays(100), + 유효한_사용자_아이디, + 골_팀에_등록된_사용자_아이디_목록 + ); + + 유효한_골_dto = ReadGoalDetailDto.from(유효한_골); + + 사용자가_참여한_골_목록 = ReadAllGoalDto.from(참여한_골_목록); + } +} diff --git a/src/test/java/com/backend/blooming/goal/domain/GoalTermTest.java b/src/test/java/com/backend/blooming/goal/domain/GoalTermTest.java new file mode 100644 index 00000000..c0086126 --- /dev/null +++ b/src/test/java/com/backend/blooming/goal/domain/GoalTermTest.java @@ -0,0 +1,36 @@ +package com.backend.blooming.goal.domain; + +import com.backend.blooming.goal.application.exception.InvalidGoalException; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class GoalTermTest { + + @Test + void 골_시작날짜가_현재보다_이전인_경우_예외를_발생한다() { + // when & then + assertThatThrownBy(() -> new GoalTerm(LocalDate.now().minusDays(2), LocalDate.now().plusDays(2))) + .isInstanceOf(InvalidGoalException.InvalidInvalidGoalStartDay.class); + } + + @Test + void 골_종료날짜가_시작날짜보다_이전인_경우_예외를_발생한다() { + // when & then + assertThatThrownBy(() -> new GoalTerm(LocalDate.now().plusDays(5), LocalDate.now().plusDays(2))) + .isInstanceOf(InvalidGoalException.InvalidInvalidGoalPeriod.class); + } + + @Test + void 골_날짜가_100_초과인_경우_예외를_발생한다() { + // when & then + assertThatThrownBy(() -> new GoalTerm(LocalDate.now(), LocalDate.now().plusDays(100))) + .isInstanceOf(InvalidGoalException.InvalidInvalidGoalDays.class); + } +} diff --git a/src/test/java/com/backend/blooming/goal/domain/GoalTest.java b/src/test/java/com/backend/blooming/goal/domain/GoalTest.java new file mode 100644 index 00000000..bf44bf96 --- /dev/null +++ b/src/test/java/com/backend/blooming/goal/domain/GoalTest.java @@ -0,0 +1,60 @@ +package com.backend.blooming.goal.domain; + +import com.backend.blooming.goal.application.exception.InvalidGoalException; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class GoalTest extends GoalTestFixture { + + @Test + void 골_메모를_빈_값_또는_null로_받은_경우_비어있는_값으로_저장한다() { + // when + final Goal goal = Goal.builder() + .name(골_제목) + .memo("") + .startDate(골_시작일) + .endDate(골_종료일) + .managerId(골_관리자_아이디) + .users(골_참여자_목록) + .build(); + + // then + assertThat(goal.getMemo()).isEmpty(); + } + + @Test + void 골_생성시_골_팀을_생성한다() { + // when + final Goal goal = Goal.builder() + .name(골_제목) + .memo("골 메모") + .startDate(골_시작일) + .endDate(골_종료일) + .managerId(골_관리자_아이디) + .users(골_참여자_목록) + .build(); + + // then + assertThat(goal.getTeams()).hasSize(2); + } + + @Test + void 골_참여자_목록이_5명_초과인_경우_예외를_발생한다() { + // when & then + assertThatThrownBy(() -> Goal.builder() + .name(골_제목) + .memo("골 메모") + .startDate(골_시작일) + .endDate(골_종료일) + .managerId(골_관리자_아이디) + .users(유효하지_않은_골_참여자_목록) + .build()) + .isInstanceOf(InvalidGoalException.InvalidInvalidUsersSize.class); + } +} diff --git a/src/test/java/com/backend/blooming/goal/domain/GoalTestFixture.java b/src/test/java/com/backend/blooming/goal/domain/GoalTestFixture.java new file mode 100644 index 00000000..794bb4d1 --- /dev/null +++ b/src/test/java/com/backend/blooming/goal/domain/GoalTestFixture.java @@ -0,0 +1,45 @@ +package com.backend.blooming.goal.domain; + +import com.backend.blooming.authentication.infrastructure.oauth.OAuthType; +import com.backend.blooming.themecolor.domain.ThemeColor; +import com.backend.blooming.user.domain.Email; +import com.backend.blooming.user.domain.Name; +import com.backend.blooming.user.domain.User; +import org.junit.jupiter.api.BeforeEach; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("NonAsciiCharacters") +public class GoalTestFixture { + + protected String 골_제목 = "골 제목"; + protected LocalDate 골_시작일 = LocalDate.now(); + protected LocalDate 골_종료일 = LocalDate.now().plusDays(40); + protected Long 골_관리자_아이디 = 1L; + protected List 골_참여자_목록 = new ArrayList<>(); + protected List 유효하지_않은_골_참여자_목록 = new ArrayList<>(); + private User 유효한_사용자 = User.builder() + .oAuthId("아이디") + .oAuthType(OAuthType.KAKAO) + .email(new Email("test@gmail.com")) + .name(new Name("테스트")) + .color(ThemeColor.BABY_BLUE) + .statusMessage("상태메시지") + .build(); + private User 유효한_사용자_2 = User.builder() + .oAuthId("아이디2") + .oAuthType(OAuthType.KAKAO) + .email(new Email("test2@gmail.com")) + .name(new Name("테스트2")) + .color(ThemeColor.BABY_BLUE) + .statusMessage("상태메시지2") + .build(); + + @BeforeEach + void setUp() { + 골_참여자_목록.addAll(List.of(유효한_사용자, 유효한_사용자_2)); + 유효하지_않은_골_참여자_목록.addAll(List.of(유효한_사용자, 유효한_사용자, 유효한_사용자, 유효한_사용자, 유효한_사용자, 유효한_사용자)); + } +} diff --git a/src/test/java/com/backend/blooming/goal/infrastructure/repository/GoalRepositoryTest.java b/src/test/java/com/backend/blooming/goal/infrastructure/repository/GoalRepositoryTest.java new file mode 100644 index 00000000..021b4be4 --- /dev/null +++ b/src/test/java/com/backend/blooming/goal/infrastructure/repository/GoalRepositoryTest.java @@ -0,0 +1,55 @@ +package com.backend.blooming.goal.infrastructure.repository; + +import com.backend.blooming.configuration.JpaConfiguration; +import com.backend.blooming.goal.application.exception.NotFoundGoalException; +import com.backend.blooming.goal.domain.Goal; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@DataJpaTest +@Import(JpaConfiguration.class) +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class GoalRepositoryTest extends GoalRepositoryTestFixture { + + @Autowired + private GoalRepository goalRepository; + + @Test + void 요청한_골_아이디에_해당하는_골_정보를_반환한다() { + // when + final Goal result = goalRepository.findByIdAndDeletedIsFalse(유효한_골.getId()) + .orElseThrow(NotFoundGoalException::new); + System.out.println(result.getTeams().get(0).getId()); + System.out.println(result.getTeams().get(0).getGoal()); + System.out.println(result.getTeams().get(0).getUser()); + System.out.println(result.getTeams().get(1)); + + // then + assertThat(result).usingRecursiveComparison().isEqualTo(유효한_골); + assertThat(result.getTeams()).hasSize(2); + } + + @Test + void 요청한_사용자_아이디가_골_참여자로_있는_모든_골을_반환한다() { + // when + final List result = goalRepository.findAllByUserIdAndDeletedIsFalse(골_관리자_사용자.getId()); + + // then + assertSoftly(softAssertions -> { + softAssertions.assertThat(result).hasSize(사용자가_참여한_골_목록.size()); + softAssertions.assertThat(result) + .usingRecursiveComparison() + .isEqualTo(사용자가_참여한_골_목록); + }); + } +} diff --git a/src/test/java/com/backend/blooming/goal/infrastructure/repository/GoalRepositoryTestFixture.java b/src/test/java/com/backend/blooming/goal/infrastructure/repository/GoalRepositoryTestFixture.java new file mode 100644 index 00000000..462372ca --- /dev/null +++ b/src/test/java/com/backend/blooming/goal/infrastructure/repository/GoalRepositoryTestFixture.java @@ -0,0 +1,77 @@ +package com.backend.blooming.goal.infrastructure.repository; + +import com.backend.blooming.authentication.infrastructure.oauth.OAuthType; +import com.backend.blooming.goal.domain.Goal; +import com.backend.blooming.themecolor.domain.ThemeColor; +import com.backend.blooming.user.domain.Email; +import com.backend.blooming.user.domain.Name; +import com.backend.blooming.user.domain.User; +import com.backend.blooming.user.infrastructure.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("NonAsciiCharacters") +public class GoalRepositoryTestFixture { + + @Autowired + private UserRepository userRepository; + + @Autowired + private GoalRepository goalRepository; + + protected User 골_관리자_사용자; + protected User 골에_참여한_사용자; + protected Goal 유효한_골; + protected List 사용자가_참여한_골_목록 = new ArrayList<>(); + private List 골에_참여한_사용자_목록 = new ArrayList<>(); + private Goal 사용자가_참여한_골2; + + @BeforeEach + void setUp() { + 골_관리자_사용자 = User.builder() + .oAuthId("아이디") + .oAuthType(OAuthType.KAKAO) + .email(new Email("test@gmail.com")) + .name(new Name("테스트")) + .color(ThemeColor.BABY_BLUE) + .statusMessage("상태메시지") + .build(); + + 골에_참여한_사용자 = User.builder() + .oAuthId("아이디2") + .oAuthType(OAuthType.KAKAO) + .email(new Email("test2@gmail.com")) + .name(new Name("테스트2")) + .color(ThemeColor.BLUE) + .statusMessage("상태메시지2") + .build(); + + userRepository.saveAll(List.of(골_관리자_사용자, 골에_참여한_사용자)); + 골에_참여한_사용자_목록.addAll(List.of(골_관리자_사용자, 골에_참여한_사용자)); + + 유효한_골 = Goal.builder() + .name("골 제목") + .memo("골 메모") + .startDate(LocalDate.now()) + .endDate(LocalDate.now().plusDays(9)) + .managerId(골_관리자_사용자.getId()) + .users(골에_참여한_사용자_목록) + .build(); + + 사용자가_참여한_골2 = Goal.builder() + .name("골 제목2") + .memo("골 메모2") + .startDate(LocalDate.now()) + .endDate(LocalDate.now().plusDays(19)) + .managerId(골_관리자_사용자.getId()) + .users(골에_참여한_사용자_목록) + .build(); + + goalRepository.saveAll(List.of(유효한_골, 사용자가_참여한_골2)); + 사용자가_참여한_골_목록.addAll(List.of(유효한_골, 사용자가_참여한_골2)); + } +} diff --git a/src/test/java/com/backend/blooming/goal/presentation/GoalControllerTest.java b/src/test/java/com/backend/blooming/goal/presentation/GoalControllerTest.java new file mode 100644 index 00000000..fcebff76 --- /dev/null +++ b/src/test/java/com/backend/blooming/goal/presentation/GoalControllerTest.java @@ -0,0 +1,371 @@ +package com.backend.blooming.goal.presentation; + +import com.backend.blooming.authentication.infrastructure.jwt.TokenProvider; +import com.backend.blooming.authentication.presentation.argumentresolver.AuthenticatedThreadLocal; +import com.backend.blooming.common.RestDocsConfiguration; +import com.backend.blooming.goal.application.GoalService; +import com.backend.blooming.goal.application.exception.InvalidGoalException; +import com.backend.blooming.goal.application.exception.NotFoundGoalException; +import com.backend.blooming.user.application.exception.NotFoundUserException; +import com.backend.blooming.user.infrastructure.repository.UserRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.MockMvc; + +import static org.hamcrest.Matchers.is; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +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.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(GoalController.class) +@AutoConfigureRestDocs +@Import({RestDocsConfiguration.class, AuthenticatedThreadLocal.class}) +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class GoalControllerTest extends GoalControllerTestFixture { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private GoalService goalService; + + @Autowired + private RestDocumentationResultHandler restDocs; + + @MockBean + private TokenProvider tokenProvider; + + @MockBean + private UserRepository userRepository; + + @Test + void 골_생성을_요청하면_새로운_골을_생성한다() throws Exception { + // given + given(tokenProvider.parseToken(액세스_토큰_타입, 액세스_토큰)).willReturn(사용자_토큰_정보); + given(userRepository.existsByIdAndDeletedIsFalse(사용자_토큰_정보.userId())).willReturn(true); + given(goalService.createGoal(유효한_골_생성_dto)).willReturn(유효한_골_아이디); + + // when & then + mockMvc.perform(post("/goals") + .header("X-API-VERSION", 1) + .header(HttpHeaders.AUTHORIZATION, 액세스_토큰) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(요청한_골_dto)) + ).andExpectAll( + status().isCreated(), + redirectedUrl("/goals/" + 유효한_골_아이디) + ).andDo(print()).andDo(restDocs.document( + requestHeaders( + headerWithName("X-API-VERSION").description("요청 버전"), + headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") + ), + requestFields( + fieldWithPath("name").type(JsonFieldType.STRING) + .description("골 제목"), + fieldWithPath("memo").type(JsonFieldType.STRING) + .description("골 메모"), + fieldWithPath("startDate").type(JsonFieldType.STRING) + .description("골 시작날짜"), + fieldWithPath("endDate").type(JsonFieldType.STRING) + .description("골 종료날짜"), + fieldWithPath("teamUserIds").type(JsonFieldType.ARRAY) + .description("골 팀 사용자 아이디") + ) + )); + } + + @Test + void 골_생성시_존재하지_않는_사용자가_참여자로_있는_경우_404_예외를_발생시킨다() throws Exception { + // given + given(tokenProvider.parseToken(액세스_토큰_타입, 액세스_토큰)).willReturn(사용자_토큰_정보); + given(userRepository.existsByIdAndDeletedIsFalse(사용자_토큰_정보.userId())).willReturn(true); + given(goalService.createGoal(존재하지_않는_사용자가_참여자로_있는_골_생성_dto)).willThrow(new NotFoundUserException()); + + // when & then + mockMvc.perform(post("/goals") + .header("X-API-VERSION", 1) + .header(HttpHeaders.AUTHORIZATION, 액세스_토큰) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(존재하지_않는_사용자가_참여자로_있는_골_생성_dto)) + ).andExpectAll( + status().isNotFound(), + jsonPath("$.message").exists() + ).andDo(print()); + } + + @Test + void 골_종료날짜가_시작날짜보다_이전인_경우_403_예외를_발생한다() throws Exception { + // given + given(tokenProvider.parseToken(액세스_토큰_타입, 액세스_토큰)).willReturn(사용자_토큰_정보); + given(userRepository.existsByIdAndDeletedIsFalse(사용자_토큰_정보.userId())).willReturn(true); + given(goalService.createGoal(골_종료날짜가_시작날짜보다_이전인_골_생성_dto)).willThrow(new InvalidGoalException.InvalidInvalidGoalPeriod()); + + // when & then + mockMvc.perform(post("/goals") + .header("X-API-VERSION", 1) + .header(HttpHeaders.AUTHORIZATION, 액세스_토큰) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(골_종료날짜가_시작날짜보다_이전인_골_생성_dto)) + ).andExpectAll( + status().isForbidden(), + jsonPath("$.message").exists() + ).andDo(print()); + } + + @Test + void 골_날짜가_100_초과인_경우_403_예외를_발생한다() throws Exception { + // given + given(tokenProvider.parseToken(액세스_토큰_타입, 액세스_토큰)).willReturn(사용자_토큰_정보); + given(userRepository.existsByIdAndDeletedIsFalse(사용자_토큰_정보.userId())).willReturn(true); + given(goalService.createGoal(골_날짜수가_100_초과인_골_생성_dto)).willThrow(new InvalidGoalException.InvalidInvalidGoalDays()); + + // when & then + mockMvc.perform(post("/goals") + .header("X-API-VERSION", 1) + .header(HttpHeaders.AUTHORIZATION, 액세스_토큰) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(골_날짜수가_100_초과인_골_생성_dto)) + ).andExpectAll( + status().isForbidden(), + jsonPath("$.message").exists() + ).andDo(print()); + } + + @Test + void 골_생성시_사용자_리스트가_5명_초과인_경우_403_예외를_발생한다() throws Exception { + // given + given(tokenProvider.parseToken(액세스_토큰_타입, 액세스_토큰)).willReturn(사용자_토큰_정보); + given(userRepository.existsByIdAndDeletedIsFalse(사용자_토큰_정보.userId())).willReturn(true); + given(goalService.createGoal(참여자_리스트가_5명_초과인_골_생성_dto)).willThrow(new InvalidGoalException.InvalidInvalidUsersSize()); + + // when & then + mockMvc.perform(post("/goals") + .header("X-API-VERSION", 1) + .header(HttpHeaders.AUTHORIZATION, 액세스_토큰) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(참여자_리스트가_5명_초과인_골_생성_dto)) + ).andExpectAll( + status().isForbidden(), + jsonPath("$.message").exists() + ).andDo(print()); + } + + @Test + void 골_아이디로_조회하면_해당_골의_정보를_반환한다() throws Exception { + // given + given(tokenProvider.parseToken(액세스_토큰_타입, 액세스_토큰)).willReturn(사용자_토큰_정보); + given(userRepository.existsByIdAndDeletedIsFalse(사용자_토큰_정보.userId())).willReturn(true); + given(goalService.readGoalDetailById(유효한_골_아이디)).willReturn(유효한_골_dto); + + // when & then + mockMvc.perform(get("/goals/{goalId}", 유효한_골_아이디) + .header("X-API-VERSION", 1) + .header(HttpHeaders.AUTHORIZATION, 액세스_토큰) + ).andExpectAll( + status().isOk(), + jsonPath("$.id", is(유효한_골_응답_dto.id()), Long.class), + jsonPath("$.name", is(유효한_골_응답_dto.name()), String.class), + jsonPath("$.memo", is(유효한_골_응답_dto.memo()), String.class), + jsonPath("$.startDate", is(유효한_골_응답_dto.startDate() + .toString()), String.class), + jsonPath("$.endDate", is(유효한_골_응답_dto.endDate() + .toString()), String.class), + jsonPath("$.days", is(유효한_골_응답_dto.days()), long.class), + jsonPath("$.managerId", is(유효한_골_응답_dto.managerId()), Long.class), + jsonPath("$.goalTeamWithUserInfo.[0].id", is(유효한_골_응답_dto.goalTeamWithUserInfo() + .get(0) + .id()), Long.class), + jsonPath("$.goalTeamWithUserInfo.[0].name", is(유효한_골_응답_dto.goalTeamWithUserInfo() + .get(0) + .name()), String.class), + jsonPath("$.goalTeamWithUserInfo.[0].colorCode", is(유효한_골_응답_dto.goalTeamWithUserInfo() + .get(0) + .colorCode()), String.class), + jsonPath("$.goalTeamWithUserInfo.[0].statusMessage", is(유효한_골_응답_dto.goalTeamWithUserInfo() + .get(0) + .statusMessage()), String.class), + jsonPath("$.goalTeamWithUserInfo.[1].id", is(유효한_골_응답_dto.goalTeamWithUserInfo() + .get(1) + .id()), Long.class) + ).andDo(print()).andDo(restDocs.document( + pathParameters(parameterWithName("goalId").description("조회할 골 아이디")), + requestHeaders( + headerWithName("X-API-VERSION").description("요청 버전"), + headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") + ), + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER) + .description("골 아이디"), + fieldWithPath("name").type(JsonFieldType.STRING) + .description("골 제목"), + fieldWithPath("memo").type(JsonFieldType.STRING) + .description("골 메모"), + fieldWithPath("startDate").type(JsonFieldType.STRING) + .description("골 시작날짜"), + fieldWithPath("endDate").type(JsonFieldType.STRING) + .description("골 종료날짜"), + fieldWithPath("days").type(JsonFieldType.NUMBER) + .description("골 날짜 수"), + fieldWithPath("managerId").type(JsonFieldType.NUMBER) + .description("골 관리자 아이디"), + fieldWithPath("goalTeamWithUserInfo.[].id").type(JsonFieldType.NUMBER) + .description("골 참여자 아이디"), + fieldWithPath("goalTeamWithUserInfo.[].name").type(JsonFieldType.STRING) + .description("골 참여자 이름"), + fieldWithPath("goalTeamWithUserInfo.[].colorCode").type(JsonFieldType.STRING) + .description("골 참여자 색상"), + fieldWithPath("goalTeamWithUserInfo.[].statusMessage").type(JsonFieldType.STRING) + .description("골 참여자 상태메시지") + ) + )); + } + + @Test + void 존재하지_않는_골을_조회했을_때_404_예외를_발생한다() throws Exception { + // given + given(tokenProvider.parseToken(액세스_토큰_타입, 액세스_토큰)).willReturn(사용자_토큰_정보); + given(userRepository.existsByIdAndDeletedIsFalse(사용자_토큰_정보.userId())).willReturn(true); + given(goalService.readGoalDetailById(유효한_골_아이디)).willThrow(new NotFoundGoalException()); + + // when & then + mockMvc.perform(get("/goals/{goalId}", 유효한_골_아이디) + .header("X-API-VERSION", 1) + .header(HttpHeaders.AUTHORIZATION, 액세스_토큰) + ).andExpectAll( + status().isNotFound(), + jsonPath("$.message").exists() + ).andDo(print()); + } + + @Test + void 현재_로그인한_사용자가_참여한_모든_골을_조회한다() throws Exception { + // given + given(tokenProvider.parseToken(액세스_토큰_타입, 액세스_토큰)).willReturn(사용자_토큰_정보); + given(userRepository.existsByIdAndDeletedIsFalse(사용자_토큰_정보.userId())).willReturn(true); + given(goalService.readAllGoalByUserId(사용자_토큰_정보.userId())).willReturn(사용자가_참여한_모든_골_목록_dto); + + // when & then + mockMvc.perform(get("/goals/all") + .header("X-API-VERSION", 1) + .header(HttpHeaders.AUTHORIZATION, 액세스_토큰) + ).andExpectAll( + status().isOk(), + jsonPath("$.goals.[0].id", is(사용자가_참여한_모든_골_목록_응답_dto.goals() + .get(0) + .id()), Long.class), + jsonPath("$.goals.[0].name", is(사용자가_참여한_모든_골_목록_응답_dto.goals() + .get(0) + .name()), String.class), + jsonPath("$.goals.[0].startDate", is(사용자가_참여한_모든_골_목록_응답_dto.goals() + .get(0) + .startDate() + .toString()), String.class), + jsonPath("$.goals.[0].endDate", is(사용자가_참여한_모든_골_목록_응답_dto.goals() + .get(0) + .endDate() + .toString()), String.class), + jsonPath("$.goals.[0].days", is(사용자가_참여한_모든_골_목록_응답_dto.goals() + .get(0) + .days()), long.class), + jsonPath("$.goals.[0].goalTeamWithUserInfos.[0].id", is(사용자가_참여한_모든_골_목록_응답_dto.goals() + .get(0) + .goalTeamWithUserInfos() + .get(0) + .id()), Long.class), + jsonPath("$.goals.[0].goalTeamWithUserInfos.[0].name", is(사용자가_참여한_모든_골_목록_응답_dto.goals() + .get(0) + .goalTeamWithUserInfos() + .get(0) + .name()), String.class), + jsonPath("$.goals.[0].goalTeamWithUserInfos.[0].colorCode", is(사용자가_참여한_모든_골_목록_응답_dto.goals() + .get(0) + .goalTeamWithUserInfos() + .get(0) + .colorCode()), String.class), + jsonPath("$.goals.[0].goalTeamWithUserInfos.[1].id", is(사용자가_참여한_모든_골_목록_응답_dto.goals() + .get(0) + .goalTeamWithUserInfos() + .get(1) + .id()), Long.class), + jsonPath("$.goals.[0].goalTeamWithUserInfos.[1].name", is(사용자가_참여한_모든_골_목록_응답_dto.goals() + .get(0) + .goalTeamWithUserInfos() + .get(1) + .name()), String.class), + jsonPath("$.goals.[0].goalTeamWithUserInfos.[1].colorCode", is(사용자가_참여한_모든_골_목록_응답_dto.goals() + .get(0) + .goalTeamWithUserInfos() + .get(1) + .colorCode()), String.class), + jsonPath("$.goals.[1].id", is(사용자가_참여한_모든_골_목록_응답_dto.goals() + .get(1) + .id()), Long.class) + ).andDo(print()).andDo(restDocs.document( + requestHeaders( + headerWithName("X-API-VERSION").description("요청 버전"), + headerWithName(HttpHeaders.AUTHORIZATION).description("액세스 토큰") + ), + responseFields( + fieldWithPath("goals.[].id").type(JsonFieldType.NUMBER) + .description("골 아이디"), + fieldWithPath("goals.[].name").type(JsonFieldType.STRING) + .description("골 제목"), + fieldWithPath("goals.[].startDate").type(JsonFieldType.STRING) + .description("골 시작날짜"), + fieldWithPath("goals.[].endDate").type(JsonFieldType.STRING) + .description("골 종료날짜"), + fieldWithPath("goals.[].days").type(JsonFieldType.NUMBER) + .description("골 날짜 수"), + fieldWithPath("goals.[].goalTeamWithUserInfos.[].id").type(JsonFieldType.NUMBER) + .description("골 참여자 아이디"), + fieldWithPath("goals.[].goalTeamWithUserInfos.[].name").type(JsonFieldType.STRING) + .description("골 참여자 이름"), + fieldWithPath("goals.[].goalTeamWithUserInfos.[].colorCode").type(JsonFieldType.STRING) + .description("골 참여자 색상") + ) + )); + } + + @Test + void 현재_로그인한_사용자가_참여한_골_중_존재하지_않는_골을_조회했을_때_404_예외를_발생한다() throws Exception { + // given + given(tokenProvider.parseToken(액세스_토큰_타입, 액세스_토큰)).willReturn(사용자_토큰_정보); + given(userRepository.existsByIdAndDeletedIsFalse(사용자_토큰_정보.userId())).willReturn(true); + given(goalService.readAllGoalByUserId(사용자_토큰_정보.userId())).willThrow(new NotFoundGoalException()); + + // when & then + mockMvc.perform(get("/goals/all") + .header("X-API-VERSION", 1) + .header(HttpHeaders.AUTHORIZATION, 액세스_토큰) + ).andExpectAll( + status().isNotFound(), + jsonPath("$.message").exists() + ).andDo(print()); + } +} diff --git a/src/test/java/com/backend/blooming/goal/presentation/GoalControllerTestFixture.java b/src/test/java/com/backend/blooming/goal/presentation/GoalControllerTestFixture.java new file mode 100644 index 00000000..de4860f1 --- /dev/null +++ b/src/test/java/com/backend/blooming/goal/presentation/GoalControllerTestFixture.java @@ -0,0 +1,123 @@ +package com.backend.blooming.goal.presentation; + +import com.backend.blooming.authentication.infrastructure.jwt.TokenType; +import com.backend.blooming.authentication.infrastructure.jwt.dto.AuthClaims; +import com.backend.blooming.goal.application.dto.CreateGoalDto; +import com.backend.blooming.goal.application.dto.ReadAllGoalDto; +import com.backend.blooming.goal.application.dto.ReadGoalDetailDto; +import com.backend.blooming.goal.presentation.dto.request.CreateGoalRequest; +import com.backend.blooming.goal.presentation.dto.response.ReadAllGoalResponse; +import com.backend.blooming.goal.presentation.dto.response.ReadGoalResponse; +import com.backend.blooming.themecolor.domain.ThemeColor; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("NonAsciiCharacters") +public class GoalControllerTestFixture { + + private String 골_제목 = "골 제목"; + private String 골_메모 = "골 메모"; + private LocalDate 골_시작일 = LocalDate.now(); + private LocalDate 골_종료일 = LocalDate.now().plusDays(40); + private Long 골_관리자_아이디 = 1L; + private List 골_팀에_등록된_사용자_아이디_목록 = new ArrayList<>(List.of(1L, 2L, 3L)); + private List 존재하지_않는_사용자가_있는_사용자_아이디_목록 = new ArrayList<>(List.of(골_관리자_아이디, 999L)); + private List 유효하지_않은_골_참여_사용자_아이디_목록 = new ArrayList<>(List.of(1L, 2L, 3L, 4L, 5L, 6L)); + protected AuthClaims 사용자_토큰_정보 = new AuthClaims(골_관리자_아이디); + protected TokenType 액세스_토큰_타입 = TokenType.ACCESS; + protected String 액세스_토큰 = "Bearer access_token"; + protected Long 유효한_골_아이디 = 1L; + protected CreateGoalRequest 유효한_골_생성_요청_dto = new CreateGoalRequest( + 골_제목, + 골_메모, + 골_시작일, + 골_종료일, + 골_팀에_등록된_사용자_아이디_목록 + ); + + protected CreateGoalDto 유효한_골_생성_dto = CreateGoalDto.of(유효한_골_생성_요청_dto, 골_관리자_아이디); + + protected CreateGoalRequest 존재하지_않는_사용자가_참여자로_있는_골_생성_요청_dto = new CreateGoalRequest( + 골_제목, + 골_메모, + 골_시작일, + 골_종료일, + 존재하지_않는_사용자가_있는_사용자_아이디_목록 + ); + + protected CreateGoalDto 존재하지_않는_사용자가_참여자로_있는_골_생성_dto = CreateGoalDto.of(존재하지_않는_사용자가_참여자로_있는_골_생성_요청_dto, 골_관리자_아이디); + + protected CreateGoalDto 골_시작날짜가_현재보다_이전인_골_생성_dto = new CreateGoalDto( + 골_제목, + 골_메모, + LocalDate.now().minusDays(2), + 골_종료일, + 골_관리자_아이디, + 골_팀에_등록된_사용자_아이디_목록 + ); + protected CreateGoalDto 골_종료날짜가_현재보다_이전인_골_생성_dto = new CreateGoalDto( + 골_제목, + 골_메모, + 골_시작일, + LocalDate.now().minusDays(2), + 골_관리자_아이디, + 골_팀에_등록된_사용자_아이디_목록 + ); + protected CreateGoalDto 골_종료날짜가_시작날짜보다_이전인_골_생성_dto = new CreateGoalDto( + 골_제목, + 골_메모, + LocalDate.now().plusDays(4), + LocalDate.now().plusDays(2), + 골_관리자_아이디, + 골_팀에_등록된_사용자_아이디_목록 + ); + protected CreateGoalDto 골_날짜수가_100_초과인_골_생성_dto = new CreateGoalDto( + 골_제목, + 골_메모, + LocalDate.now(), + LocalDate.now().plusDays(100), + 골_관리자_아이디, + 골_팀에_등록된_사용자_아이디_목록 + ); + protected CreateGoalDto 참여자_리스트가_5명_초과인_골_생성_dto = new CreateGoalDto( + 골_제목, + 골_메모, + LocalDate.now(), + LocalDate.now().plusDays(40), + 골_관리자_아이디, + 유효하지_않은_골_참여_사용자_아이디_목록 + ); + protected CreateGoalRequest 요청한_골_dto = new CreateGoalRequest( + 골_제목, + 골_메모, + 골_시작일, + 골_종료일, + 골_팀에_등록된_사용자_아이디_목록 + ); + + protected ReadGoalDetailDto.GoalTeamWithUserInfoDto 골_참여자1 = new ReadGoalDetailDto.GoalTeamWithUserInfoDto(1L, "테스트 유저1", ThemeColor.BABY_PINK, "테스트 상태메시지1"); + protected ReadGoalDetailDto.GoalTeamWithUserInfoDto 골_참여자2 = new ReadGoalDetailDto.GoalTeamWithUserInfoDto(2L, "테스트 유저2", ThemeColor.BABY_BLUE, "테스트 상태메시지2"); + protected ReadGoalResponse.GoalTeamWithUserInfoResponse 골_참여자_응답1 = new ReadGoalResponse.GoalTeamWithUserInfoResponse(1L, "테스트 유저1", "#f8c8c4", "테스트 상태메시지1"); + protected ReadGoalResponse.GoalTeamWithUserInfoResponse 골_참여자_응답2 = new ReadGoalResponse.GoalTeamWithUserInfoResponse(2L, "테스트 유저2", "#a1b3d7", "테스트 상태메시지2"); + protected ReadGoalDetailDto 유효한_골_dto = new ReadGoalDetailDto(1L, "테스트 골1", "테스트 골 메모1", LocalDate.now(), LocalDate.now() + .plusDays(10), 11, 1L, List.of(골_참여자1, 골_참여자2)); + protected ReadGoalResponse 유효한_골_응답_dto = new ReadGoalResponse(1L, "테스트 골1", "테스트 골 메모1", LocalDate.now(), LocalDate.now() + .plusDays(10), 11, 1L, List.of(골_참여자_응답1, 골_참여자_응답2)); + + protected ReadAllGoalDto.GoalInfoDto.GoalTeamWithUserInfoDto 골_참여자_정보1 = new ReadAllGoalDto.GoalInfoDto.GoalTeamWithUserInfoDto(1L, "테스트 유저1", ThemeColor.BABY_PINK); + protected ReadAllGoalDto.GoalInfoDto.GoalTeamWithUserInfoDto 골_참여자_정보2 = new ReadAllGoalDto.GoalInfoDto.GoalTeamWithUserInfoDto(2L, "테스트 유저2", ThemeColor.BABY_BLUE); + protected ReadAllGoalDto.GoalInfoDto 유효한_골_정보_dto1 = new ReadAllGoalDto.GoalInfoDto(1L, "테스트 골1", LocalDate.now(), LocalDate.now() + .plusDays(10), 11, List.of(골_참여자_정보1, 골_참여자_정보2)); + protected ReadAllGoalDto.GoalInfoDto 유효한_골_정보_dto2 = new ReadAllGoalDto.GoalInfoDto(2L, "테스트 골2", LocalDate.now(), LocalDate.now() + .plusDays(20), 21, List.of(골_참여자_정보1, 골_참여자_정보2)); + protected ReadAllGoalResponse.GoalTeamWithUserInfoResponse 골_참여자_정보_응답1 = new ReadAllGoalResponse.GoalTeamWithUserInfoResponse(1L, "테스트 유저1", "#f8c8c4"); + protected ReadAllGoalResponse.GoalTeamWithUserInfoResponse 골_참여자_정보_응답2 = new ReadAllGoalResponse.GoalTeamWithUserInfoResponse(2L, "테스트 유저2", "#a1b3d7"); + protected ReadAllGoalResponse.GoalInfoResponse 유효한_골_정보_응답_dto1 = new ReadAllGoalResponse.GoalInfoResponse(1L, "테스트 골1", LocalDate.now(), LocalDate.now() + .plusDays(10), 11, List.of(골_참여자_정보_응답1, 골_참여자_정보_응답2)); + protected ReadAllGoalResponse.GoalInfoResponse 유효한_골_정보_응답_dto2 = new ReadAllGoalResponse.GoalInfoResponse(2L, "테스트 골2", LocalDate.now(), LocalDate.now() + .plusDays(20), 21, List.of(골_참여자_정보_응답1, 골_참여자_정보_응답2)); + protected ReadAllGoalDto 사용자가_참여한_모든_골_목록_dto = new ReadAllGoalDto(List.of(유효한_골_정보_dto1, 유효한_골_정보_dto2)); + protected ReadAllGoalResponse 사용자가_참여한_모든_골_목록_응답_dto = new ReadAllGoalResponse(List.of(유효한_골_정보_응답_dto1, 유효한_골_정보_응답_dto2)); +}