-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
게시글 조기 마감 기능 구현 #115
게시글 조기 마감 기능 구현 #115
Changes from all commits
24057e5
e65bfab
1d93dd7
8ab5bf2
c1692e2
03fd8bd
7b5e0b7
50380e4
9b775e3
b4c9b43
e0f4990
bc66a7b
21d10f3
34e5aa7
3f0f28a
b8d8b56
82c227e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -19,6 +19,7 @@ | |||||
import jakarta.persistence.JoinColumn; | ||||||
import jakarta.persistence.ManyToOne; | ||||||
import jakarta.persistence.OneToMany; | ||||||
import java.time.Duration; | ||||||
import java.time.LocalDateTime; | ||||||
import java.util.ArrayList; | ||||||
import java.util.List; | ||||||
|
@@ -106,10 +107,6 @@ private List<PostOption> toPostOptions( | |||||
.toList(); | ||||||
} | ||||||
|
||||||
public boolean hasPostOption(final PostOption postOption) { | ||||||
return postOptions.contains(postOption); | ||||||
} | ||||||
|
||||||
public void validateDeadlineNotExceedByMaximumDeadline(final int maximumDeadline) { | ||||||
LocalDateTime maximumDeadlineFromNow = LocalDateTime.now().plusDays(maximumDeadline); | ||||||
if (this.deadline.isAfter(maximumDeadlineFromNow)) { | ||||||
|
@@ -118,15 +115,11 @@ public void validateDeadlineNotExceedByMaximumDeadline(final int maximumDeadline | |||||
} | ||||||
|
||||||
public void validateWriter(final Member member) { | ||||||
if (!Objects.equals(this.writer.getId(), member.getId())) { | ||||||
if (!Objects.equals(this.writer, member)) { | ||||||
throw new BadRequestException(PostExceptionType.NOT_WRITER); | ||||||
} | ||||||
} | ||||||
|
||||||
public boolean isClosed() { | ||||||
return deadline.isBefore(LocalDateTime.now()); | ||||||
} | ||||||
|
||||||
public Vote makeVote(final Member voter, final PostOption postOption) { | ||||||
validateDeadLine(); | ||||||
validateVoter(voter); | ||||||
|
@@ -140,26 +133,43 @@ public Vote makeVote(final Member voter, final PostOption postOption) { | |||||
return vote; | ||||||
} | ||||||
|
||||||
public void validateDeadLine() { | ||||||
if (isClosed()) { | ||||||
throw new BadRequestException(PostExceptionType.POST_CLOSED); | ||||||
} | ||||||
} | ||||||
|
||||||
private boolean isClosed() { | ||||||
return deadline.isBefore(LocalDateTime.now()); | ||||||
} | ||||||
|
||||||
private void validateVoter(final Member voter) { | ||||||
if (Objects.equals(this.writer.getId(), voter.getId())) { | ||||||
throw new BadRequestException(PostExceptionType.NOT_VOTER); | ||||||
} | ||||||
} | ||||||
|
||||||
private void validateDeadLine() { | ||||||
if (isClosed()) { | ||||||
throw new IllegalStateException("게시글이 이미 마감되었습니다."); | ||||||
private void validatePostOption(final PostOption postOption) { | ||||||
if (!hasPostOption(postOption)) { | ||||||
throw new BadRequestException(PostExceptionType.POST_OPTION_NOT_FOUND); | ||||||
} | ||||||
} | ||||||
|
||||||
private void validatePostOption(final PostOption postOption) { | ||||||
if (!hasPostOption(postOption)) { | ||||||
throw new IllegalArgumentException("해당 게시글에서 존재하지 않는 선택지 입니다."); | ||||||
private boolean hasPostOption(final PostOption postOption) { | ||||||
return postOptions.contains(postOption); | ||||||
} | ||||||
|
||||||
public void validateHalfDeadLine() { | ||||||
final Duration betweenDuration = Duration.between(getCreatedAt(), this.deadline); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duration...처음 봤습니다 이런 기능이 있었군요! |
||||||
final LocalDateTime midpoint = getCreatedAt().plus(betweenDuration.dividedBy(2)); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
변수명을 조금 더 명확하게 해도 좋을 것 같아요 :) |
||||||
|
||||||
if (midpoint.isAfter(LocalDateTime.now())) { | ||||||
throw new BadRequestException(PostExceptionType.POST_NOT_HALF_DEADLINE); | ||||||
} | ||||||
} | ||||||
|
||||||
public boolean isWriter(final Member member) { | ||||||
return Objects.equals(this.writer, member); | ||||||
public void closeEarly() { | ||||||
this.deadline = LocalDateTime.now(); | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
|
||||||
public void addContentImage(final String contentImageUrl) { | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,9 +10,12 @@ public enum PostExceptionType implements ExceptionType { | |
POST_OPTION_NOT_FOUND(1001, "해당 게시글 투표 옵션이 존재하지 않습니다."), | ||
UNRELATED_POST_OPTION(1002, "게시글 투표 옵션이 게시글과 연관되어 있지 않습니다."), | ||
NOT_WRITER(1003, "해당 게시글 작성자가 아닙니다."), | ||
POST_CLOSED(1004, "게시글이 이미 마감되었습니다."), | ||
POST_NOT_HALF_DEADLINE(1005, "게시글이 마감 시간까지 절반의 시간 이상이 지나지 않으면 조기마감을 할 수 없습니다."), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
NOT_VOTER(1004, "해당 게시글 작성자는 투표할 수 없습니다."), | ||
DEADLINE_EXCEED_THREE_DAYS(1005, "마감 기한은 현재 시간으로부터 3일을 초과할 수 없습니다."), | ||
WRONG_IMAGE(1006, "이미지 저장에 실패했습니다. 다시 시도해주세요."); | ||
WRONG_IMAGE(1006, "이미지 저장에 실패했습니다. 다시 시도해주세요."), | ||
; | ||
|
||
private final int code; | ||
private final String message; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -213,4 +213,15 @@ private String groupAgeRange(final String ageRange) { | |
return ageRange; | ||
} | ||
|
||
@Transactional | ||
public void closePostEarlyById(final Long id, final Member loginMember) { | ||
final Post post = postRepository.findById(id) | ||
.orElseThrow(() -> new IllegalArgumentException("해당 게시글은 존재하지 않습니다.")); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
위의 정보에 대한 검증이 필요해 보입니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 동의합니다 특히나 3번 검증 과정은 정책상 필수적이라는 생각이 드네요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금까지 예외 처리에 대한 부분은 고민하지 않고 구현을 했었는데, 이제 Exception 구조가 자리 잡혔으니 바로 적용해보도록 하겠습니다. |
||
post.validateWriter(loginMember); | ||
post.validateDeadLine(); | ||
post.validateHalfDeadLine(); | ||
post.closeEarly(); | ||
Comment on lines
+221
to
+224
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -297,4 +297,20 @@ void getVoteOptionStatistics() { | |
assertThat(result).usingRecursiveComparison().isEqualTo(response); | ||
} | ||
|
||
@Test | ||
@DisplayName("게시글을 조기 마감 합니다") | ||
void postClosedEarly() { | ||
// given | ||
long postId = 1L; | ||
|
||
// when | ||
ExtractableResponse<MockMvcResponse> response = RestAssuredMockMvc.given().log().all() | ||
.when().patch("/posts/{postId}/close", postId) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍🏻 |
||
.then().log().all() | ||
.extract(); | ||
|
||
// then | ||
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
package com.votogether.domain.post.entity; | ||
|
||
import static com.votogether.fixtures.MemberFixtures.MALE_30; | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatNoException; | ||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
|
@@ -14,6 +13,7 @@ | |
import com.votogether.fixtures.MemberFixtures; | ||
import java.nio.charset.StandardCharsets; | ||
import java.time.LocalDateTime; | ||
import java.time.temporal.ChronoUnit; | ||
import java.util.List; | ||
import java.util.stream.Stream; | ||
import org.junit.jupiter.api.DisplayName; | ||
|
@@ -103,41 +103,79 @@ void throwExceptionIsWriter() { | |
} | ||
|
||
@Test | ||
@DisplayName("게시글의 작성자 여부를 확인한다.") | ||
void isWriter() { | ||
@DisplayName("게시글의 마감 여부에 따라 예외를 던질 지 결정한다.") | ||
void throwExceptionIsDeadlinePassed() { | ||
// given | ||
Post post = Post.builder() | ||
.writer(MALE_30.get()) | ||
final Member writer = MemberFixtures.MALE_30.get(); | ||
ReflectionTestUtils.setField(writer, "id", 1L); | ||
|
||
Post post1 = Post.builder() | ||
.writer(writer) | ||
.deadline(LocalDateTime.of(2000, 1, 1, 1, 1)) | ||
.build(); | ||
|
||
// when | ||
boolean result1 = post.isWriter(MALE_30.get()); | ||
Post post2 = Post.builder() | ||
.writer(writer) | ||
.deadline(LocalDateTime.of(9999, 1, 1, 1, 1)) | ||
.build(); | ||
|
||
// then | ||
assertThat(result1).isTrue(); | ||
// when, then | ||
assertAll( | ||
() -> assertThatThrownBy(post1::validateDeadLine) | ||
.isInstanceOf(BadRequestException.class) | ||
.hasMessage(PostExceptionType.POST_CLOSED.getMessage()), | ||
() -> assertThatNoException() | ||
.isThrownBy(post2::validateDeadLine) | ||
); | ||
} | ||
|
||
@Test | ||
@DisplayName("게시글의 마감 여부를 확인한다.") | ||
void isClosed() { | ||
@DisplayName("게시글의 마감까지 절반의 시간을 넘겼는 지에 따라 예외를 던질 지 결정한다.") | ||
void throwExceptionIsHalfToTheDeadline() { | ||
// given | ||
Post postA = Post.builder() | ||
.deadline(LocalDateTime.of(2022, 1, 1, 0, 0)) | ||
final Member writer = MemberFixtures.MALE_30.get(); | ||
ReflectionTestUtils.setField(writer, "id", 1L); | ||
|
||
Post post1 = Post.builder() | ||
.writer(writer) | ||
.deadline(LocalDateTime.of(9999, 1, 1, 1, 1)) | ||
.build(); | ||
ReflectionTestUtils.setField(post1, "createdAt", LocalDateTime.now()); | ||
|
||
Post post2 = Post.builder() | ||
.writer(writer) | ||
.deadline(LocalDateTime.now().plus(100, ChronoUnit.MILLIS)) | ||
.build(); | ||
ReflectionTestUtils.setField(post2, "createdAt", LocalDateTime.now()); | ||
|
||
// when, then | ||
assertAll( | ||
() -> assertThatThrownBy(post1::validateHalfDeadLine) | ||
.isInstanceOf(BadRequestException.class) | ||
.hasMessage(PostExceptionType.POST_NOT_HALF_DEADLINE.getMessage()), | ||
() -> { | ||
Thread.sleep(50); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
assertThatNoException() | ||
.isThrownBy(post2::validateHalfDeadLine); | ||
|
||
Post postB = Post.builder() | ||
.deadline(LocalDateTime.of(3222, 1, 1, 0, 0)) | ||
} | ||
); | ||
} | ||
|
||
@Test | ||
@DisplayName("해당 게시글을 조기 마감 합니다.") | ||
void closedEarly() { | ||
// given | ||
LocalDateTime deadline = LocalDateTime.of(2100, 1, 1, 0, 0); | ||
Post post = Post.builder() | ||
.deadline(deadline) | ||
.build(); | ||
|
||
// when | ||
boolean resultA = postA.isClosed(); | ||
boolean resultB = postB.isClosed(); | ||
post.closeEarly(); | ||
|
||
// then | ||
assertAll( | ||
() -> assertThat(resultA).isTrue(), | ||
() -> assertThat(resultB).isFalse() | ||
); | ||
assertThat(post.getDeadline()).isBefore(deadline); | ||
} | ||
|
||
} |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3
크게 중요하진 않지만 BadRequestException인데 NotFound를 던지고 있으니 개인적으로는 어색하다는 생각이 듭니다.
NonExistPostOption등의 네이밍으로 변경해보는 것은 어떨까요?