-
Notifications
You must be signed in to change notification settings - Fork 1
COMMENTS-PRIVATE: Создание/обновление комментария пользователем #84 и #86 #97
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
Changes from all commits
c13a5a5
a927c8f
efbad80
7fa91a7
e690288
9fc0afe
8dbc3ee
d7a83e8
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 |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| package ru.practicum.explorewithme.main.controller.priv; | ||
|
|
||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.Positive; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.validation.annotation.Validated; | ||
| import org.springframework.web.bind.annotation.*; | ||
| import ru.practicum.explorewithme.main.dto.CommentDto; | ||
| import ru.practicum.explorewithme.main.dto.NewCommentDto; | ||
| import ru.practicum.explorewithme.main.dto.UpdateCommentDto; | ||
| import ru.practicum.explorewithme.main.service.CommentService; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/users/{userId}/comments") | ||
| @RequiredArgsConstructor | ||
| @Validated | ||
| @Slf4j | ||
| public class PrivateCommentController { | ||
|
|
||
| private final CommentService commentService; | ||
|
|
||
| @PostMapping | ||
| public ResponseEntity<CommentDto> createComment( | ||
| @PathVariable @Positive Long userId, | ||
| @RequestParam @Positive Long eventId, | ||
| @Valid @RequestBody NewCommentDto newCommentDto) { | ||
|
|
||
| log.info("Создание нового комментария {} зарегистрированным пользователем c id {} " + | ||
| "к событию с id {}", newCommentDto, userId, eventId); | ||
|
|
||
| return ResponseEntity.status(HttpStatus.CREATED) | ||
| .body(commentService.addComment(userId, eventId, newCommentDto)); | ||
| } | ||
|
|
||
| @PatchMapping("/{commentId}") | ||
| @ResponseStatus(HttpStatus.OK) | ||
| public ResponseEntity<CommentDto> updateComment( | ||
| @PathVariable @Positive Long userId, | ||
| @PathVariable @Positive Long commentId, | ||
| @Valid @RequestBody UpdateCommentDto updateCommentDto) { | ||
|
|
||
| log.info("Обновление комментария c id {} пользователем c id {}," + | ||
| " новый комментарий {}", commentId, userId, updateCommentDto); | ||
|
|
||
| return ResponseEntity.status(HttpStatus.OK).body(commentService.updateUserComment(userId, commentId, updateCommentDto)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,17 @@ | ||
| package ru.practicum.explorewithme.main.service; | ||
|
|
||
| import ru.practicum.explorewithme.main.dto.CommentDto; | ||
| import ru.practicum.explorewithme.main.dto.NewCommentDto; | ||
| import ru.practicum.explorewithme.main.dto.UpdateCommentDto; | ||
| import ru.practicum.explorewithme.main.service.params.PublicCommentParameters; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public interface CommentService { | ||
|
|
||
| List<CommentDto> getCommentsForEvent(Long eventId, PublicCommentParameters commentParameters); | ||
|
|
||
| CommentDto addComment(Long userId, Long eventId, NewCommentDto newCommentDto); | ||
|
|
||
| CommentDto updateUserComment(Long userId, Long commentId, UpdateCommentDto updateCommentDto); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,21 +6,29 @@ | |
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import ru.practicum.explorewithme.main.dto.CommentDto; | ||
| import ru.practicum.explorewithme.main.dto.NewCommentDto; | ||
| import ru.practicum.explorewithme.main.dto.UpdateCommentDto; | ||
| import ru.practicum.explorewithme.main.error.BusinessRuleViolationException; | ||
| import ru.practicum.explorewithme.main.error.EntityNotFoundException; | ||
| import ru.practicum.explorewithme.main.mapper.CommentMapper; | ||
| import ru.practicum.explorewithme.main.model.Comment; | ||
| import ru.practicum.explorewithme.main.model.Event; | ||
| import ru.practicum.explorewithme.main.model.EventState; | ||
| import ru.practicum.explorewithme.main.model.User; | ||
| import ru.practicum.explorewithme.main.repository.CommentRepository; | ||
| import ru.practicum.explorewithme.main.repository.EventRepository; | ||
| import ru.practicum.explorewithme.main.repository.UserRepository; | ||
| import ru.practicum.explorewithme.main.service.params.PublicCommentParameters; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.util.Optional; | ||
| import java.util.List; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class CommentServiceImpl implements CommentService { | ||
|
|
||
| private final UserRepository userRepository; | ||
| private final CommentRepository commentRepository; | ||
| private final EventRepository eventRepository; | ||
| private final CommentMapper commentMapper; | ||
|
|
@@ -43,4 +51,60 @@ public List<CommentDto> getCommentsForEvent(Long eventId, PublicCommentParameter | |
|
|
||
| return commentMapper.toDtoList(result); | ||
| } | ||
|
|
||
| @Override | ||
| @Transactional | ||
| public CommentDto addComment(Long userId, Long eventId, NewCommentDto newCommentDto) { | ||
|
Collaborator
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. [НЕОБЯЗАТЕЛЬНО] Хоть я и склоняюсь к мысли, что требование передавать в методах не более 2-х параметров - это определенное требование, но, по моему, в коде были такие методы и это не вызвало замечаний ревьювера.
Collaborator
Author
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. Я понимаю о чем ты, об этом говорил старший преподаватель, однако в тз такого требования нет. И видимо, ревьюеру это не принципиально, т.к. параметров не на столько много, чтобы их количество вызывало трудности в разборе написанного кода. |
||
|
|
||
| User author = userRepository.findById(userId) | ||
| .orElseThrow(() -> new EntityNotFoundException("Пользователь с id " + userId + " не найден")); | ||
|
|
||
Gagarskiy-Andrey marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Event event = eventRepository.findById(eventId) | ||
| .orElseThrow(() -> new EntityNotFoundException("Событие с id " + eventId + " не найдено")); | ||
|
|
||
| if (!event.getState().equals(EventState.PUBLISHED)) { | ||
| throw new BusinessRuleViolationException("Событие еще не опубликовано"); | ||
| } | ||
|
|
||
| if (!event.isCommentsEnabled()) { | ||
| throw new BusinessRuleViolationException("Комментарии запрещены"); | ||
| } | ||
|
|
||
| Comment comment = commentMapper.toComment(newCommentDto); | ||
|
|
||
| comment.setAuthor(author); | ||
| comment.setEvent(event); | ||
|
|
||
| return commentMapper.toDto(commentRepository.save(comment)); | ||
| } | ||
|
|
||
| @Override | ||
| @Transactional | ||
| public CommentDto updateUserComment(Long userId, Long commentId, UpdateCommentDto updateCommentDto) { | ||
|
|
||
| Optional<Comment> comment = commentRepository.findById(commentId); | ||
|
|
||
| if (comment.isEmpty()) { | ||
| throw new EntityNotFoundException("Комментарий с id" + commentId + " не найден"); | ||
| } | ||
|
|
||
| Comment existedComment = comment.get(); | ||
|
|
||
| if (!existedComment.getAuthor().getId().equals(userId)) { | ||
| throw new EntityNotFoundException("Искомый комментарий с id " + commentId + " пользователя с id " + userId + "не найден"); | ||
| } | ||
|
|
||
| if (existedComment.isDeleted() == true) { | ||
| throw new BusinessRuleViolationException("Редактирование невозможно. Комментарий удален"); | ||
| } | ||
|
|
||
| if (existedComment.getCreatedOn().isBefore(LocalDateTime.now().minusHours(6))) { | ||
| throw new BusinessRuleViolationException("Время для редактирования истекло"); | ||
| } | ||
|
|
||
| existedComment.setText(updateCommentDto.getText()); | ||
| existedComment.setEdited(true); | ||
|
|
||
| return commentMapper.toDto(commentRepository.save(existedComment)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,216 @@ | ||
| package ru.practicum.explorewithme.main.controller.priv; | ||
|
|
||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Nested; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; | ||
| import org.springframework.http.MediaType; | ||
| import org.springframework.test.context.bean.override.mockito.MockitoBean; | ||
| import org.springframework.test.web.servlet.MockMvc; | ||
| import ru.practicum.explorewithme.main.dto.CommentDto; | ||
| import ru.practicum.explorewithme.main.dto.NewCommentDto; | ||
| import ru.practicum.explorewithme.main.dto.UpdateCommentDto; | ||
| import ru.practicum.explorewithme.main.dto.UserShortDto; | ||
| import ru.practicum.explorewithme.main.service.CommentService; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| import static org.mockito.ArgumentMatchers.any; | ||
| import static org.mockito.ArgumentMatchers.eq; | ||
| import static org.mockito.Mockito.*; | ||
| import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; | ||
| import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; // <-- для post-запроса | ||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; | ||
|
|
||
Gagarskiy-Andrey marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @WebMvcTest(PrivateCommentController.class) | ||
| public class PrivateCommentControllerTest { | ||
|
|
||
| @Autowired | ||
| private MockMvc mockMvc; | ||
|
|
||
| @MockitoBean | ||
| private CommentService commentService; | ||
|
|
||
| private ObjectMapper objectMapper; | ||
|
|
||
| private final Long userId = 1L; | ||
| private final Long eventId = 100L; | ||
|
|
||
| private NewCommentDto newCommentDto; | ||
| private CommentDto commentDto; | ||
|
|
||
| @BeforeEach | ||
| void setUp() { | ||
| objectMapper = new ObjectMapper(); | ||
| objectMapper.findAndRegisterModules(); | ||
|
|
||
| newCommentDto = NewCommentDto.builder() | ||
| .text("Test comment text") | ||
| .build(); | ||
|
|
||
| UserShortDto author = UserShortDto.builder() | ||
| .id(2L) | ||
| .name("testUser") | ||
| .build(); | ||
|
|
||
| commentDto = CommentDto.builder() | ||
| .id(10L) | ||
| .text(newCommentDto.getText()) | ||
| .author(author) | ||
| .eventId(eventId) | ||
| .createdOn(LocalDateTime.now()) | ||
| .updatedOn(LocalDateTime.now()) | ||
| .isEdited(false) | ||
| .build(); | ||
| } | ||
|
|
||
| @Nested | ||
| @DisplayName("Набор тестов для метода createComment") | ||
| class CreateComment { | ||
|
|
||
| @Test | ||
| void createComment_whenValidInput_thenReturnsCreatedComment() throws Exception { | ||
| when(commentService.addComment(eq(userId), eq(eventId), any(NewCommentDto.class))) | ||
| .thenReturn(commentDto); | ||
|
|
||
| mockMvc.perform(post("/users/{userId}/comments?eventId={eventId}", userId, eventId) | ||
| .contentType(MediaType.APPLICATION_JSON) | ||
| .content(objectMapper.writeValueAsString(newCommentDto))) | ||
| .andExpect(status().isCreated()) | ||
| .andExpect(jsonPath("$.id").value(commentDto.getId())) | ||
| .andExpect(jsonPath("$.text").value(commentDto.getText())) | ||
| .andExpect(jsonPath("$.eventId").value(eventId)) | ||
| .andExpect(jsonPath("$.author.id").value(commentDto.getAuthor().getId())) | ||
| .andExpect(jsonPath("$.author.name").value(commentDto.getAuthor().getName())) | ||
| .andExpect(jsonPath("$.isEdited").value(false)); | ||
| } | ||
|
|
||
| @Test | ||
| void createComment_whenInvalidText_thenReturnsBadRequest() throws Exception { | ||
| NewCommentDto invalidDto = NewCommentDto.builder() | ||
| .text("") | ||
| .build(); | ||
|
|
||
| mockMvc.perform(post("/users/{userId}/comments?eventId={eventId}", userId, eventId) | ||
| .contentType(MediaType.APPLICATION_JSON) | ||
| .content(objectMapper.writeValueAsString(invalidDto))) | ||
| .andExpect(status().isBadRequest()); | ||
| } | ||
|
|
||
| @Test | ||
| void createComment_whenNegativeUserId_thenReturnsBadRequest() throws Exception { | ||
| mockMvc.perform(post("/users/{userId}/comments?eventId={eventId}", -1, eventId) | ||
| .contentType(MediaType.APPLICATION_JSON) | ||
| .content(objectMapper.writeValueAsString(newCommentDto))) | ||
| .andExpect(status().isBadRequest()); | ||
| } | ||
|
|
||
| @Test | ||
| void createComment_whenNegativeEventId_thenReturnsBadRequest() throws Exception { | ||
| mockMvc.perform(post("/users/{userId}/comments?eventId={eventId}", userId, -1) | ||
| .contentType(MediaType.APPLICATION_JSON) | ||
| .content(objectMapper.writeValueAsString(newCommentDto))) | ||
| .andExpect(status().isBadRequest()); | ||
| } | ||
| } | ||
|
|
||
| @Nested | ||
| @DisplayName("Набор тестов для метода updateComment") | ||
| class UpdateComment { | ||
|
|
||
| @Test | ||
| void updateComment_shouldReturnUpdatedComment_whenInputIsValid() throws Exception { | ||
| Long commentId = commentDto.getId(); | ||
|
|
||
| UpdateCommentDto updateCommentDto = UpdateCommentDto.builder() | ||
| .text("Updated text") | ||
| .build(); | ||
|
|
||
| CommentDto updatedComment = CommentDto.builder() | ||
| .id(commentId) | ||
| .text(updateCommentDto.getText()) | ||
| .author(commentDto.getAuthor()) | ||
| .eventId(eventId) | ||
| .createdOn(commentDto.getCreatedOn()) | ||
| .updatedOn(commentDto.getUpdatedOn()) | ||
| .isEdited(true) | ||
| .build(); | ||
|
|
||
| when(commentService.updateUserComment(eq(userId), eq(commentId), any(UpdateCommentDto.class))) | ||
| .thenReturn(updatedComment); | ||
|
|
||
| mockMvc.perform(patch("/users/{userId}/comments/{commentId}", userId, commentId) | ||
| .contentType(MediaType.APPLICATION_JSON) | ||
| .content(objectMapper.writeValueAsString(updateCommentDto))) | ||
| .andExpect(status().isOk()) | ||
| .andExpect(jsonPath("$.id").value(commentId)) | ||
| .andExpect(jsonPath("$.text").value(updateCommentDto.getText())) | ||
| .andExpect(jsonPath("$.author.id").value(commentDto.getAuthor().getId())) | ||
| .andExpect(jsonPath("$.isEdited").value(true)); | ||
|
|
||
| verify(commentService, times(1)) | ||
| .updateUserComment(eq(userId), eq(commentId), any(UpdateCommentDto.class)); | ||
| } | ||
|
|
||
| @Test | ||
| void updateComment_shouldReturnBadRequest_whenPathVariablesInvalid() throws Exception { | ||
| UpdateCommentDto updateCommentDto = UpdateCommentDto.builder() | ||
| .text("Comment text") | ||
| .build(); | ||
|
|
||
| mockMvc.perform(patch("/users/{userId}/comments/{commentId}", -1, 10) | ||
| .contentType(MediaType.APPLICATION_JSON) | ||
| .content(objectMapper.writeValueAsString(updateCommentDto))) | ||
| .andExpect(status().isBadRequest()); | ||
|
|
||
| mockMvc.perform(patch("/users/{userId}/comments/{commentId}", 1, -10) | ||
| .contentType(MediaType.APPLICATION_JSON) | ||
| .content(objectMapper.writeValueAsString(updateCommentDto))) | ||
| .andExpect(status().isBadRequest()); | ||
| } | ||
|
|
||
| @Test | ||
| void updateComment_shouldReturnBadRequest_whenBodyTextBlank() throws Exception { | ||
| UpdateCommentDto updateCommentDto = UpdateCommentDto.builder() | ||
| .text(" ") | ||
| .build(); | ||
|
|
||
| mockMvc.perform(patch("/users/{userId}/comments/{commentId}", userId, commentDto.getId()) | ||
| .contentType(MediaType.APPLICATION_JSON) | ||
| .content(objectMapper.writeValueAsString(updateCommentDto))) | ||
| .andExpect(status().isBadRequest()) | ||
| .andExpect(jsonPath("$.errors[0]").exists()) | ||
| .andExpect(jsonPath("$.errors[0]").value("text: Comment text cannot be blank.")); | ||
| } | ||
|
|
||
| @Test | ||
| void updateComment_shouldReturnBadRequest_whenBodyTextTooLong() throws Exception { | ||
| String longText = "a".repeat(2001); | ||
| UpdateCommentDto updateCommentDto = UpdateCommentDto.builder() | ||
| .text(longText) | ||
| .build(); | ||
|
|
||
| mockMvc.perform(patch("/users/{userId}/comments/{commentId}", userId, commentDto.getId()) | ||
| .contentType(MediaType.APPLICATION_JSON) | ||
| .content(objectMapper.writeValueAsString(updateCommentDto))) | ||
| .andExpect(status().isBadRequest()) | ||
| .andExpect(jsonPath("$.errors[0]").exists()) | ||
| .andExpect(jsonPath("$.errors[0]").value("text: Comment text must be between 1 and 2000 characters.")); | ||
| } | ||
|
|
||
| @Test | ||
| void updateComment_shouldReturnBadRequest_whenBodyEmpty() throws Exception { | ||
| mockMvc.perform(patch("/users/{userId}/comments/{commentId}", userId, commentDto.getId()) | ||
| .contentType(MediaType.APPLICATION_JSON) | ||
| .content("{}")) | ||
| .andExpect(status().isBadRequest()) | ||
| .andExpect(jsonPath("$.errors[0]").exists()) | ||
| .andExpect(jsonPath("$.errors[0]").value("text: Comment text cannot be blank.")); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.