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,55 @@
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.*;
import ru.practicum.explorewithme.main.dto.CommentDto;
import ru.practicum.explorewithme.main.service.CommentService;
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<CommentDto> 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(sortingRule)
.build();
List<CommentDto> result = commentService.getCommentsForEvent(eventId, parameters);
log.info("Public: Got list comments for eventId: {}, parameters: from: {}, size: {}, sort: {}",
eventId, from, size, sort);
return result;
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package ru.practicum.explorewithme.main.repository;


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<Comment, Long> {

@EntityGraph(attributePaths = {"author"})
Page<Comment> findByEventIdAndIsDeletedFalse(Long eventId, Pageable pageable);

}
Original file line number Diff line number Diff line change
@@ -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<CommentDto> getCommentsForEvent(Long eventId, PublicCommentParameters commentParameters);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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.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.List;

@Service
@RequiredArgsConstructor
public class CommentServiceImpl implements CommentService {

private final CommentRepository commentRepository;
private final EventRepository eventRepository;
private final CommentMapper commentMapper;

@Override
@Transactional(readOnly = true)
public List<CommentDto> getCommentsForEvent(Long eventId, PublicCommentParameters parameters) {

Event event = eventRepository.findByIdAndState(eventId, EventState.PUBLISHED)
.orElseThrow(() -> new EntityNotFoundException("Published event", "Id", eventId));

if (!event.isCommentsEnabled()) {
return List.of();
}

Pageable pageable = PageRequest.of(parameters.getFrom() / parameters.getSize(),
parameters.getSize(), parameters.getSort());

List<Comment> result = commentRepository.findByEventIdAndIsDeletedFalse(eventId, pageable).getContent();

return commentMapper.toDtoList(result);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

import java.util.List;

@Data
@Getter
@Builder
@EqualsAndHashCode
@AllArgsConstructor
public class GetListUsersParameters {
private final List<Long> ids;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ru.practicum.explorewithme.main.service.params;

import lombok.*;
import org.springframework.data.domain.Sort;

@Getter
@Builder(toBuilder = true)
@EqualsAndHashCode
@AllArgsConstructor
public class PublicCommentParameters {
private final int from;
private final int size;
private final Sort sort;
}
Original file line number Diff line number Diff line change
@@ -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<CommentDto> 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());
}
}
}
Loading