Skip to content

Commit

Permalink
Merge branch 'develop' into feature/#538-member-profile-image-change
Browse files Browse the repository at this point in the history
  • Loading branch information
s6m1n committed Feb 5, 2025
2 parents ee371e3 + 23e10cb commit 5f4a792
Show file tree
Hide file tree
Showing 3 changed files with 296 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.staccato.comment.controller.docs.CommentControllerDocs;
import com.staccato.comment.service.CommentService;
import com.staccato.comment.service.dto.request.CommentRequest;
import com.staccato.comment.service.dto.request.CommentRequestV2;
import com.staccato.comment.service.dto.request.CommentUpdateRequest;
import com.staccato.comment.service.dto.response.CommentResponses;
import com.staccato.config.auth.LoginMember;
Expand All @@ -42,6 +43,23 @@ public ResponseEntity<Void> createComment(
.build();
}

@PostMapping("/v2")
public ResponseEntity<Void> createComment(
@LoginMember Member member,
@Valid @RequestBody CommentRequestV2 commentRequestV2
) {
long commentId = commentService.createComment(toCommentRequest(commentRequestV2), member);
return ResponseEntity.created(URI.create("/comments/" + commentId))
.build();
}

private CommentRequest toCommentRequest(CommentRequestV2 commentRequestV2) {
return new CommentRequest(
commentRequestV2.staccatoId(),
commentRequestV2.content()
);
}

@GetMapping
public ResponseEntity<CommentResponses> readCommentsByMomentId(
@LoginMember Member member,
Expand All @@ -51,6 +69,15 @@ public ResponseEntity<CommentResponses> readCommentsByMomentId(
return ResponseEntity.ok().body(commentResponses);
}

@GetMapping("/v2")
public ResponseEntity<CommentResponses> readCommentsByStaccatoId(
@LoginMember Member member,
@RequestParam @Min(value = 1L, message = "스타카토 식별자는 양수로 이루어져야 합니다.") long staccatoId
) {
CommentResponses commentResponses = commentService.readAllCommentsByMomentId(member, staccatoId);
return ResponseEntity.ok().body(commentResponses);
}

@PutMapping("/{commentId}")
public ResponseEntity<Void> updateComment(
@LoginMember Member member,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.staccato.comment.service.dto.request;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

import com.staccato.comment.domain.Comment;
import com.staccato.member.domain.Member;
import com.staccato.moment.domain.Moment;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "댓글 생성 시 요청 형식입니다.")
public record CommentRequestV2(
@Schema(example = "1")
@NotNull(message = "스타카토를 선택해주세요.")
@Min(value = 1L, message = "스타카토 식별자는 양수로 이루어져야 합니다.")
Long staccatoId,
@Schema(example = "예시 댓글 내용")
@NotBlank(message = "댓글 내용을 입력해주세요.")
@Size(max = 500, message = "댓글은 공백 포함 500자 이하로 입력해주세요.")
String content
) {
public Comment toComment(Moment moment, Member member) {
return Comment.builder()
.content(content)
.moment(moment)
.member(member)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
package com.staccato.comment.controller;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.util.List;
import java.util.stream.Stream;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

import com.staccato.ControllerTest;
import com.staccato.comment.service.dto.request.CommentRequestV2;
import com.staccato.comment.service.dto.request.CommentUpdateRequest;
import com.staccato.comment.service.dto.response.CommentResponse;
import com.staccato.comment.service.dto.response.CommentResponses;
import com.staccato.exception.ExceptionResponse;
import com.staccato.fixture.Member.MemberFixture;
import com.staccato.fixture.comment.CommentUpdateRequestFixture;

public class CommentControllerV2Test extends ControllerTest {
private static final int MAX_CONTENT_LENGTH = 500;
private static final long MIN_STACCATO_ID = 1L;

static Stream<Arguments> invalidCommentRequestProvider() {
return Stream.of(
Arguments.of(
new CommentRequestV2(null, "예시 댓글 내용"),
"스타카토를 선택해주세요."
),
Arguments.of(
new CommentRequestV2(MIN_STACCATO_ID - 1, "예시 댓글 내용"),
"스타카토 식별자는 양수로 이루어져야 합니다."
),
Arguments.of(
new CommentRequestV2(MIN_STACCATO_ID, null),
"댓글 내용을 입력해주세요."
),
Arguments.of(
new CommentRequestV2(MIN_STACCATO_ID, ""),
"댓글 내용을 입력해주세요."
),
Arguments.of(
new CommentRequestV2(MIN_STACCATO_ID, " "),
"댓글 내용을 입력해주세요."
),
Arguments.of(
new CommentRequestV2(MIN_STACCATO_ID, "1".repeat(MAX_CONTENT_LENGTH + 1)),
"댓글은 공백 포함 500자 이하로 입력해주세요."
)
);
}

@DisplayName("댓글 생성 요청/응답에 대한 직렬화/역직렬화에 성공한다.")
@Test
void createComment() throws Exception {
// given
when(authService.extractFromToken(any())).thenReturn(MemberFixture.create());
String commentRequest = """
{
"staccatoId": 1,
"content": "content"
}
""";
when(commentService.createComment(any(), any())).thenReturn(1L);

// when & then
mockMvc.perform(post("/comments/v2")
.contentType(MediaType.APPLICATION_JSON)
.content(commentRequest)
.header(HttpHeaders.AUTHORIZATION, "token"))
.andExpect(status().isCreated())
.andExpect(header().string(HttpHeaders.LOCATION, "/comments/1"));
}

@DisplayName("올바르지 않은 형식으로 정보를 입력하면, 댓글을 생성할 수 없다.")
@ParameterizedTest
@MethodSource("invalidCommentRequestProvider")
void createCommentFail(CommentRequestV2 commentRequestV2, String expectedMessage) throws Exception {
// given
when(authService.extractFromToken(any())).thenReturn(MemberFixture.create());
ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), expectedMessage);

// when & then
mockMvc.perform(post("/comments/v2")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(commentRequestV2))
.header(HttpHeaders.AUTHORIZATION, "token"))
.andExpect(status().isBadRequest())
.andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse)));
}

