diff --git a/main-service/pom.xml b/main-service/pom.xml
index 6e7b885..75b3a9b 100644
--- a/main-service/pom.xml
+++ b/main-service/pom.xml
@@ -62,6 +62,13 @@
querydsl-jpa
jakarta
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
diff --git a/main-service/src/main/java/ru/practicum/exception/ConflictException.java b/main-service/src/main/java/ru/practicum/exception/ConflictException.java
new file mode 100644
index 0000000..e2fddf1
--- /dev/null
+++ b/main-service/src/main/java/ru/practicum/exception/ConflictException.java
@@ -0,0 +1,7 @@
+package ru.practicum.exception;
+
+public class ConflictException extends RuntimeException {
+ public ConflictException(String message) {
+ super(message);
+ }
+}
diff --git a/main-service/src/main/java/ru/practicum/exception/handler/GlobalExceptionHandler.java b/main-service/src/main/java/ru/practicum/exception/handler/GlobalExceptionHandler.java
index bf746af..7a51bc4 100644
--- a/main-service/src/main/java/ru/practicum/exception/handler/GlobalExceptionHandler.java
+++ b/main-service/src/main/java/ru/practicum/exception/handler/GlobalExceptionHandler.java
@@ -8,19 +8,18 @@
import jakarta.validation.ConstraintViolationException;
-import ru.practicum.exception.ForbiddenAccessException;
-import ru.practicum.exception.IllegalEventUpdateException;
-import ru.practicum.exception.NotFoundException;
-import ru.practicum.exception.ValidationException;
import ru.practicum.exception.dto.ApiError;
import ru.practicum.exception.dto.Violation;
+import ru.practicum.exception.*;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import lombok.extern.slf4j.Slf4j;
@@ -45,6 +44,33 @@ public ApiError handleException(Exception e) {
LocalDateTime.now());
}
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ @ExceptionHandler({
+ MissingServletRequestParameterException.class,
+ MethodArgumentTypeMismatchException.class
+ })
+ public ApiError handleRequestParamException(Exception e) {
+ log.warn(e.getMessage(), e);
+ return new ApiError(
+ null,
+ e.getMessage(),
+ "Incorrect request",
+ HttpStatus.BAD_REQUEST.toString(),
+ LocalDateTime.now());
+ }
+
+ @ResponseStatus(HttpStatus.CONFLICT)
+ @ExceptionHandler(ConflictException.class)
+ public ApiError handleConflictException(ConflictException e) {
+ log.warn(e.getMessage(), e);
+ return new ApiError(
+ null,
+ e.getMessage(),
+ "Conflict",
+ HttpStatus.CONFLICT.toString(),
+ LocalDateTime.now());
+ }
+
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public ApiError handleConstraintValidationException(ConstraintViolationException e) {
diff --git a/main-service/src/main/java/ru/practicum/request/controller/ParticipationRequestEventPrivateController.java b/main-service/src/main/java/ru/practicum/request/controller/ParticipationRequestEventPrivateController.java
new file mode 100644
index 0000000..c20a510
--- /dev/null
+++ b/main-service/src/main/java/ru/practicum/request/controller/ParticipationRequestEventPrivateController.java
@@ -0,0 +1,50 @@
+package ru.practicum.request.controller;
+
+import java.util.List;
+
+import jakarta.validation.Valid;
+
+import ru.practicum.request.dto.EventRequestStatusUpdateRequest;
+import ru.practicum.request.dto.EventRequestStatusUpdateResult;
+import ru.practicum.request.dto.ParticipationRequestDto;
+import ru.practicum.request.service.ParticipationRequestService;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/users/{userId}/events/{eventId}/requests")
+public class ParticipationRequestEventPrivateController {
+
+ private final ParticipationRequestService requestService;
+
+ @GetMapping
+ public List getEventRequests(
+ @PathVariable Long userId, @PathVariable Long eventId) {
+ log.info("Get requests for eventId={} by initiator userId={}", eventId, userId);
+ return requestService.getEventRequestsByInitiator(userId, eventId);
+ }
+
+ @PatchMapping
+ public EventRequestStatusUpdateResult updateRequestsStatus(
+ @PathVariable Long userId,
+ @PathVariable Long eventId,
+ @Valid @RequestBody EventRequestStatusUpdateRequest updateRequest) {
+ log.info(
+ "Update requests status for eventId={} by userId={}, requestIds={}, status={}",
+ eventId,
+ userId,
+ updateRequest.requestIds(),
+ updateRequest.status());
+ return requestService.updateEventRequestsStatus(userId, eventId, updateRequest);
+ }
+}
diff --git a/main-service/src/main/java/ru/practicum/request/controller/ParticipationRequestPrivateController.java b/main-service/src/main/java/ru/practicum/request/controller/ParticipationRequestPrivateController.java
new file mode 100644
index 0000000..37f49f3
--- /dev/null
+++ b/main-service/src/main/java/ru/practicum/request/controller/ParticipationRequestPrivateController.java
@@ -0,0 +1,46 @@
+package ru.practicum.request.controller;
+
+import java.util.List;
+
+import ru.practicum.request.dto.ParticipationRequestDto;
+import ru.practicum.request.service.ParticipationRequestService;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/users/{userId}/requests")
+public class ParticipationRequestPrivateController {
+
+ private final ParticipationRequestService requestService;
+
+ @PostMapping
+ public ParticipationRequestDto createRequest(
+ @PathVariable Long userId, @RequestParam Long eventId) {
+ log.info("Create participation request userId={}, eventId={}", userId, eventId);
+ return requestService.createRequest(userId, eventId);
+ }
+
+ @GetMapping
+ public List getUserRequests(@PathVariable Long userId) {
+ log.info("Get participation requests by userId={}", userId);
+ return requestService.getUserRequests(userId);
+ }
+
+ @PatchMapping("/{requestId}/cancel")
+ public ParticipationRequestDto cancelRequest(
+ @PathVariable Long userId, @PathVariable Long requestId) {
+ log.info("Cancel participation request userId={}, requestId={}", userId, requestId);
+ return requestService.cancelRequest(userId, requestId);
+ }
+}
diff --git a/main-service/src/main/java/ru/practicum/request/mapper/ParticipationRequestMapper.java b/main-service/src/main/java/ru/practicum/request/mapper/ParticipationRequestMapper.java
new file mode 100644
index 0000000..2060323
--- /dev/null
+++ b/main-service/src/main/java/ru/practicum/request/mapper/ParticipationRequestMapper.java
@@ -0,0 +1,31 @@
+package ru.practicum.request.mapper;
+
+import java.util.List;
+
+import ru.practicum.request.dto.ParticipationRequestDto;
+import ru.practicum.request.model.ParticipationRequest;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class ParticipationRequestMapper {
+
+ public ParticipationRequestDto toDto(ParticipationRequest request) {
+ if (request == null) {
+ return null;
+ }
+ return new ParticipationRequestDto(
+ request.getCreated(),
+ request.getEvent().getId(),
+ request.getId(),
+ request.getRequester().getId(),
+ request.getStatus().name());
+ }
+
+ public List toDtoList(List requests) {
+ if (requests == null) {
+ return List.of();
+ }
+ return requests.stream().map(ParticipationRequestMapper::toDto).toList();
+ }
+}
diff --git a/main-service/src/main/java/ru/practicum/request/model/EventRequestStatus.java b/main-service/src/main/java/ru/practicum/request/model/EventRequestStatus.java
index e595013..fb21ff3 100644
--- a/main-service/src/main/java/ru/practicum/request/model/EventRequestStatus.java
+++ b/main-service/src/main/java/ru/practicum/request/model/EventRequestStatus.java
@@ -3,5 +3,6 @@
public enum EventRequestStatus {
PENDING,
CONFIRMED,
- REJECTED
+ REJECTED,
+ CANCELED
}
diff --git a/main-service/src/main/java/ru/practicum/request/model/ParticipationRequest.java b/main-service/src/main/java/ru/practicum/request/model/ParticipationRequest.java
index 54aab5d..9e99701 100644
--- a/main-service/src/main/java/ru/practicum/request/model/ParticipationRequest.java
+++ b/main-service/src/main/java/ru/practicum/request/model/ParticipationRequest.java
@@ -2,21 +2,36 @@
import java.time.LocalDateTime;
-import jakarta.persistence.*;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
import ru.practicum.event.model.Event;
import ru.practicum.user.model.User;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
import lombok.experimental.FieldDefaults;
-import lombok.*;
@Entity
+@Table(name = "participation_request")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
-@Table(name = "participation_request")
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ParticipationRequest {
diff --git a/main-service/src/main/java/ru/practicum/request/repository/ParticipationRequestRepository.java b/main-service/src/main/java/ru/practicum/request/repository/ParticipationRequestRepository.java
new file mode 100644
index 0000000..4678b92
--- /dev/null
+++ b/main-service/src/main/java/ru/practicum/request/repository/ParticipationRequestRepository.java
@@ -0,0 +1,23 @@
+package ru.practicum.request.repository;
+
+import java.util.Collection;
+import java.util.List;
+
+import ru.practicum.request.model.EventRequestStatus;
+import ru.practicum.request.model.ParticipationRequest;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface ParticipationRequestRepository extends JpaRepository {
+ boolean existsByEvent_IdAndRequester_Id(Long eventId, Long requesterId);
+
+ List findAllByRequester_IdOrderByCreatedDesc(Long requesterId);
+
+ List findAllByEvent_IdOrderByCreatedAsc(Long eventId);
+
+ long countByEvent_IdAndStatus(Long eventId, EventRequestStatus status);
+
+ List findAllByIdInAndEvent_Id(Collection ids, Long eventId);
+
+ List findAllByEvent_IdAndStatus(Long eventId, EventRequestStatus status);
+}
diff --git a/main-service/src/main/java/ru/practicum/request/service/ParticipationRequestService.java b/main-service/src/main/java/ru/practicum/request/service/ParticipationRequestService.java
new file mode 100644
index 0000000..543a5b0
--- /dev/null
+++ b/main-service/src/main/java/ru/practicum/request/service/ParticipationRequestService.java
@@ -0,0 +1,20 @@
+package ru.practicum.request.service;
+
+import java.util.List;
+
+import ru.practicum.request.dto.EventRequestStatusUpdateRequest;
+import ru.practicum.request.dto.EventRequestStatusUpdateResult;
+import ru.practicum.request.dto.ParticipationRequestDto;
+
+public interface ParticipationRequestService {
+ ParticipationRequestDto createRequest(Long userId, Long eventId);
+
+ List getUserRequests(Long userId);
+
+ ParticipationRequestDto cancelRequest(Long userId, Long requestId);
+
+ List getEventRequestsByInitiator(Long userId, Long eventId);
+
+ EventRequestStatusUpdateResult updateEventRequestsStatus(
+ Long userId, Long eventId, EventRequestStatusUpdateRequest request);
+}
diff --git a/main-service/src/main/java/ru/practicum/request/service/ParticipationRequestServiceImpl.java b/main-service/src/main/java/ru/practicum/request/service/ParticipationRequestServiceImpl.java
new file mode 100644
index 0000000..a4bd298
--- /dev/null
+++ b/main-service/src/main/java/ru/practicum/request/service/ParticipationRequestServiceImpl.java
@@ -0,0 +1,246 @@
+package ru.practicum.request.service;
+
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import ru.practicum.event.model.Event;
+import ru.practicum.event.model.EventState;
+import ru.practicum.event.repository.EventRepository;
+import ru.practicum.exception.ConflictException;
+import ru.practicum.exception.ForbiddenAccessException;
+import ru.practicum.exception.NotFoundException;
+import ru.practicum.request.dto.EventRequestStatusUpdateRequest;
+import ru.practicum.request.dto.EventRequestStatusUpdateResult;
+import ru.practicum.request.dto.ParticipationRequestDto;
+import ru.practicum.request.mapper.ParticipationRequestMapper;
+import ru.practicum.request.model.EventRequestStatus;
+import ru.practicum.request.model.ParticipationRequest;
+import ru.practicum.request.repository.ParticipationRequestRepository;
+import ru.practicum.user.model.User;
+import ru.practicum.user.repository.UserRepository;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import lombok.RequiredArgsConstructor;
+
+@Service
+@RequiredArgsConstructor
+@Transactional(readOnly = true)
+public class ParticipationRequestServiceImpl implements ParticipationRequestService {
+
+ private final ParticipationRequestRepository requestRepository;
+ private final EventRepository eventRepository;
+ private final UserRepository userRepository;
+
+ @Override
+ @Transactional
+ public ParticipationRequestDto createRequest(Long userId, Long eventId) {
+ User user = getUserByIdOrThrow(userId);
+ Event event = getEventByIdOrThrow(eventId);
+
+ if (requestRepository.existsByEvent_IdAndRequester_Id(eventId, userId)) {
+ throw new ConflictException("Request already exists");
+ }
+
+ if (event.getInitiator().getId().equals(userId)) {
+ throw new ConflictException("Initiator can't participate in own event");
+ }
+
+ if (!EventState.PUBLISHED.equals(event.getState())) {
+ throw new ConflictException("Event must be published");
+ }
+
+ long confirmed =
+ requestRepository.countByEvent_IdAndStatus(eventId, EventRequestStatus.CONFIRMED);
+ if (event.getParticipantLimit() != null
+ && event.getParticipantLimit() > 0
+ && confirmed >= event.getParticipantLimit()) {
+ throw new ConflictException("Participant limit has been reached");
+ }
+
+ EventRequestStatus status = EventRequestStatus.PENDING;
+ if (Boolean.FALSE.equals(event.getRequestModeration())
+ || event.getParticipantLimit() == null
+ || event.getParticipantLimit() == 0) {
+ status = EventRequestStatus.CONFIRMED;
+ }
+
+ ParticipationRequest request =
+ ParticipationRequest.builder()
+ .event(event)
+ .requester(user)
+ .created(LocalDateTime.now())
+ .status(status)
+ .build();
+
+ ParticipationRequest saved = requestRepository.save(request);
+ return ParticipationRequestMapper.toDto(saved);
+ }
+
+ @Override
+ public List getUserRequests(Long userId) {
+ getUserByIdOrThrow(userId);
+ return ParticipationRequestMapper.toDtoList(
+ requestRepository.findAllByRequester_IdOrderByCreatedDesc(userId));
+ }
+
+ @Override
+ @Transactional
+ public ParticipationRequestDto cancelRequest(Long userId, Long requestId) {
+ getUserByIdOrThrow(userId);
+ ParticipationRequest request = getRequestByIdOrThrow(requestId);
+
+ if (!request.getRequester().getId().equals(userId)) {
+ throw new ForbiddenAccessException("You can't cancel request that is not yours");
+ }
+
+ request.setStatus(EventRequestStatus.CANCELED);
+ ParticipationRequest saved = requestRepository.save(request);
+ return ParticipationRequestMapper.toDto(saved);
+ }
+
+ @Override
+ public List getEventRequestsByInitiator(Long userId, Long eventId) {
+ getUserByIdOrThrow(userId);
+ Event event = getEventByIdOrThrow(eventId);
+
+ if (!event.getInitiator().getId().equals(userId)) {
+ throw new ForbiddenAccessException(
+ "You can't view requests for event that is not yours");
+ }
+
+ return ParticipationRequestMapper.toDtoList(
+ requestRepository.findAllByEvent_IdOrderByCreatedAsc(eventId));
+ }
+
+ @Override
+ @Transactional
+ public EventRequestStatusUpdateResult updateEventRequestsStatus(
+ Long userId, Long eventId, EventRequestStatusUpdateRequest updateRequest) {
+ getUserByIdOrThrow(userId);
+ Event event = getEventByIdOrThrow(eventId);
+
+ if (!event.getInitiator().getId().equals(userId)) {
+ throw new ForbiddenAccessException(
+ "You can't update requests for event that is not yours");
+ }
+
+ Set ids = new HashSet<>(updateRequest.requestIds());
+ List requests =
+ requestRepository.findAllByIdInAndEvent_Id(ids, eventId);
+ if (requests.size() != ids.size()) {
+ throw new NotFoundException("Some requests were not found");
+ }
+
+ for (ParticipationRequest r : requests) {
+ if (!EventRequestStatus.PENDING.equals(r.getStatus())) {
+ throw new ConflictException("Only PENDING requests can be updated");
+ }
+ }
+
+ EventRequestStatus targetStatus = updateRequest.status();
+ if (EventRequestStatus.CONFIRMED.equals(targetStatus)) {
+ return confirmRequests(event, requests);
+ }
+ if (EventRequestStatus.REJECTED.equals(targetStatus)) {
+ requests.forEach(r -> r.setStatus(EventRequestStatus.REJECTED));
+ requestRepository.saveAll(requests);
+ return new EventRequestStatusUpdateResult(
+ List.of(), ParticipationRequestMapper.toDtoList(requests));
+ }
+
+ throw new ConflictException("Unsupported status update: " + targetStatus);
+ }
+
+ private EventRequestStatusUpdateResult confirmRequests(
+ Event event, List requests) {
+ int limit = event.getParticipantLimit() == null ? 0 : event.getParticipantLimit();
+ boolean moderation = Boolean.TRUE.equals(event.getRequestModeration());
+
+ if (!moderation || limit == 0) {
+ requests.forEach(r -> r.setStatus(EventRequestStatus.CONFIRMED));
+ requestRepository.saveAll(requests);
+ return new EventRequestStatusUpdateResult(
+ ParticipationRequestMapper.toDtoList(requests), List.of());
+ }
+
+ long confirmed =
+ requestRepository.countByEvent_IdAndStatus(
+ event.getId(), EventRequestStatus.CONFIRMED);
+ long available = limit - confirmed;
+ if (available <= 0) {
+ throw new ConflictException("Participant limit has been reached");
+ }
+
+ List confirmedRequests;
+ List rejectedRequests;
+
+ if (requests.size() <= available) {
+ requests.forEach(r -> r.setStatus(EventRequestStatus.CONFIRMED));
+ requestRepository.saveAll(requests);
+ confirmedRequests = requests;
+ rejectedRequests = List.of();
+ } else {
+ confirmedRequests = requests.subList(0, (int) available);
+ rejectedRequests = requests.subList((int) available, requests.size());
+ confirmedRequests.forEach(r -> r.setStatus(EventRequestStatus.CONFIRMED));
+ rejectedRequests.forEach(r -> r.setStatus(EventRequestStatus.REJECTED));
+ requestRepository.saveAll(requests);
+ }
+
+ long nowConfirmed = confirmed + confirmedRequests.size();
+ if (nowConfirmed >= limit) {
+ List pendingToReject =
+ requestRepository.findAllByEvent_IdAndStatus(
+ event.getId(), EventRequestStatus.PENDING);
+
+ Set touched = idsOf(requests);
+ List toReject =
+ pendingToReject.stream()
+ .filter(r -> !touched.contains(r.getId()))
+ .collect(Collectors.toList());
+ if (!toReject.isEmpty()) {
+ toReject.forEach(r -> r.setStatus(EventRequestStatus.REJECTED));
+ requestRepository.saveAll(toReject);
+ }
+ }
+
+ return new EventRequestStatusUpdateResult(
+ ParticipationRequestMapper.toDtoList(confirmedRequests),
+ ParticipationRequestMapper.toDtoList(rejectedRequests));
+ }
+
+ private static Set idsOf(Collection requests) {
+ return requests.stream().map(ParticipationRequest::getId).collect(Collectors.toSet());
+ }
+
+ private User getUserByIdOrThrow(Long userId) {
+ return userRepository
+ .findById(userId)
+ .orElseThrow(
+ () -> new NotFoundException("User with id=%d not found".formatted(userId)));
+ }
+
+ private Event getEventByIdOrThrow(Long eventId) {
+ return eventRepository
+ .findById(eventId)
+ .orElseThrow(
+ () ->
+ new NotFoundException(
+ "Event with id=%d not found".formatted(eventId)));
+ }
+
+ private ParticipationRequest getRequestByIdOrThrow(Long requestId) {
+ return requestRepository
+ .findById(requestId)
+ .orElseThrow(
+ () ->
+ new NotFoundException(
+ "Request with id=%d not found".formatted(requestId)));
+ }
+}
diff --git a/main-service/src/main/resources/schema.sql b/main-service/src/main/resources/schema.sql
index 17e4a2e..33d7a92 100644
--- a/main-service/src/main/resources/schema.sql
+++ b/main-service/src/main/resources/schema.sql
@@ -53,12 +53,11 @@ CREATE TABLE compilation_events
CREATE TABLE participation_request
(
- id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
- created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- status VARCHAR(20) DEFAULT 'PENDING' CHECK (status IN ('PENDING', 'CONFIRMED', 'REJECTED')),
- event_id BIGINT NOT NULL,
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ created TIMESTAMP NOT NULL,
+ status VARCHAR(20) NOT NULL,
+ event_id BIGINT NOT NULL,
requester_id BIGINT NOT NULL,
-
CONSTRAINT fk_request_event FOREIGN KEY (event_id) REFERENCES event (id),
CONSTRAINT fk_request_user FOREIGN KEY (requester_id) REFERENCES users (id)
);
diff --git a/main-service/src/test/java/ru/practicum/request/ParticipationRequestBatchUpdateIT.java b/main-service/src/test/java/ru/practicum/request/ParticipationRequestBatchUpdateIT.java
new file mode 100644
index 0000000..b8f441b
--- /dev/null
+++ b/main-service/src/test/java/ru/practicum/request/ParticipationRequestBatchUpdateIT.java
@@ -0,0 +1,136 @@
+package ru.practicum.request;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+import ru.practicum.category.model.Category;
+import ru.practicum.category.repository.CategoryRepository;
+import ru.practicum.event.model.Event;
+import ru.practicum.event.model.EventState;
+import ru.practicum.event.model.Location;
+import ru.practicum.event.repository.EventRepository;
+import ru.practicum.request.dto.EventRequestStatusUpdateRequest;
+import ru.practicum.request.dto.EventRequestStatusUpdateResult;
+import ru.practicum.request.dto.ParticipationRequestDto;
+import ru.practicum.request.model.EventRequestStatus;
+import ru.practicum.request.repository.ParticipationRequestRepository;
+import ru.practicum.request.service.ParticipationRequestService;
+import ru.practicum.user.model.User;
+import ru.practicum.user.repository.UserRepository;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.transaction.annotation.Transactional;
+
+@SpringBootTest
+@Transactional
+class ParticipationRequestBatchUpdateIT {
+
+ @Autowired private ParticipationRequestService requestService;
+
+ @Autowired private ParticipationRequestRepository requestRepository;
+
+ @Autowired private UserRepository userRepository;
+
+ @Autowired private CategoryRepository categoryRepository;
+
+ @Autowired private EventRepository eventRepository;
+
+ private User initiator;
+ private User u1;
+ private User u2;
+ private User u3;
+ private Category category;
+
+ @BeforeEach
+ void setUp() {
+ requestRepository.deleteAll();
+ eventRepository.deleteAll();
+ userRepository.deleteAll();
+ categoryRepository.deleteAll();
+
+ initiator = userRepository.save(User.builder().name("init").email("init@mail.com").build());
+ u1 = userRepository.save(User.builder().name("u1").email("u1@mail.com").build());
+ u2 = userRepository.save(User.builder().name("u2").email("u2@mail.com").build());
+ u3 = userRepository.save(User.builder().name("u3").email("u3@mail.com").build());
+ category = categoryRepository.save(Category.builder().name("cat").build());
+ }
+
+ @Test
+ void batchConfirm_shouldConfirmUpToLimit_andRejectRest_andRejectOtherPendingWhenLimitReached() {
+ Event event = eventRepository.save(buildEvent(EventState.PUBLISHED, 2, true));
+
+ ParticipationRequestDto r1 = requestService.createRequest(u1.getId(), event.getId());
+ ParticipationRequestDto r2 = requestService.createRequest(u2.getId(), event.getId());
+ ParticipationRequestDto r3 = requestService.createRequest(u3.getId(), event.getId());
+
+ EventRequestStatusUpdateResult result =
+ requestService.updateEventRequestsStatus(
+ initiator.getId(),
+ event.getId(),
+ new EventRequestStatusUpdateRequest(
+ List.of(r1.id(), r2.id(), r3.id()), EventRequestStatus.CONFIRMED));
+
+ assertEquals(2, result.confirmedRequests().size());
+ assertEquals(1, result.rejectedRequests().size());
+
+ List all =
+ requestService.getEventRequestsByInitiator(initiator.getId(), event.getId());
+
+ long confirmed =
+ all.stream()
+ .filter(r -> r.status().equals(EventRequestStatus.CONFIRMED.name()))
+ .count();
+ long rejected =
+ all.stream()
+ .filter(r -> r.status().equals(EventRequestStatus.REJECTED.name()))
+ .count();
+
+ assertEquals(2, confirmed);
+ assertEquals(1, rejected);
+ }
+
+ @Test
+ void batchReject_shouldRejectOnlyProvidedPending() {
+ Event event = eventRepository.save(buildEvent(EventState.PUBLISHED, 10, true));
+
+ ParticipationRequestDto r1 = requestService.createRequest(u1.getId(), event.getId());
+ ParticipationRequestDto r2 = requestService.createRequest(u2.getId(), event.getId());
+
+ EventRequestStatusUpdateResult result =
+ requestService.updateEventRequestsStatus(
+ initiator.getId(),
+ event.getId(),
+ new EventRequestStatusUpdateRequest(
+ List.of(r1.id(), r2.id()), EventRequestStatus.REJECTED));
+
+ assertEquals(0, result.confirmedRequests().size());
+ assertEquals(2, result.rejectedRequests().size());
+ }
+
+ private Event buildEvent(EventState state, int participantLimit, boolean moderation) {
+ return Event.builder()
+ .title("title")
+ .annotation("ann")
+ .description("desc")
+ .createdOn(LocalDateTime.now().minusMinutes(1))
+ .eventDate(LocalDateTime.now().plusDays(1))
+ .paid(false)
+ .participantLimit(participantLimit)
+ .requestModeration(moderation)
+ .state(state)
+ .initiator(initiator)
+ .category(category)
+ .location(
+ Location.builder()
+ .lat(BigDecimal.valueOf(55.755800))
+ .lon(BigDecimal.valueOf(37.617300))
+ .build())
+ .build();
+ }
+}
diff --git a/main-service/src/test/java/ru/practicum/request/ParticipationRequestServiceIT.java b/main-service/src/test/java/ru/practicum/request/ParticipationRequestServiceIT.java
new file mode 100644
index 0000000..8965c6f
--- /dev/null
+++ b/main-service/src/test/java/ru/practicum/request/ParticipationRequestServiceIT.java
@@ -0,0 +1,122 @@
+package ru.practicum.request;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+import ru.practicum.category.model.Category;
+import ru.practicum.category.repository.CategoryRepository;
+import ru.practicum.event.model.Event;
+import ru.practicum.event.model.EventState;
+import ru.practicum.event.model.Location;
+import ru.practicum.event.repository.EventRepository;
+import ru.practicum.exception.ConflictException;
+import ru.practicum.request.dto.ParticipationRequestDto;
+import ru.practicum.request.model.EventRequestStatus;
+import ru.practicum.request.repository.ParticipationRequestRepository;
+import ru.practicum.request.service.ParticipationRequestService;
+import ru.practicum.user.model.User;
+import ru.practicum.user.repository.UserRepository;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.transaction.annotation.Transactional;
+
+@SpringBootTest
+@Transactional
+class ParticipationRequestServiceIT {
+
+ @Autowired private ParticipationRequestService requestService;
+
+ @Autowired private ParticipationRequestRepository requestRepository;
+
+ @Autowired private UserRepository userRepository;
+
+ @Autowired private CategoryRepository categoryRepository;
+
+ @Autowired private EventRepository eventRepository;
+
+ private User initiator;
+ private User requester;
+ private Category category;
+
+ @BeforeEach
+ void setUp() {
+ requestRepository.deleteAll();
+ eventRepository.deleteAll();
+ userRepository.deleteAll();
+ categoryRepository.deleteAll();
+
+ initiator = userRepository.save(User.builder().name("init").email("init@mail.com").build());
+ requester = userRepository.save(User.builder().name("req").email("req@mail.com").build());
+ category = categoryRepository.save(Category.builder().name("cat").build());
+ }
+
+ @Test
+ void createRequest_shouldConfirmImmediately_whenModerationOff() {
+ Event event = eventRepository.save(buildEvent(EventState.PUBLISHED, 10, false));
+
+ ParticipationRequestDto dto =
+ requestService.createRequest(requester.getId(), event.getId());
+
+ assertEquals(event.getId(), dto.event());
+ assertEquals(requester.getId(), dto.requester());
+ assertEquals(EventRequestStatus.CONFIRMED.name(), dto.status());
+ assertNotNull(dto.created());
+ assertTrue(requestRepository.existsById(dto.id()));
+ }
+
+ @Test
+ void createRequest_shouldThrowConflict_whenEventNotPublished() {
+ Event event = eventRepository.save(buildEvent(EventState.PENDING, 10, true));
+
+ assertThrows(
+ ConflictException.class,
+ () -> requestService.createRequest(requester.getId(), event.getId()));
+ }
+
+ @Test
+ void createRequest_shouldThrowConflict_whenRequesterIsInitiator() {
+ Event event = eventRepository.save(buildEvent(EventState.PUBLISHED, 10, true));
+
+ assertThrows(
+ ConflictException.class,
+ () -> requestService.createRequest(initiator.getId(), event.getId()));
+ }
+
+ @Test
+ void cancelRequest_shouldSetCanceledStatus() {
+ Event event = eventRepository.save(buildEvent(EventState.PUBLISHED, 10, true));
+
+ ParticipationRequestDto created =
+ requestService.createRequest(requester.getId(), event.getId());
+ ParticipationRequestDto canceled =
+ requestService.cancelRequest(requester.getId(), created.id());
+
+ assertEquals(EventRequestStatus.CANCELED.name(), canceled.status());
+ }
+
+ private Event buildEvent(EventState state, int participantLimit, boolean moderation) {
+ return Event.builder()
+ .title("title")
+ .annotation("ann")
+ .description("desc")
+ .createdOn(LocalDateTime.now().minusMinutes(1))
+ .eventDate(LocalDateTime.now().plusDays(1))
+ .paid(false)
+ .participantLimit(participantLimit)
+ .requestModeration(moderation)
+ .state(state)
+ .initiator(initiator)
+ .category(category)
+ .location(
+ Location.builder()
+ .lat(BigDecimal.valueOf(55.755800))
+ .lon(BigDecimal.valueOf(37.617300))
+ .build())
+ .build();
+ }
+}
diff --git a/main-service/src/test/java/ru/practicum/request/ParticipationRequestsControllerIT.java b/main-service/src/test/java/ru/practicum/request/ParticipationRequestsControllerIT.java
new file mode 100644
index 0000000..c031aa5
--- /dev/null
+++ b/main-service/src/test/java/ru/practicum/request/ParticipationRequestsControllerIT.java
@@ -0,0 +1,421 @@
+package ru.practicum.request;
+
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+import ru.practicum.category.model.Category;
+import ru.practicum.category.repository.CategoryRepository;
+import ru.practicum.event.model.Event;
+import ru.practicum.event.model.EventState;
+import ru.practicum.event.model.Location;
+import ru.practicum.event.repository.EventRepository;
+import ru.practicum.request.model.EventRequestStatus;
+import ru.practicum.request.repository.ParticipationRequestRepository;
+import ru.practicum.user.model.User;
+import ru.practicum.user.repository.UserRepository;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@ActiveProfiles("dev")
+@Transactional
+class ParticipationRequestsIT {
+
+ @Autowired MockMvc mvc;
+ @Autowired ObjectMapper om;
+
+ @Autowired UserRepository userRepository;
+ @Autowired CategoryRepository categoryRepository;
+ @Autowired EventRepository eventRepository;
+ @Autowired ParticipationRequestRepository requestRepository;
+
+ private Long initiatorId;
+ private Long requesterId;
+ private Long publishedEventId;
+
+ @BeforeEach
+ void setUp() {
+ User initiator =
+ userRepository.save(
+ User.builder().name("initiator").email("initiator@mail.com").build());
+ User requester =
+ userRepository.save(
+ User.builder().name("requester").email("requester@mail.com").build());
+
+ Category category =
+ categoryRepository.save(
+ Category.builder().name("cat-" + System.nanoTime()).build());
+
+ Event published =
+ eventRepository.save(
+ Event.builder()
+ .annotation("annotation-annotation-annotation")
+ .category(category)
+ .createdOn(LocalDateTime.now())
+ .description("description-description-description")
+ .eventDate(LocalDateTime.now().plusDays(3))
+ .initiator(initiator)
+ .location(
+ Location.builder()
+ .lat(new BigDecimal("55.750000"))
+ .lon(new BigDecimal("37.610000"))
+ .build())
+ .paid(false)
+ .participantLimit(10)
+ .requestModeration(true)
+ .state(EventState.PUBLISHED)
+ .title("title")
+ .publishedOn(LocalDateTime.now())
+ .build());
+
+ initiatorId = initiator.getId();
+ requesterId = requester.getId();
+ publishedEventId = published.getId();
+ }
+
+ @Test
+ void createRequest_ok_pending() throws Exception {
+ mvc.perform(
+ post("/users/{userId}/requests", requesterId)
+ .queryParam("eventId", String.valueOf(publishedEventId))
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
+ .andExpect(jsonPath("$.id", notNullValue()))
+ .andExpect(jsonPath("$.event", is(publishedEventId.intValue())))
+ .andExpect(jsonPath("$.requester", is(requesterId.intValue())))
+ .andExpect(jsonPath("$.created", notNullValue()))
+ .andExpect(jsonPath("$.status", is(EventRequestStatus.PENDING.name())));
+ }
+
+ @Test
+ void createRequest_withoutEventId_400() throws Exception {
+ mvc.perform(
+ post("/users/{userId}/requests", requesterId)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isBadRequest());
+ }
+
+ @Test
+ void createRequest_duplicate_409() throws Exception {
+ createRequestAndGetId(requesterId, publishedEventId);
+
+ mvc.perform(
+ post("/users/{userId}/requests", requesterId)
+ .queryParam("eventId", String.valueOf(publishedEventId))
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isConflict());
+ }
+
+ @Test
+ void createRequest_byInitiator_409() throws Exception {
+ mvc.perform(
+ post("/users/{userId}/requests", initiatorId)
+ .queryParam("eventId", String.valueOf(publishedEventId))
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isConflict());
+ }
+
+ @Test
+ void createRequest_onNotPublished_409() throws Exception {
+ User initiator = userRepository.findById(initiatorId).orElseThrow();
+ Category category = categoryRepository.findAll().stream().findFirst().orElseThrow();
+
+ Event pending =
+ eventRepository.save(
+ Event.builder()
+ .annotation("annotation-annotation-annotation")
+ .category(category)
+ .createdOn(LocalDateTime.now())
+ .description("description-description-description")
+ .eventDate(LocalDateTime.now().plusDays(3))
+ .initiator(initiator)
+ .location(
+ Location.builder()
+ .lat(new BigDecimal("55.750000"))
+ .lon(new BigDecimal("37.610000"))
+ .build())
+ .paid(false)
+ .participantLimit(10)
+ .requestModeration(true)
+ .state(EventState.PENDING)
+ .title("title")
+ .build());
+
+ mvc.perform(
+ post("/users/{userId}/requests", requesterId)
+ .queryParam("eventId", String.valueOf(pending.getId()))
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isConflict());
+ }
+
+ @Test
+ void createRequest_limitReached_409() throws Exception {
+ User initiator = userRepository.findById(initiatorId).orElseThrow();
+ Category category = categoryRepository.findAll().stream().findFirst().orElseThrow();
+
+ Event event =
+ eventRepository.save(
+ Event.builder()
+ .annotation("annotation-annotation-annotation")
+ .category(category)
+ .createdOn(LocalDateTime.now())
+ .description("description-description-description")
+ .eventDate(LocalDateTime.now().plusDays(3))
+ .initiator(initiator)
+ .location(
+ Location.builder()
+ .lat(new BigDecimal("55.750000"))
+ .lon(new BigDecimal("37.610000"))
+ .build())
+ .paid(false)
+ .participantLimit(1)
+ .requestModeration(true)
+ .state(EventState.PUBLISHED)
+ .title("title")
+ .publishedOn(LocalDateTime.now())
+ .build());
+
+ long r1 = createRequestAndGetId(requesterId, event.getId());
+ String body =
+ om.writeValueAsString(Map.of("requestIds", List.of(r1), "status", "CONFIRMED"));
+ mvc.perform(
+ patch(
+ "/users/{userId}/events/{eventId}/requests",
+ initiatorId,
+ event.getId())
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(body)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk());
+
+ User requester2 =
+ userRepository.save(User.builder().name("r2").email("r2@mail.com").build());
+
+ mvc.perform(
+ post("/users/{userId}/requests", requester2.getId())
+ .queryParam("eventId", String.valueOf(event.getId()))
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isConflict());
+ }
+
+ @Test
+ void createRequest_moderationFalse_autoConfirmed() throws Exception {
+ User initiator = userRepository.findById(initiatorId).orElseThrow();
+ Category category = categoryRepository.findAll().stream().findFirst().orElseThrow();
+
+ Event event =
+ eventRepository.save(
+ Event.builder()
+ .annotation("annotation-annotation-annotation")
+ .category(category)
+ .createdOn(LocalDateTime.now())
+ .description("description-description-description")
+ .eventDate(LocalDateTime.now().plusDays(3))
+ .initiator(initiator)
+ .location(
+ Location.builder()
+ .lat(new BigDecimal("55.750000"))
+ .lon(new BigDecimal("37.610000"))
+ .build())
+ .paid(false)
+ .participantLimit(10)
+ .requestModeration(false)
+ .state(EventState.PUBLISHED)
+ .title("title")
+ .publishedOn(LocalDateTime.now())
+ .build());
+
+ mvc.perform(
+ post("/users/{userId}/requests", requesterId)
+ .queryParam("eventId", String.valueOf(event.getId()))
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.status", is(EventRequestStatus.CONFIRMED.name())));
+ }
+
+ @Test
+ void getUserRequests_ok_containsCreated() throws Exception {
+ long reqId = createRequestAndGetId(requesterId, publishedEventId);
+
+ mvc.perform(get("/users/{userId}/requests", requesterId).accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
+ .andExpect(jsonPath("$[*].id", hasItem((int) reqId)));
+ }
+
+ @Test
+ void cancelRequest_ok_canceled() throws Exception {
+ long reqId = createRequestAndGetId(requesterId, publishedEventId);
+
+ mvc.perform(
+ patch("/users/{userId}/requests/{requestId}/cancel", requesterId, reqId)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is((int) reqId)))
+ .andExpect(jsonPath("$.status", is(EventRequestStatus.CANCELED.name())));
+ }
+
+ @Test
+ void getEventRequestsByInitiator_ok_containsCreated() throws Exception {
+ long reqId = createRequestAndGetId(requesterId, publishedEventId);
+
+ mvc.perform(
+ get(
+ "/users/{userId}/events/{eventId}/requests",
+ initiatorId,
+ publishedEventId)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$[*].id", hasItem((int) reqId)));
+ }
+
+ @Test
+ void updateRequestsStatus_confirm_ok() throws Exception {
+ long reqId = createRequestAndGetId(requesterId, publishedEventId);
+
+ String body =
+ om.writeValueAsString(Map.of("requestIds", List.of(reqId), "status", "CONFIRMED"));
+
+ mvc.perform(
+ patch(
+ "/users/{userId}/events/{eventId}/requests",
+ initiatorId,
+ publishedEventId)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(body)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.confirmedRequests[0].id", is((int) reqId)))
+ .andExpect(
+ jsonPath(
+ "$.confirmedRequests[0].status",
+ is(EventRequestStatus.CONFIRMED.name())));
+ }
+
+ @Test
+ void updateRequestsStatus_reject_ok() throws Exception {
+ long reqId = createRequestAndGetId(requesterId, publishedEventId);
+
+ String body =
+ om.writeValueAsString(Map.of("requestIds", List.of(reqId), "status", "REJECTED"));
+
+ mvc.perform(
+ patch(
+ "/users/{userId}/events/{eventId}/requests",
+ initiatorId,
+ publishedEventId)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(body)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.rejectedRequests[0].id", is((int) reqId)))
+ .andExpect(
+ jsonPath(
+ "$.rejectedRequests[0].status",
+ is(EventRequestStatus.REJECTED.name())));
+ }
+
+ @Test
+ void updateRequestsStatus_nonPending_409() throws Exception {
+ long reqId = createRequestAndGetId(requesterId, publishedEventId);
+
+ String confirm =
+ om.writeValueAsString(Map.of("requestIds", List.of(reqId), "status", "CONFIRMED"));
+
+ mvc.perform(
+ patch(
+ "/users/{userId}/events/{eventId}/requests",
+ initiatorId,
+ publishedEventId)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(confirm)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk());
+
+ String reject =
+ om.writeValueAsString(Map.of("requestIds", List.of(reqId), "status", "REJECTED"));
+
+ mvc.perform(
+ patch(
+ "/users/{userId}/events/{eventId}/requests",
+ initiatorId,
+ publishedEventId)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(reject)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isConflict());
+ }
+
+ @Test
+ void updateRequestsStatus_emptyIds_400() throws Exception {
+ String body = om.writeValueAsString(Map.of("requestIds", List.of(), "status", "CONFIRMED"));
+
+ mvc.perform(
+ patch(
+ "/users/{userId}/events/{eventId}/requests",
+ initiatorId,
+ publishedEventId)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(body)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isBadRequest());
+ }
+
+ @Test
+ void updateRequestsStatus_nullStatus_400() throws Exception {
+ String body =
+ "{\"requestIds\":["
+ + createRequestAndGetId(requesterId, publishedEventId)
+ + "],\"status\":null}";
+
+ mvc.perform(
+ patch(
+ "/users/{userId}/events/{eventId}/requests",
+ initiatorId,
+ publishedEventId)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(body)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isBadRequest());
+ }
+
+ private long createRequestAndGetId(long userId, long eventId) throws Exception {
+ String json =
+ mvc.perform(
+ post("/users/{userId}/requests", userId)
+ .queryParam("eventId", String.valueOf(eventId))
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ JsonNode node = om.readTree(json);
+ return node.get("id").asLong();
+ }
+}
diff --git a/main-service/src/test/resources/application-test.yaml b/main-service/src/test/resources/application-test.yaml
new file mode 100644
index 0000000..a7b2c55
--- /dev/null
+++ b/main-service/src/test/resources/application-test.yaml
@@ -0,0 +1,26 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:ewm-test-db;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=TRUE
+ driver-class-name: org.h2.Driver
+ username: sa
+ password:
+
+ sql:
+ init:
+ mode: always
+ schema-locations: classpath:schema.sql
+
+ jpa:
+ hibernate:
+ ddl-auto: none
+ properties:
+ hibernate:
+ format_sql: true
+
+logging:
+ level:
+ org.hibernate.SQL: debug
+
+stats-server:
+ url: http://localhost:9090
+ app: ewm-main-service
\ No newline at end of file