From f1c6eb14bd01921a8eac65a7694495b083b15c94 Mon Sep 17 00:00:00 2001 From: Sergey Filippovskikh <116564864+SergikF@users.noreply.github.com> Date: Thu, 29 May 2025 14:20:32 +0300 Subject: [PATCH 1/4] =?UTF-8?q?COMMENTS-PUBLIC:=20=D0=9F=D0=BE=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B5=D0=B2=20=D0=BA=20=D1=81?= =?UTF-8?q?=D0=BE=D0=B1=D1=8B=D1=82=D0=B8=D1=8E=20#85=20=D0=9F=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B8=D1=87=D0=BD=D0=BE=D0=B5=20=D0=B8=D1=81=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B1=D0=B5=D0=B7=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pub/PublicCommentController.java | 46 +++++++++++++++++ .../main/repository/CommentRepository.java | 5 ++ .../main/service/CommentService.java | 11 ++++ .../main/service/CommentServiceImpl.java | 51 +++++++++++++++++++ .../params/PublicCommentParameters.java | 14 +++++ 5 files changed, 127 insertions(+) create mode 100644 main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java create mode 100644 main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentService.java create mode 100644 main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentServiceImpl.java create mode 100644 main-service/src/main/java/ru/practicum/explorewithme/main/service/params/PublicCommentParameters.java diff --git a/main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java b/main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java new file mode 100644 index 0000000..a977e67 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java @@ -0,0 +1,46 @@ +package ru.practicum.explorewithme.main.controller.pub; + +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.explorewithme.main.dto.CommentDto; +import ru.practicum.explorewithme.main.service.CommentService; +import ru.practicum.explorewithme.main.service.params.GetListUsersParameters; +import ru.practicum.explorewithme.main.service.params.PublicCommentParameters; + +import java.util.List; + +@RestController +@RequestMapping("/events/{eventId}/comments") +@RequiredArgsConstructor +@Validated +@Slf4j +public class PublicCommentController { + + private final CommentService commentService; + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public List getCommentsForEventId( + @PathVariable @Positive Long eventId, + @RequestParam(name = "from", defaultValue = "0") @PositiveOrZero int from, + @RequestParam(name = "size", defaultValue = "10") @Positive int size, + @RequestParam(defaultValue="createdOn,DESC") String sort) { + log.info("Public: Received request to get list comments for eventId:"+ + " {}, parameters: from: {}, size: {}, sort: {}", eventId, from, size, sort); + PublicCommentParameters parameters = PublicCommentParameters.builder() + .from(from) + .size(size) + .sort(sort) + .build(); + List result = commentService.getCommentsForEvent(eventId, parameters); + log.info("Public: Got list comments for eventId: {}, parameters: from: {}, size: {}, sort: {}", + eventId, from, size, sort); + return result; + + } +} diff --git a/main-service/src/main/java/ru/practicum/explorewithme/main/repository/CommentRepository.java b/main-service/src/main/java/ru/practicum/explorewithme/main/repository/CommentRepository.java index 24773c6..a877664 100644 --- a/main-service/src/main/java/ru/practicum/explorewithme/main/repository/CommentRepository.java +++ b/main-service/src/main/java/ru/practicum/explorewithme/main/repository/CommentRepository.java @@ -1,8 +1,13 @@ package ru.practicum.explorewithme.main.repository; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import ru.practicum.explorewithme.main.model.Comment; public interface CommentRepository extends JpaRepository { + Page findByEventIdAndIsDeletedFalse(Long eventId, Pageable pageable); + } \ No newline at end of file diff --git a/main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentService.java b/main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentService.java new file mode 100644 index 0000000..19d0e41 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentService.java @@ -0,0 +1,11 @@ +package ru.practicum.explorewithme.main.service; + +import ru.practicum.explorewithme.main.dto.CommentDto; +import ru.practicum.explorewithme.main.service.params.PublicCommentParameters; + +import java.util.List; + +public interface CommentService { + + List getCommentsForEvent(Long eventId, PublicCommentParameters commentParameters); +} diff --git a/main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentServiceImpl.java b/main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentServiceImpl.java new file mode 100644 index 0000000..5df22a5 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentServiceImpl.java @@ -0,0 +1,51 @@ +package ru.practicum.explorewithme.main.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.explorewithme.main.dto.CommentDto; +import ru.practicum.explorewithme.main.error.EntityNotFoundException; +import ru.practicum.explorewithme.main.mapper.CommentMapper; +import ru.practicum.explorewithme.main.model.Category; +import ru.practicum.explorewithme.main.model.Comment; +import ru.practicum.explorewithme.main.model.Event; +import ru.practicum.explorewithme.main.repository.CommentRepository; +import ru.practicum.explorewithme.main.repository.EventRepository; +import ru.practicum.explorewithme.main.service.params.PublicCommentParameters; + +import java.util.Comparator; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class CommentServiceImpl implements CommentService { + + private final CommentRepository commentRepository; + private final EventRepository eventRepository; + private final CommentMapper commentMapper; + + @Override + @Transactional + public List getCommentsForEvent(Long eventId, PublicCommentParameters parameters) { + + Pageable pageable = PageRequest.of(parameters.getFrom() / parameters.getSize(), + parameters.getSize()); + + Event event = eventRepository.findById(eventId) + .orElseThrow(() -> new EntityNotFoundException("Event", "Id", eventId)); + + if (!event.isCommentsEnabled()) { + return List.of(); + } + + List result = commentRepository.findByEventIdAndIsDeletedFalse(eventId, pageable).getContent().stream() + .sorted(parameters.getSort().equals("createdOn,ASC") ? + Comparator.comparing(Comment::getCreatedOn) : + Comparator.comparing(Comment::getCreatedOn).reversed()) + .toList(); + + return commentMapper.toDtoList(result); + } +} diff --git a/main-service/src/main/java/ru/practicum/explorewithme/main/service/params/PublicCommentParameters.java b/main-service/src/main/java/ru/practicum/explorewithme/main/service/params/PublicCommentParameters.java new file mode 100644 index 0000000..2aacaff --- /dev/null +++ b/main-service/src/main/java/ru/practicum/explorewithme/main/service/params/PublicCommentParameters.java @@ -0,0 +1,14 @@ +package ru.practicum.explorewithme.main.service.params; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@AllArgsConstructor +public class PublicCommentParameters { + private final int from; + private final int size; + private final String sort; +} From 331eba4bb37898f8948b8994e19636b98042d9e0 Mon Sep 17 00:00:00 2001 From: Sergey Filippovskikh <116564864+SergikF@users.noreply.github.com> Date: Thu, 29 May 2025 14:27:17 +0300 Subject: [PATCH 2/4] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D1=82=D0=B8=D0=BB=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/controller/pub/PublicCommentController.java | 5 ++--- .../explorewithme/main/service/CommentServiceImpl.java | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java b/main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java index a977e67..6368c1a 100644 --- a/main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java +++ b/main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java @@ -9,7 +9,6 @@ import org.springframework.web.bind.annotation.*; import ru.practicum.explorewithme.main.dto.CommentDto; import ru.practicum.explorewithme.main.service.CommentService; -import ru.practicum.explorewithme.main.service.params.GetListUsersParameters; import ru.practicum.explorewithme.main.service.params.PublicCommentParameters; import java.util.List; @@ -29,8 +28,8 @@ public List getCommentsForEventId( @PathVariable @Positive Long eventId, @RequestParam(name = "from", defaultValue = "0") @PositiveOrZero int from, @RequestParam(name = "size", defaultValue = "10") @Positive int size, - @RequestParam(defaultValue="createdOn,DESC") String sort) { - log.info("Public: Received request to get list comments for eventId:"+ + @RequestParam(defaultValue = "createdOn,DESC") String sort) { + log.info("Public: Received request to get list comments for eventId:" + " {}, parameters: from: {}, size: {}, sort: {}", eventId, from, size, sort); PublicCommentParameters parameters = PublicCommentParameters.builder() .from(from) diff --git a/main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentServiceImpl.java b/main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentServiceImpl.java index 5df22a5..d9fb8cd 100644 --- a/main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentServiceImpl.java +++ b/main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentServiceImpl.java @@ -8,7 +8,6 @@ import ru.practicum.explorewithme.main.dto.CommentDto; import ru.practicum.explorewithme.main.error.EntityNotFoundException; import ru.practicum.explorewithme.main.mapper.CommentMapper; -import ru.practicum.explorewithme.main.model.Category; import ru.practicum.explorewithme.main.model.Comment; import ru.practicum.explorewithme.main.model.Event; import ru.practicum.explorewithme.main.repository.CommentRepository; From 3ed7005f56727c40685124e0501dce8fc8682b74 Mon Sep 17 00:00:00 2001 From: Sergey Filippovskikh <116564864+SergikF@users.noreply.github.com> Date: Fri, 30 May 2025 09:44:24 +0300 Subject: [PATCH 3/4] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B9=20=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pub/PublicCommentController.java | 14 +- .../main/repository/CommentRepository.java | 2 + .../main/service/CommentServiceImpl.java | 20 +- ...EventRequestStatusUpdateRequestParams.java | 7 +- .../params/GetListUsersParameters.java | 3 +- .../params/PublicCommentParameters.java | 12 +- .../pub/PublicCommentControllerTest.java | 170 ++++++++++++ .../CommentServiceIntegrationTest.java | 259 ++++++++++++++++++ 8 files changed, 462 insertions(+), 25 deletions(-) create mode 100644 main-service/src/test/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentControllerTest.java create mode 100644 main-service/src/test/java/ru/practicum/explorewithme/main/service/CommentServiceIntegrationTest.java diff --git a/main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java b/main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java index 6368c1a..10ebfe9 100644 --- a/main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java +++ b/main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java @@ -1,9 +1,11 @@ package ru.practicum.explorewithme.main.controller.pub; +import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.PositiveOrZero; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -28,18 +30,26 @@ public List getCommentsForEventId( @PathVariable @Positive Long eventId, @RequestParam(name = "from", defaultValue = "0") @PositiveOrZero int from, @RequestParam(name = "size", defaultValue = "10") @Positive int size, + @Pattern( regexp = "^(createdOn),(ASC|DESC)$", + message = "Параметр sort должен иметь формат createdOn,ASC|DESC") @RequestParam(defaultValue = "createdOn,DESC") String sort) { log.info("Public: Received request to get list comments for eventId:" + " {}, parameters: from: {}, size: {}, sort: {}", eventId, from, size, sort); + Sort sortingRule; + if (sort != null && sort.equalsIgnoreCase("createdOn,ASC")) { + sortingRule = Sort.by(Sort.Direction.ASC, "createdOn"); + } else { + sortingRule = Sort.by(Sort.Direction.DESC, "createdOn"); + } PublicCommentParameters parameters = PublicCommentParameters.builder() .from(from) .size(size) - .sort(sort) + .sort(sortingRule) .build(); List result = commentService.getCommentsForEvent(eventId, parameters); log.info("Public: Got list comments for eventId: {}, parameters: from: {}, size: {}, sort: {}", eventId, from, size, sort); return result; - } + } diff --git a/main-service/src/main/java/ru/practicum/explorewithme/main/repository/CommentRepository.java b/main-service/src/main/java/ru/practicum/explorewithme/main/repository/CommentRepository.java index a877664..4977c33 100644 --- a/main-service/src/main/java/ru/practicum/explorewithme/main/repository/CommentRepository.java +++ b/main-service/src/main/java/ru/practicum/explorewithme/main/repository/CommentRepository.java @@ -3,11 +3,13 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import ru.practicum.explorewithme.main.model.Comment; public interface CommentRepository extends JpaRepository { + @EntityGraph(attributePaths = {"author"}) Page findByEventIdAndIsDeletedFalse(Long eventId, Pageable pageable); } \ No newline at end of file diff --git a/main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentServiceImpl.java b/main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentServiceImpl.java index d9fb8cd..8691914 100644 --- a/main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentServiceImpl.java +++ b/main-service/src/main/java/ru/practicum/explorewithme/main/service/CommentServiceImpl.java @@ -10,11 +10,11 @@ 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.repository.CommentRepository; import ru.practicum.explorewithme.main.repository.EventRepository; import ru.practicum.explorewithme.main.service.params.PublicCommentParameters; -import java.util.Comparator; import java.util.List; @Service @@ -26,24 +26,20 @@ public class CommentServiceImpl implements CommentService { private final CommentMapper commentMapper; @Override - @Transactional + @Transactional(readOnly = true) public List getCommentsForEvent(Long eventId, PublicCommentParameters parameters) { - Pageable pageable = PageRequest.of(parameters.getFrom() / parameters.getSize(), - parameters.getSize()); - - Event event = eventRepository.findById(eventId) - .orElseThrow(() -> new EntityNotFoundException("Event", "Id", eventId)); + Event event = eventRepository.findByIdAndState(eventId, EventState.PUBLISHED) + .orElseThrow(() -> new EntityNotFoundException("Published event", "Id", eventId)); if (!event.isCommentsEnabled()) { return List.of(); } - List result = commentRepository.findByEventIdAndIsDeletedFalse(eventId, pageable).getContent().stream() - .sorted(parameters.getSort().equals("createdOn,ASC") ? - Comparator.comparing(Comment::getCreatedOn) : - Comparator.comparing(Comment::getCreatedOn).reversed()) - .toList(); + Pageable pageable = PageRequest.of(parameters.getFrom() / parameters.getSize(), + parameters.getSize(), parameters.getSort()); + + List result = commentRepository.findByEventIdAndIsDeletedFalse(eventId, pageable).getContent(); return commentMapper.toDtoList(result); } diff --git a/main-service/src/main/java/ru/practicum/explorewithme/main/service/params/EventRequestStatusUpdateRequestParams.java b/main-service/src/main/java/ru/practicum/explorewithme/main/service/params/EventRequestStatusUpdateRequestParams.java index 7310410..53b089b 100644 --- a/main-service/src/main/java/ru/practicum/explorewithme/main/service/params/EventRequestStatusUpdateRequestParams.java +++ b/main-service/src/main/java/ru/practicum/explorewithme/main/service/params/EventRequestStatusUpdateRequestParams.java @@ -1,14 +1,13 @@ package ru.practicum.explorewithme.main.service.params; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; +import lombok.*; import ru.practicum.explorewithme.main.model.RequestStatus; import java.util.List; -@Data +@Getter @Builder +@EqualsAndHashCode @AllArgsConstructor public class EventRequestStatusUpdateRequestParams { private final Long userId; diff --git a/main-service/src/main/java/ru/practicum/explorewithme/main/service/params/GetListUsersParameters.java b/main-service/src/main/java/ru/practicum/explorewithme/main/service/params/GetListUsersParameters.java index 5250444..5c625d8 100644 --- a/main-service/src/main/java/ru/practicum/explorewithme/main/service/params/GetListUsersParameters.java +++ b/main-service/src/main/java/ru/practicum/explorewithme/main/service/params/GetListUsersParameters.java @@ -4,8 +4,9 @@ import java.util.List; -@Data +@Getter @Builder +@EqualsAndHashCode @AllArgsConstructor public class GetListUsersParameters { private final List ids; diff --git a/main-service/src/main/java/ru/practicum/explorewithme/main/service/params/PublicCommentParameters.java b/main-service/src/main/java/ru/practicum/explorewithme/main/service/params/PublicCommentParameters.java index 2aacaff..536e13b 100644 --- a/main-service/src/main/java/ru/practicum/explorewithme/main/service/params/PublicCommentParameters.java +++ b/main-service/src/main/java/ru/practicum/explorewithme/main/service/params/PublicCommentParameters.java @@ -1,14 +1,14 @@ package ru.practicum.explorewithme.main.service.params; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; +import lombok.*; +import org.springframework.data.domain.Sort; -@Data -@Builder +@Getter +@Builder(toBuilder = true) +@EqualsAndHashCode @AllArgsConstructor public class PublicCommentParameters { private final int from; private final int size; - private final String sort; + private final Sort sort; } diff --git a/main-service/src/test/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentControllerTest.java b/main-service/src/test/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentControllerTest.java new file mode 100644 index 0000000..44897e3 --- /dev/null +++ b/main-service/src/test/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentControllerTest.java @@ -0,0 +1,170 @@ +package ru.practicum.explorewithme.main.controller.pub; + +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.mockito.ArgumentCaptor; +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.service.CommentService; +import ru.practicum.explorewithme.main.dto.UserShortDto; +import ru.practicum.explorewithme.main.service.params.PublicCommentParameters; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.assertj.core.api.Assertions.assertThat; + +@WebMvcTest(PublicCommentController.class) +@DisplayName("Публичный контроллер комментариев должен") +class PublicCommentControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockitoBean + private CommentService commentService; + + private CommentDto commentDto; + private CommentDto anotherCommentDto; + + @BeforeEach + void setUp() { + UserShortDto firstAuthor = UserShortDto.builder() + .id(100L) + .name("Автор 1") + .build(); + + UserShortDto secondAuthor = UserShortDto.builder() + .id(101L) + .name("Автор 2") + .build(); + + commentDto = CommentDto.builder() + .id(1L) + .text("Первый комментарий") + .author(firstAuthor) + .createdOn(LocalDateTime.now().minusDays(1)) + .build(); + + anotherCommentDto = CommentDto.builder() + .id(2L) + .text("Второй комментарий") + .author(secondAuthor) + .createdOn(LocalDateTime.now()) + .build(); + } + + @Nested + @DisplayName("при получении списка комментариев события") + class GetCommentsTests { + + @Test + @DisplayName("возвращать список комментариев со статусом 200") + void getComments_ReturnsList() throws Exception { + long eventId = 5L; + List expected = List.of(commentDto, anotherCommentDto); + + when(commentService.getCommentsForEvent(eq(eventId), any(PublicCommentParameters.class))) + .thenReturn(expected); + + mockMvc.perform(get("/events/{eventId}/comments", eventId) + .param("sort", "createdOn,DESC") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.length()").value(2)) + .andExpect(jsonPath("$[0].id").value(commentDto.getId())) + .andExpect(jsonPath("$[1].id").value(anotherCommentDto.getId())); + } + + @Test + @DisplayName("возвращать пустой список, если комментариев нет") + void getComments_WhenNone_ReturnsEmptyList() throws Exception { + long eventId = 7L; + + when(commentService.getCommentsForEvent(eq(eventId), any(PublicCommentParameters.class))) + .thenReturn(Collections.emptyList()); + + mockMvc.perform(get("/events/{eventId}/comments", eventId) + .param("sort", "createdOn,DESC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()").value(0)); + } + + @Test + @DisplayName("применять параметры пагинации и сортировки") + void getComments_UsesPaginationAndSorting() throws Exception { + long eventId = 11L; + + when(commentService.getCommentsForEvent(eq(eventId), any(PublicCommentParameters.class))) + .thenReturn(List.of(commentDto)); + + mockMvc.perform(get("/events/{eventId}/comments", eventId) + .param("from", "20") + .param("size", "5") + .param("sort", "createdOn,ASC")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()").value(1)); + } + + @Test + @DisplayName("использовать значения по умолчанию, когда параметры не переданы") + void getComments_DefaultParamsAreUsed() throws Exception { + long eventId = 13L; + + when(commentService.getCommentsForEvent(eq(eventId), any(PublicCommentParameters.class))) + .thenReturn(Collections.emptyList()); + + mockMvc.perform(get("/events/{eventId}/comments", eventId)) + .andExpect(status().isOk()); + + var captor = ArgumentCaptor.forClass(PublicCommentParameters.class); + verify(commentService).getCommentsForEvent(eq(eventId), captor.capture()); + + PublicCommentParameters params = captor.getValue(); + assertThat(params.getFrom()).isZero(); + assertThat(params.getSize()).isEqualTo(10); + assertThat(params.getSort().toString()).isEqualTo("createdOn: DESC"); + } + + @Test + @DisplayName("возвращать 400, если параметр sort не соответствует паттерну") + void getComments_InvalidSort_ReturnsBadRequest() throws Exception { + mockMvc.perform(get("/events/{eventId}/comments", 1) + .param("sort", "wrong,value")) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("возвращать 400 при отрицательном 'from'") + void getComments_NegativeFrom_ReturnsBadRequest() throws Exception { + mockMvc.perform(get("/events/{eventId}/comments", 1) + .param("from", "-1")) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("возвращать 400 при отрицательном 'size'") + void getComments_NegativeSize_ReturnsBadRequest() throws Exception { + mockMvc.perform(get("/events/{eventId}/comments", 1) + .param("size", "-5")) + .andExpect(status().isBadRequest()); + } + } +} \ No newline at end of file diff --git a/main-service/src/test/java/ru/practicum/explorewithme/main/service/CommentServiceIntegrationTest.java b/main-service/src/test/java/ru/practicum/explorewithme/main/service/CommentServiceIntegrationTest.java new file mode 100644 index 0000000..c1f41a9 --- /dev/null +++ b/main-service/src/test/java/ru/practicum/explorewithme/main/service/CommentServiceIntegrationTest.java @@ -0,0 +1,259 @@ +package ru.practicum.explorewithme.main.service; + +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.context.SpringBootTest; +import org.springframework.data.domain.Sort; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.transaction.annotation.Transactional; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import ru.practicum.explorewithme.main.dto.CommentDto; +import ru.practicum.explorewithme.main.error.EntityNotFoundException; +import ru.practicum.explorewithme.main.model.*; +import ru.practicum.explorewithme.main.repository.CommentRepository; +import ru.practicum.explorewithme.main.repository.EventRepository; +import ru.practicum.explorewithme.main.service.params.PublicCommentParameters; +import ru.practicum.explorewithme.main.repository.CategoryRepository; +import ru.practicum.explorewithme.main.repository.UserRepository; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static ru.practicum.explorewithme.common.constants.DateTimeConstants.DATE_TIME_FORMAT_PATTERN; + +@SpringBootTest +@Testcontainers +@Transactional +@DisplayName("Интеграционное тестирование CommentServiceImpl") +class CommentServiceIntegrationTest { + + @Container + static final PostgreSQLContainer POSTGRES = new PostgreSQLContainer<>("postgres:16.1") + .withDatabaseName("ewm") + .withUsername("test") + .withPassword("test"); + + @DynamicPropertySource + static void registerPgProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", POSTGRES::getJdbcUrl); + registry.add("spring.datasource.username", POSTGRES::getUsername); + registry.add("spring.datasource.password", POSTGRES::getPassword); + } + + @Autowired + private CommentService commentService; + @Autowired + private EventRepository eventRepository; + @Autowired + private CommentRepository commentRepository; + @Autowired + private CategoryRepository categoryRepository; + @Autowired + private UserRepository userRepository; + + @Nested + @DisplayName("Получение комментариев к событию") + class GetCommentsForEvent { + + @Test + @DisplayName("Возвращает комментарии опубликованного события с включёнными комментариями") + void shouldReturnComments_whenEventPublishedAndCommentsEnabled() { + + Event event = saveEvent(true, EventState.PUBLISHED); + saveComment(event, "Первый комментарий", + LocalDateTime.parse("2025-01-01 10:00:00", DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_PATTERN))); + saveComment(event, "Второй комментарий", + LocalDateTime.parse("2025-01-02 10:00:00", DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_PATTERN))); + + + PublicCommentParameters params = PublicCommentParameters.builder() + .from(0) + .size(10) + .sort(Sort.by(Sort.Direction.DESC, "createdOn")) + .build(); + + + List result = commentService.getCommentsForEvent(event.getId(), params); + + + assertThat(result) + .hasSize(2) + .extracting(CommentDto::getText) + .containsExactly("Второй комментарий", "Первый комментарий"); // DESC-сортировка + } + + @Test + @DisplayName("Возвращает пустой список, если комментарии отключены") + void shouldReturnEmptyList_whenCommentsDisabled() { + + Event event = saveEvent(false, EventState.PUBLISHED); + saveComment(event, "Отключённый комментарий", LocalDateTime.now()); + + PublicCommentParameters params = PublicCommentParameters.builder() + .from(0) + .size(10) + .sort(Sort.unsorted()) + .build(); + + List result = commentService.getCommentsForEvent(event.getId(), params); + + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("Бросает EntityNotFoundException, когда событие не опубликовано") + void shouldThrowException_whenEventNotPublished() { + + Event event = saveEvent(true, EventState.CANCELED); + + PublicCommentParameters params = PublicCommentParameters.builder() + .from(0) + .size(10) + .sort(Sort.unsorted()) + .build(); + + assertThrows(EntityNotFoundException.class, + () -> commentService.getCommentsForEvent(event.getId(), params)); + } + + @Test + @DisplayName("Корректная пагинация") + void shouldApplyPagination() { + + Event event = saveEvent(true, EventState.PUBLISHED); + for (int i = 0; i < 5; i++) { + saveComment(event, "Комментарий " + i, + LocalDateTime.now().plusSeconds(i)); + } + + PublicCommentParameters params = PublicCommentParameters.builder() + .from(0) + .size(2) + .sort(Sort.by(Sort.Direction.ASC, "createdOn")) + .build(); + + List page1 = commentService.getCommentsForEvent(event.getId(), params); + + params = params.toBuilder().from(2).build(); + List page2 = commentService.getCommentsForEvent(event.getId(), params); + + assertThat(page1).hasSize(2); + assertThat(page2).hasSize(2); + + params = params.toBuilder().from(4).build(); + List page3 = commentService.getCommentsForEvent(event.getId(), params); + assertThat(page3).hasSize(1); + } + + @Test + @DisplayName("Пустой список, когда у опубликованного события нет комментариев") + void shouldReturnEmptyList_whenNoComments() { + Event event = saveEvent(true, EventState.PUBLISHED); // комментарии включены, но мы их не создаём + + PublicCommentParameters params = PublicCommentParameters.builder() + .from(0).size(10).sort(Sort.unsorted()).build(); + + List result = commentService.getCommentsForEvent(event.getId(), params); + + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("Комментарий, помеченный как удалённый, не возвращается") + void shouldIgnoreDeletedComments() { + Event event = saveEvent(true, EventState.PUBLISHED); + + Comment deletedComment = Comment.builder() + .event(event) + .author(userRepository.save( + User.builder() + .name("X") + .email("x@example.com") + .build())) + .text("Удалённый") + .createdOn(LocalDateTime.now()) + .isDeleted(true) + .build(); + commentRepository.save(deletedComment); + + PublicCommentParameters params = PublicCommentParameters.builder() + .from(0).size(10).sort(Sort.unsorted()).build(); + + List result = commentService.getCommentsForEvent(event.getId(), params); + + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("EntityNotFoundException, когда событие не найдено") + void shouldThrowException_whenEventNotFound() { + PublicCommentParameters params = PublicCommentParameters.builder() + .from(0).size(10).sort(Sort.unsorted()).build(); + + assertThrows(EntityNotFoundException.class, + () -> commentService.getCommentsForEvent(9999L, params)); + } + + } + + /* ---------- Вспомогательные методы ---------- */ + private Event saveEvent(boolean commentsEnabled, EventState state) { + + + Category category = categoryRepository.save( + Category.builder() + .name("Тестовая категория") + .build()); + + User initiator = userRepository.save( + User.builder() + .name("Инициатор") + .email("initiator@example.com") + .build()); + + Event event = Event.builder() + .title("Событие") + .annotation("Краткое описание события") + .description("Описание") + .state(state) + .commentsEnabled(commentsEnabled) + .category(category) + .initiator(initiator) + .eventDate(LocalDateTime.now().plusDays(1)) + .location( new Location(55.7522F, 37.6156F)) + .paid(false) + .participantLimit(0) + .requestModeration(false) + .build(); + + return eventRepository.save(event); + } + + private void saveComment(Event event, String text, LocalDateTime createdOn) { + + User author = userRepository.save( + User.builder() + .name("Автор") + .email("author_" + UUID.randomUUID() + "@example.com") + .build()); + + Comment comment = Comment.builder() + .event(event) + .author(author) // ← задаём автора + .text(text) + .createdOn(createdOn) + .isDeleted(false) + .build(); + + commentRepository.save(comment); + } +} \ No newline at end of file From b75bed9a5442d5eb3442e8b9cb9dcfd005dc3dfc Mon Sep 17 00:00:00 2001 From: Sergey Filippovskikh <116564864+SergikF@users.noreply.github.com> Date: Fri, 30 May 2025 09:48:46 +0300 Subject: [PATCH 4/4] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D1=82=D0=B8=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/controller/pub/PublicCommentController.java | 2 +- .../main/service/CommentServiceIntegrationTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java b/main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java index 10ebfe9..341a369 100644 --- a/main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java +++ b/main-service/src/main/java/ru/practicum/explorewithme/main/controller/pub/PublicCommentController.java @@ -30,7 +30,7 @@ public List getCommentsForEventId( @PathVariable @Positive Long eventId, @RequestParam(name = "from", defaultValue = "0") @PositiveOrZero int from, @RequestParam(name = "size", defaultValue = "10") @Positive int size, - @Pattern( regexp = "^(createdOn),(ASC|DESC)$", + @Pattern(regexp = "^(createdOn),(ASC|DESC)$", message = "Параметр sort должен иметь формат createdOn,ASC|DESC") @RequestParam(defaultValue = "createdOn,DESC") String sort) { log.info("Public: Received request to get list comments for eventId:" + diff --git a/main-service/src/test/java/ru/practicum/explorewithme/main/service/CommentServiceIntegrationTest.java b/main-service/src/test/java/ru/practicum/explorewithme/main/service/CommentServiceIntegrationTest.java index c1f41a9..07b49db 100644 --- a/main-service/src/test/java/ru/practicum/explorewithme/main/service/CommentServiceIntegrationTest.java +++ b/main-service/src/test/java/ru/practicum/explorewithme/main/service/CommentServiceIntegrationTest.java @@ -229,7 +229,7 @@ private Event saveEvent(boolean commentsEnabled, EventState state) { .category(category) .initiator(initiator) .eventDate(LocalDateTime.now().plusDays(1)) - .location( new Location(55.7522F, 37.6156F)) + .location(new Location(55.7522F, 37.6156F)) .paid(false) .participantLimit(0) .requestModeration(false)