@DisplayName("댓글을 조회했을 때 응답 직렬화에 성공한다.")
@Test
void readCommentsByStaccatoId() throws Exception {
// given
when(authService.extractFromToken(any())).thenReturn(MemberFixture.create());
CommentResponse commentResponse = new CommentResponse(1L, 1L, "member", "image.jpg", "내용");
CommentResponses commentResponses = new CommentResponses(List.of(commentResponse));
when(commentService.readAllCommentsByMomentId(any(), any())).thenReturn(commentResponses);
String expectedResponse = """
{
"comments": [
{
"commentId": 1,
"memberId": 1,
"nickname": "member",
"memberImageUrl": "image.jpg",
"content": "내용"
}
]
}
""";

// when & then
mockMvc.perform(get("/comments/v2")
.param("staccatoId", "1")
.contentType(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, "token"))
.andExpect(status().isOk())
.andExpect(content().json(expectedResponse));
}

@DisplayName("스타카토 식별자가 양수가 아닐 경우 댓글 읽기에 실패한다.")
@Test
void readCommentsByStaccatoIdFail() throws Exception {
// given
when(authService.extractFromToken(any())).thenReturn(MemberFixture.create());
ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "스타카토 식별자는 양수로 이루어져야 합니다.");

// when & then
mockMvc.perform(get("/comments/v2")
.param("staccatoId", "0")
.contentType(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, "token"))
.andExpect(status().isBadRequest())
.andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse)));
}

@DisplayName("댓글 수정 요청 역직렬화에 성공한다.")
@Test
void updateComment() throws Exception {
// given
when(authService.extractFromToken(any())).thenReturn(MemberFixture.create());
String commentUpdateRequest = """
{
"content": "content"
}
""";

// when & then
mockMvc.perform(put("/comments/{commentId}", 1)
.content(commentUpdateRequest)
.contentType(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, "token"))
.andExpect(status().isOk());
}

@DisplayName("댓글 식별자가 양수가 아닐 경우 댓글 수정에 실패한다.")
@Test
void updateCommentFail() throws Exception {
// given
when(authService.extractFromToken(any())).thenReturn(MemberFixture.create());
CommentUpdateRequest commentUpdateRequest = CommentUpdateRequestFixture.create();
ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "댓글 식별자는 양수로 이루어져야 합니다.");

// when & then
mockMvc.perform(put("/comments/{commentId}", 0)
.content(objectMapper.writeValueAsString(commentUpdateRequest))
.contentType(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, "token"))
.andExpect(status().isBadRequest())
.andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse)));
}

@DisplayName("댓글 내용을 입력하지 않을 경우 댓글 수정에 실패한다.")
@ParameterizedTest
@NullSource
@ValueSource(strings = {"", " "})
void updateCommentFailByBlank(String updatedContent) throws Exception {
// given
when(authService.extractFromToken(any())).thenReturn(MemberFixture.create());
CommentUpdateRequest commentUpdateRequest = new CommentUpdateRequest(updatedContent);
ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "댓글 내용을 입력해주세요.");

// when & then
mockMvc.perform(put("/comments/{commentId}", 1)
.content(objectMapper.writeValueAsString(commentUpdateRequest))
.contentType(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, "token"))
.andExpect(status().isBadRequest())
.andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse)));
}

@DisplayName("올바른 형식으로 댓글 삭제를 시도하면 성공한다.")
@Test
void deleteComment() throws Exception {
// given
when(authService.extractFromToken(any())).thenReturn(MemberFixture.create());

// when & then
mockMvc.perform(delete("/comments/{commentId}", 1)
.contentType(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, "token"))
.andExpect(status().isOk());
}

@DisplayName("댓글 식별자가 양수가 아닐 경우 댓글 삭제에 실패한다.")
@Test
void deleteCommentFail() throws Exception {
// given
when(authService.extractFromToken(any())).thenReturn(MemberFixture.create());
ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "댓글 식별자는 양수로 이루어져야 합니다.");

// when & then
mockMvc.perform(delete("/comments/{commentId}", 0)
.contentType(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, "token"))
.andExpect(status().isBadRequest())
.andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse)));
}
}

0 comments on commit 5f4a792

Please sign in to comment.