Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Expand Up @@ -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;
Expand All @@ -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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[НЕОБЯЗАТЕЛЬНО] Хоть я и склоняюсь к мысли, что требование передавать в методах не более 2-х параметров - это определенное требование, но, по моему, в коде были такие методы и это не вызвало замечаний ревьювера.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я понимаю о чем ты, об этом говорил старший преподаватель, однако в тз такого требования нет. И видимо, ревьюеру это не принципиально, т.к. параметров не на столько много, чтобы их количество вызывало трудности в разборе написанного кода.


User author = userRepository.findById(userId)
.orElseThrow(() -> new EntityNotFoundException("Пользователь с id " + userId + " не найден"));

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;


@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."));
}
}
}
Loading