From c8575af992cde61dc91b5f5922b9039714d8c543 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Wed, 24 Dec 2025 12:44:35 +0300 Subject: [PATCH 01/10] feat: add json localdatetime global formatting --- .../java/ru/practicum/config/JsonConfig.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 main-service/src/main/java/ru/practicum/config/JsonConfig.java diff --git a/main-service/src/main/java/ru/practicum/config/JsonConfig.java b/main-service/src/main/java/ru/practicum/config/JsonConfig.java new file mode 100644 index 0000000..7a538bc --- /dev/null +++ b/main-service/src/main/java/ru/practicum/config/JsonConfig.java @@ -0,0 +1,23 @@ +package ru.practicum.config; + +import java.time.format.DateTimeFormatter; + +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; + +@Configuration +public class JsonConfig { + private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { + return builder -> { + builder.simpleDateFormat(DATE_TIME_PATTERN); + builder.serializers( + new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN))); + }; + } +} From c9ac4e55cc01c504bdb68f921a0a8a5a2893b7b8 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Wed, 24 Dec 2025 12:44:56 +0300 Subject: [PATCH 02/10] feat: add exception handler --- .../ru/practicum/exception/dto/ApiError.java | 11 +++ .../ru/practicum/exception/dto/Violation.java | 3 + .../handler/GlobalExceptionHandler.java | 80 +++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 main-service/src/main/java/ru/practicum/exception/dto/ApiError.java create mode 100644 main-service/src/main/java/ru/practicum/exception/dto/Violation.java create mode 100644 main-service/src/main/java/ru/practicum/exception/handler/GlobalExceptionHandler.java diff --git a/main-service/src/main/java/ru/practicum/exception/dto/ApiError.java b/main-service/src/main/java/ru/practicum/exception/dto/ApiError.java new file mode 100644 index 0000000..65b2421 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/exception/dto/ApiError.java @@ -0,0 +1,11 @@ +package ru.practicum.exception.dto; + +import java.time.LocalDateTime; +import java.util.List; + +public record ApiError( + List errors, + String message, + String reason, + String status, + LocalDateTime timestamp) {} diff --git a/main-service/src/main/java/ru/practicum/exception/dto/Violation.java b/main-service/src/main/java/ru/practicum/exception/dto/Violation.java new file mode 100644 index 0000000..39174d4 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/exception/dto/Violation.java @@ -0,0 +1,3 @@ +package ru.practicum.exception.dto; + +public record Violation(String fieldName, String 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 new file mode 100644 index 0000000..00d1ea5 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/exception/handler/GlobalExceptionHandler.java @@ -0,0 +1,80 @@ +package ru.practicum.exception.handler; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +import jakarta.validation.ConstraintViolationException; + +import ru.practicum.exception.dto.ApiError; +import ru.practicum.exception.dto.Violation; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ApiError handleException(Exception e) { + log.error(e.getMessage(), e); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + String stackTrace = sw.toString(); + + return new ApiError( + List.of(stackTrace), + "An error occured while processing request", + "Exception", + HttpStatus.INTERNAL_SERVER_ERROR.toString(), + LocalDateTime.now()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(ConstraintViolationException.class) + public ApiError handleConstraintValidationException(ConstraintViolationException e) { + final List violations = + e.getConstraintViolations().stream() + .map( + violation -> + new Violation( + violation.getPropertyPath().toString(), + violation.getMessage())) + .collect(Collectors.toList()); + log.warn(violations.toString()); + List errors = violations.stream().map(Violation::toString).toList(); + return new ApiError( + errors, + e.getMessage(), + "Some fields of RequestBody for request are invalid", + HttpStatus.BAD_REQUEST.toString(), + LocalDateTime.now()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(MethodArgumentNotValidException.class) + public ApiError handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + final List violations = + e.getBindingResult().getFieldErrors().stream() + .map(error -> new Violation(error.getField(), error.getDefaultMessage())) + .collect(Collectors.toList()); + log.warn(violations.toString()); + List errors = violations.stream().map(Violation::toString).toList(); + return new ApiError( + errors, + e.getMessage(), + "Some fields of RequestBody for request are invalid", + HttpStatus.BAD_REQUEST.toString(), + LocalDateTime.now()); + } +} From 174ec192a053990a6f27d48c3aa931cc1ace7ee4 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Wed, 24 Dec 2025 13:55:25 +0300 Subject: [PATCH 03/10] feat: add global localdatetime deserializer --- main-service/src/main/java/ru/practicum/config/JsonConfig.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main-service/src/main/java/ru/practicum/config/JsonConfig.java b/main-service/src/main/java/ru/practicum/config/JsonConfig.java index 7a538bc..feaa326 100644 --- a/main-service/src/main/java/ru/practicum/config/JsonConfig.java +++ b/main-service/src/main/java/ru/practicum/config/JsonConfig.java @@ -6,6 +6,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; @Configuration @@ -18,6 +19,8 @@ public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { builder.simpleDateFormat(DATE_TIME_PATTERN); builder.serializers( new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN))); + builder.deserializers( + new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN))); }; } } From 1e69d9d98108ca67c8488141bc19b74b36f0b3ee Mon Sep 17 00:00:00 2001 From: LightInTheFire <109972737+LightInTheFire@users.noreply.github.com> Date: Thu, 25 Dec 2025 19:48:20 +0300 Subject: [PATCH 04/10] Add schema, entity and dto (#6) * feat: add validation starter dep * feat: add all dto's from spec * feat: add schema * feat: add entities * feat: add schema and basic readme * chore: apply sql format * feat: add stats service db schema and centering * fix: remove views from model * fix: remove views from model --- README.MD | 22 +++++++ assets/readme/main-service-schema.svg | 20 ++++++ assets/readme/stats-service-schema.svg | 20 ++++++ main-service/pom.xml | 5 ++ .../practicum/category/dto/CategoryDto.java | 3 + .../category/dto/NewCategoryDto.java | 6 ++ .../ru/practicum/category/model/Category.java | 24 +++++++ .../compilation/dto/CompilationDto.java | 7 ++ .../compilation/dto/NewCompilationDto.java | 14 ++++ .../dto/UpdateCompilationRequest.java | 21 ++++++ .../compilation/model/Compilation.java | 39 +++++++++++ .../ru/practicum/event/dto/EventFullDto.java | 25 +++++++ .../ru/practicum/event/dto/EventShortDto.java | 17 +++++ .../ru/practicum/event/dto/LocationDto.java | 11 ++++ .../ru/practicum/event/dto/NewEventDto.java | 25 +++++++ .../event/dto/UpdateEventAdminRequest.java | 61 +++++++++++++++++ .../event/dto/UpdateEventUserRequest.java | 61 +++++++++++++++++ .../java/ru/practicum/event/model/Event.java | 66 +++++++++++++++++++ .../ru/practicum/event/model/EventState.java | 7 ++ .../ru/practicum/event/model/Location.java | 24 +++++++ .../ru/practicum/event/model/StateAction.java | 6 ++ .../dto/EventRequestStatusUpdateRequest.java | 11 ++++ .../dto/EventRequestStatusUpdateResult.java | 7 ++ .../request/dto/ParticipationRequestDto.java | 6 ++ .../request/model/EventRequestStatus.java | 7 ++ .../request/model/ParticipationRequest.java | 41 ++++++++++++ .../ru/practicum/user/dto/NewUserRequest.java | 9 +++ .../java/ru/practicum/user/dto/UserDto.java | 3 + .../ru/practicum/user/dto/UserShortDto.java | 3 + .../java/ru/practicum/user/model/User.java | 27 ++++++++ .../src/main/resources/application.yaml | 6 ++ main-service/src/main/resources/schema.sql | 64 ++++++++++++++++++ .../stat-server/src/main/resources/schema.sql | 13 ++-- 33 files changed, 675 insertions(+), 6 deletions(-) create mode 100644 README.MD create mode 100644 assets/readme/main-service-schema.svg create mode 100644 assets/readme/stats-service-schema.svg create mode 100644 main-service/src/main/java/ru/practicum/category/dto/CategoryDto.java create mode 100644 main-service/src/main/java/ru/practicum/category/dto/NewCategoryDto.java create mode 100644 main-service/src/main/java/ru/practicum/category/model/Category.java create mode 100644 main-service/src/main/java/ru/practicum/compilation/dto/CompilationDto.java create mode 100644 main-service/src/main/java/ru/practicum/compilation/dto/NewCompilationDto.java create mode 100644 main-service/src/main/java/ru/practicum/compilation/dto/UpdateCompilationRequest.java create mode 100644 main-service/src/main/java/ru/practicum/compilation/model/Compilation.java create mode 100644 main-service/src/main/java/ru/practicum/event/dto/EventFullDto.java create mode 100644 main-service/src/main/java/ru/practicum/event/dto/EventShortDto.java create mode 100644 main-service/src/main/java/ru/practicum/event/dto/LocationDto.java create mode 100644 main-service/src/main/java/ru/practicum/event/dto/NewEventDto.java create mode 100644 main-service/src/main/java/ru/practicum/event/dto/UpdateEventAdminRequest.java create mode 100644 main-service/src/main/java/ru/practicum/event/dto/UpdateEventUserRequest.java create mode 100644 main-service/src/main/java/ru/practicum/event/model/Event.java create mode 100644 main-service/src/main/java/ru/practicum/event/model/EventState.java create mode 100644 main-service/src/main/java/ru/practicum/event/model/Location.java create mode 100644 main-service/src/main/java/ru/practicum/event/model/StateAction.java create mode 100644 main-service/src/main/java/ru/practicum/request/dto/EventRequestStatusUpdateRequest.java create mode 100644 main-service/src/main/java/ru/practicum/request/dto/EventRequestStatusUpdateResult.java create mode 100644 main-service/src/main/java/ru/practicum/request/dto/ParticipationRequestDto.java create mode 100644 main-service/src/main/java/ru/practicum/request/model/EventRequestStatus.java create mode 100644 main-service/src/main/java/ru/practicum/request/model/ParticipationRequest.java create mode 100644 main-service/src/main/java/ru/practicum/user/dto/NewUserRequest.java create mode 100644 main-service/src/main/java/ru/practicum/user/dto/UserDto.java create mode 100644 main-service/src/main/java/ru/practicum/user/dto/UserShortDto.java create mode 100644 main-service/src/main/java/ru/practicum/user/model/User.java create mode 100644 main-service/src/main/resources/schema.sql diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..58ec58d --- /dev/null +++ b/README.MD @@ -0,0 +1,22 @@ +# Explore with me + +Repository for explore with me app - social network for sharing interesting events with users and find +companies to participate in them. Written in java. + +# Contributors + +[LightInTheFire](https://github.com/LightInTheFire)
+[Basarus](https://github.com/Basarus)
+[Kreidl](https://github.com/Kreidl)
+ +# Main service db schema + +

+ db schema +

+ +# Stats service db schema + +

+ db schema +

diff --git a/assets/readme/main-service-schema.svg b/assets/readme/main-service-schema.svg new file mode 100644 index 0000000..96f2dbc --- /dev/null +++ b/assets/readme/main-service-schema.svg @@ -0,0 +1,20 @@ +1*1*1*1*1*1*categoryidBIGINTnameVARCHAR(50)usersidBIGINTnameVARCHAR(250)emailVARCHAR(254)eventidBIGINTtitleVARCHAR(120)annotationVARCHAR(2000)descriptionVARCHAR(7000)event_dateTIMESTAMPcreated_onTIMESTAMPpublished_onTIMESTAMPpaidBOOLEANparticipant_limitINTrequest_moderationBOOLEANstateVARCHAR(20)location_latNUMERIC(9,6)location_lonNUMERIC(9,6)category_idBIGINTinitiator_idBIGINTcompilationidBIGINTtitleVARCHAR(50)pinnedBOOLEANcompilation_eventscompilation_idBIGINTevent_idBIGINTparticipation_requestidBIGINTcreatedTIMESTAMPstatusVARCHAR(20)event_idBIGINTrequester_idBIGINT \ No newline at end of file diff --git a/assets/readme/stats-service-schema.svg b/assets/readme/stats-service-schema.svg new file mode 100644 index 0000000..1bc90b0 --- /dev/null +++ b/assets/readme/stats-service-schema.svg @@ -0,0 +1,20 @@ +statidBIGINTappVARCHAR(255)uriVARCHAR(512)ipVARCHAR(45)createdTIMESTAMP \ No newline at end of file diff --git a/main-service/pom.xml b/main-service/pom.xml index a03c392..82a4b30 100644 --- a/main-service/pom.xml +++ b/main-service/pom.xml @@ -51,6 +51,11 @@ org.postgresql postgresql + + + org.springframework.boot + spring-boot-starter-validation + diff --git a/main-service/src/main/java/ru/practicum/category/dto/CategoryDto.java b/main-service/src/main/java/ru/practicum/category/dto/CategoryDto.java new file mode 100644 index 0000000..d4bf840 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/category/dto/CategoryDto.java @@ -0,0 +1,3 @@ +package ru.practicum.category.dto; + +public record CategoryDto(Long id, String name) {} diff --git a/main-service/src/main/java/ru/practicum/category/dto/NewCategoryDto.java b/main-service/src/main/java/ru/practicum/category/dto/NewCategoryDto.java new file mode 100644 index 0000000..3d04b86 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/category/dto/NewCategoryDto.java @@ -0,0 +1,6 @@ +package ru.practicum.category.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record NewCategoryDto(@Size(min = 1, max = 50) @NotBlank String name) {} diff --git a/main-service/src/main/java/ru/practicum/category/model/Category.java b/main-service/src/main/java/ru/practicum/category/model/Category.java new file mode 100644 index 0000000..203610c --- /dev/null +++ b/main-service/src/main/java/ru/practicum/category/model/Category.java @@ -0,0 +1,24 @@ +package ru.practicum.category.model; + +import jakarta.persistence.*; + +import lombok.experimental.FieldDefaults; +import lombok.*; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "category") +@FieldDefaults(level = AccessLevel.PRIVATE) +public class Category { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + @Column(nullable = false, unique = true) + String name; +} diff --git a/main-service/src/main/java/ru/practicum/compilation/dto/CompilationDto.java b/main-service/src/main/java/ru/practicum/compilation/dto/CompilationDto.java new file mode 100644 index 0000000..3cc0c11 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/compilation/dto/CompilationDto.java @@ -0,0 +1,7 @@ +package ru.practicum.compilation.dto; + +import java.util.List; + +import ru.practicum.event.dto.EventShortDto; + +public record CompilationDto(List events, Long id, boolean pinned, String title) {} diff --git a/main-service/src/main/java/ru/practicum/compilation/dto/NewCompilationDto.java b/main-service/src/main/java/ru/practicum/compilation/dto/NewCompilationDto.java new file mode 100644 index 0000000..63885a3 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/compilation/dto/NewCompilationDto.java @@ -0,0 +1,14 @@ +package ru.practicum.compilation.dto; + +import java.util.List; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record NewCompilationDto( + List events, Boolean pinned, @Size(min = 1, max = 50) @NotBlank String title) { + public NewCompilationDto { + if (pinned == null) pinned = false; + if (events == null) events = List.of(); + } +} diff --git a/main-service/src/main/java/ru/practicum/compilation/dto/UpdateCompilationRequest.java b/main-service/src/main/java/ru/practicum/compilation/dto/UpdateCompilationRequest.java new file mode 100644 index 0000000..9b18070 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/compilation/dto/UpdateCompilationRequest.java @@ -0,0 +1,21 @@ +package ru.practicum.compilation.dto; + +import java.util.List; + +import jakarta.validation.constraints.Size; + +public record UpdateCompilationRequest( + List events, Boolean pinned, @Size(min = 1, max = 50) String title) { + + public boolean hasEvents() { + return events != null; + } + + public boolean hasPinned() { + return pinned != null; + } + + public boolean hasTitle() { + return title != null && !title.isBlank(); + } +} diff --git a/main-service/src/main/java/ru/practicum/compilation/model/Compilation.java b/main-service/src/main/java/ru/practicum/compilation/model/Compilation.java new file mode 100644 index 0000000..7fc3523 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/compilation/model/Compilation.java @@ -0,0 +1,39 @@ +package ru.practicum.compilation.model; + +import java.util.LinkedHashSet; +import java.util.Set; + +import jakarta.persistence.*; + +import ru.practicum.event.model.Event; + +import lombok.experimental.FieldDefaults; +import lombok.*; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "compilation") +@FieldDefaults(level = AccessLevel.PRIVATE) +public class Compilation { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + Long id; + + @Column(name = "title", nullable = false) + String title; + + @Column(name = "pinned") + Boolean pinned; + + @ManyToMany + @JoinTable( + name = "compilation_events", + joinColumns = @JoinColumn(name = "compilation_id"), + inverseJoinColumns = @JoinColumn(name = "event_id")) + Set events = new LinkedHashSet<>(); +} diff --git a/main-service/src/main/java/ru/practicum/event/dto/EventFullDto.java b/main-service/src/main/java/ru/practicum/event/dto/EventFullDto.java new file mode 100644 index 0000000..6c5788b --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/dto/EventFullDto.java @@ -0,0 +1,25 @@ +package ru.practicum.event.dto; + +import java.time.LocalDateTime; + +import ru.practicum.category.dto.CategoryDto; +import ru.practicum.event.model.EventState; +import ru.practicum.user.dto.UserShortDto; + +public record EventFullDto( + String annotation, + CategoryDto category, + Long confirmedRequests, + LocalDateTime createdOn, + String description, + LocalDateTime eventDate, + Long id, + UserShortDto initiator, + LocationDto location, + boolean paid, + Integer participantLimit, + LocalDateTime publishedOn, + boolean requestModeration, + EventState state, + String title, + Long views) {} diff --git a/main-service/src/main/java/ru/practicum/event/dto/EventShortDto.java b/main-service/src/main/java/ru/practicum/event/dto/EventShortDto.java new file mode 100644 index 0000000..5f102c4 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/dto/EventShortDto.java @@ -0,0 +1,17 @@ +package ru.practicum.event.dto; + +import java.time.LocalDateTime; + +import ru.practicum.category.dto.CategoryDto; +import ru.practicum.user.dto.UserShortDto; + +public record EventShortDto( + String annotation, + CategoryDto category, + Long confirmedRequests, + LocalDateTime eventDate, + Long id, + UserShortDto initiator, + boolean paid, + String title, + Long views) {} diff --git a/main-service/src/main/java/ru/practicum/event/dto/LocationDto.java b/main-service/src/main/java/ru/practicum/event/dto/LocationDto.java new file mode 100644 index 0000000..cfbb0de --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/dto/LocationDto.java @@ -0,0 +1,11 @@ +package ru.practicum.event.dto; + +import java.math.BigDecimal; + +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotNull; + +public record LocationDto( + @NotNull @DecimalMin("-90.0") @DecimalMax("90.0") BigDecimal lat, + @NotNull @DecimalMin("-180.0") @DecimalMax("180.0") BigDecimal lon) {} diff --git a/main-service/src/main/java/ru/practicum/event/dto/NewEventDto.java b/main-service/src/main/java/ru/practicum/event/dto/NewEventDto.java new file mode 100644 index 0000000..323e75f --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/dto/NewEventDto.java @@ -0,0 +1,25 @@ +package ru.practicum.event.dto; + +import java.time.LocalDateTime; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.PositiveOrZero; +import jakarta.validation.constraints.Size; + +public record NewEventDto( + @NotBlank @Size(min = 20, max = 2000) String annotation, + @NotNull Long category, + @NotBlank @Size(min = 20, max = 7000) String description, + @NotNull LocalDateTime eventDate, + @NotNull LocationDto location, + Boolean paid, + @PositiveOrZero Integer participantLimit, + Boolean requestModeration, + @NotBlank @Size(min = 3, max = 120) String title) { + public NewEventDto { + if (paid == null) paid = false; + if (requestModeration == null) requestModeration = true; + if (participantLimit == null) participantLimit = 0; + } +} diff --git a/main-service/src/main/java/ru/practicum/event/dto/UpdateEventAdminRequest.java b/main-service/src/main/java/ru/practicum/event/dto/UpdateEventAdminRequest.java new file mode 100644 index 0000000..23b0f72 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/dto/UpdateEventAdminRequest.java @@ -0,0 +1,61 @@ +package ru.practicum.event.dto; + +import java.time.LocalDateTime; + +import jakarta.validation.constraints.PositiveOrZero; +import jakarta.validation.constraints.Size; + +import ru.practicum.event.model.StateAction; + +public record UpdateEventAdminRequest( + @Size(min = 20, max = 2000) String annotation, + Long category, + @Size(min = 20, max = 7000) String description, + LocalDateTime eventDate, + LocationDto location, + Boolean paid, + @PositiveOrZero Integer participantLimit, + Boolean requestModeration, + StateAction stateAction, + @Size(min = 3, max = 120) String title) { + + public boolean hasAnnotation() { + return annotation != null && !annotation.isBlank(); + } + + public boolean hasCategory() { + return category != null; + } + + public boolean hasDescription() { + return description != null && !description.isBlank(); + } + + public boolean hasEventDate() { + return eventDate != null; + } + + public boolean hasLocation() { + return location != null; + } + + public boolean hasPaid() { + return paid != null; + } + + public boolean hasParticipantLimit() { + return participantLimit != null; + } + + public boolean hasRequestModeration() { + return requestModeration != null; + } + + public boolean hasStateAction() { + return stateAction != null; + } + + public boolean hasTitle() { + return title != null && !title.isBlank(); + } +} diff --git a/main-service/src/main/java/ru/practicum/event/dto/UpdateEventUserRequest.java b/main-service/src/main/java/ru/practicum/event/dto/UpdateEventUserRequest.java new file mode 100644 index 0000000..1c95bbd --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/dto/UpdateEventUserRequest.java @@ -0,0 +1,61 @@ +package ru.practicum.event.dto; + +import java.time.LocalDateTime; + +import jakarta.validation.constraints.PositiveOrZero; +import jakarta.validation.constraints.Size; + +import ru.practicum.event.model.StateAction; + +public record UpdateEventUserRequest( + @Size(min = 20, max = 2000) String annotation, + Long category, + @Size(min = 20, max = 7000) String description, + LocalDateTime eventDate, + LocationDto location, + Boolean paid, + @PositiveOrZero Integer participantLimit, + Boolean requestModeration, + StateAction stateAction, + @Size(min = 3, max = 120) String title) { + + public boolean hasAnnotation() { + return annotation != null && !annotation.isBlank(); + } + + public boolean hasCategory() { + return category != null; + } + + public boolean hasDescription() { + return description != null && !description.isBlank(); + } + + public boolean hasEventDate() { + return eventDate != null; + } + + public boolean hasLocation() { + return location != null; + } + + public boolean hasPaid() { + return paid != null; + } + + public boolean hasParticipantLimit() { + return participantLimit != null; + } + + public boolean hasRequestModeration() { + return requestModeration != null; + } + + public boolean hasStateAction() { + return stateAction != null; + } + + public boolean hasTitle() { + return title != null && !title.isBlank(); + } +} diff --git a/main-service/src/main/java/ru/practicum/event/model/Event.java b/main-service/src/main/java/ru/practicum/event/model/Event.java new file mode 100644 index 0000000..4b5eb7d --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/model/Event.java @@ -0,0 +1,66 @@ +package ru.practicum.event.model; + +import java.time.LocalDateTime; + +import jakarta.persistence.*; + +import ru.practicum.category.model.Category; +import ru.practicum.user.model.User; + +import lombok.experimental.FieldDefaults; +import lombok.*; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "event") +@FieldDefaults(level = AccessLevel.PRIVATE) +public class Event { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + @Column(nullable = false) + String annotation; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "category_id", nullable = false) + Category category; + + @Column(nullable = false) + LocalDateTime createdOn; + + @Column(nullable = false) + String description; + + @Column(nullable = false) + LocalDateTime eventDate; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "initiator_id", nullable = false) + User initiator; + + @Embedded Location location; + + @Column(nullable = false) + Boolean paid; + + @Column(nullable = false) + Integer participantLimit; + + @Column LocalDateTime publishedOn; + + @Column(nullable = false) + Boolean requestModeration; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + EventState state; + + @Column(nullable = false) + String title; +} diff --git a/main-service/src/main/java/ru/practicum/event/model/EventState.java b/main-service/src/main/java/ru/practicum/event/model/EventState.java new file mode 100644 index 0000000..e7dc6de --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/model/EventState.java @@ -0,0 +1,7 @@ +package ru.practicum.event.model; + +public enum EventState { + PENDING, + PUBLISHED, + CANCELLED +} diff --git a/main-service/src/main/java/ru/practicum/event/model/Location.java b/main-service/src/main/java/ru/practicum/event/model/Location.java new file mode 100644 index 0000000..9a53749 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/model/Location.java @@ -0,0 +1,24 @@ +package ru.practicum.event.model; + +import java.math.BigDecimal; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; + +import lombok.experimental.FieldDefaults; +import lombok.*; + +@Getter +@Setter +@Builder +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +public class Location { + @Column(name = "location_lat", nullable = false, precision = 9, scale = 6) + BigDecimal lat; + + @Column(name = "location_lon", nullable = false, precision = 9, scale = 6) + BigDecimal lon; +} diff --git a/main-service/src/main/java/ru/practicum/event/model/StateAction.java b/main-service/src/main/java/ru/practicum/event/model/StateAction.java new file mode 100644 index 0000000..9019e86 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/model/StateAction.java @@ -0,0 +1,6 @@ +package ru.practicum.event.model; + +public enum StateAction { + SEND_TO_REVIEW, + CANCEL_REVIEW +} diff --git a/main-service/src/main/java/ru/practicum/request/dto/EventRequestStatusUpdateRequest.java b/main-service/src/main/java/ru/practicum/request/dto/EventRequestStatusUpdateRequest.java new file mode 100644 index 0000000..b9986cd --- /dev/null +++ b/main-service/src/main/java/ru/practicum/request/dto/EventRequestStatusUpdateRequest.java @@ -0,0 +1,11 @@ +package ru.practicum.request.dto; + +import java.util.List; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +import ru.practicum.request.model.EventRequestStatus; + +public record EventRequestStatusUpdateRequest( + @NotEmpty List requestIds, @NotNull EventRequestStatus status) {} diff --git a/main-service/src/main/java/ru/practicum/request/dto/EventRequestStatusUpdateResult.java b/main-service/src/main/java/ru/practicum/request/dto/EventRequestStatusUpdateResult.java new file mode 100644 index 0000000..bae32df --- /dev/null +++ b/main-service/src/main/java/ru/practicum/request/dto/EventRequestStatusUpdateResult.java @@ -0,0 +1,7 @@ +package ru.practicum.request.dto; + +import java.util.List; + +public record EventRequestStatusUpdateResult( + List confirmedRequests, + List rejectedRequests) {} diff --git a/main-service/src/main/java/ru/practicum/request/dto/ParticipationRequestDto.java b/main-service/src/main/java/ru/practicum/request/dto/ParticipationRequestDto.java new file mode 100644 index 0000000..b93cff3 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/request/dto/ParticipationRequestDto.java @@ -0,0 +1,6 @@ +package ru.practicum.request.dto; + +import java.time.LocalDateTime; + +public record ParticipationRequestDto( + LocalDateTime created, Long event, Long id, Long requester, String status) {} 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 new file mode 100644 index 0000000..e595013 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/request/model/EventRequestStatus.java @@ -0,0 +1,7 @@ +package ru.practicum.request.model; + +public enum EventRequestStatus { + PENDING, + CONFIRMED, + REJECTED +} 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 new file mode 100644 index 0000000..54aab5d --- /dev/null +++ b/main-service/src/main/java/ru/practicum/request/model/ParticipationRequest.java @@ -0,0 +1,41 @@ +package ru.practicum.request.model; + +import java.time.LocalDateTime; + +import jakarta.persistence.*; + +import ru.practicum.event.model.Event; +import ru.practicum.user.model.User; + +import lombok.experimental.FieldDefaults; +import lombok.*; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "participation_request") +@FieldDefaults(level = AccessLevel.PRIVATE) +public class ParticipationRequest { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "event_id", nullable = false) + Event event; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "requester_id", nullable = false) + User requester; + + @Column(nullable = false) + LocalDateTime created; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + EventRequestStatus status; +} diff --git a/main-service/src/main/java/ru/practicum/user/dto/NewUserRequest.java b/main-service/src/main/java/ru/practicum/user/dto/NewUserRequest.java new file mode 100644 index 0000000..d52d433 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/user/dto/NewUserRequest.java @@ -0,0 +1,9 @@ +package ru.practicum.user.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record NewUserRequest( + @Size(min = 6, max = 254) @Email @NotBlank String email, + @Size(min = 2, max = 250) @NotBlank String name) {} diff --git a/main-service/src/main/java/ru/practicum/user/dto/UserDto.java b/main-service/src/main/java/ru/practicum/user/dto/UserDto.java new file mode 100644 index 0000000..15c6cb7 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/user/dto/UserDto.java @@ -0,0 +1,3 @@ +package ru.practicum.user.dto; + +public record UserDto(String email, Long id, String name) {} diff --git a/main-service/src/main/java/ru/practicum/user/dto/UserShortDto.java b/main-service/src/main/java/ru/practicum/user/dto/UserShortDto.java new file mode 100644 index 0000000..6eb0c9e --- /dev/null +++ b/main-service/src/main/java/ru/practicum/user/dto/UserShortDto.java @@ -0,0 +1,3 @@ +package ru.practicum.user.dto; + +public record UserShortDto(Long id, String name) {} diff --git a/main-service/src/main/java/ru/practicum/user/model/User.java b/main-service/src/main/java/ru/practicum/user/model/User.java new file mode 100644 index 0000000..87a328d --- /dev/null +++ b/main-service/src/main/java/ru/practicum/user/model/User.java @@ -0,0 +1,27 @@ +package ru.practicum.user.model; + +import jakarta.persistence.*; + +import lombok.experimental.FieldDefaults; +import lombok.*; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "users") +@FieldDefaults(level = AccessLevel.PRIVATE) +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + @Column(nullable = false) + String name; + + @Column(nullable = false) + String email; +} diff --git a/main-service/src/main/resources/application.yaml b/main-service/src/main/resources/application.yaml index f44e14a..9e5b089 100644 --- a/main-service/src/main/resources/application.yaml +++ b/main-service/src/main/resources/application.yaml @@ -7,3 +7,9 @@ spring: url: ${SPRING_DATASOURCE_URL} username: ${SPRING_DATASOURCE_USERNAME} password: ${SPRING_DATASOURCE_PASSWORD} + sql: + init: + mode: always + jpa: + hibernate: + ddl-auto: validate diff --git a/main-service/src/main/resources/schema.sql b/main-service/src/main/resources/schema.sql new file mode 100644 index 0000000..17e4a2e --- /dev/null +++ b/main-service/src/main/resources/schema.sql @@ -0,0 +1,64 @@ +CREATE TABLE category +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(50) NOT NULL UNIQUE +); + +CREATE TABLE users +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(250) NOT NULL, + email VARCHAR(254) NOT NULL UNIQUE +); + +CREATE TABLE event +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + title VARCHAR(120) NOT NULL, + annotation VARCHAR(2000) NOT NULL, + description VARCHAR(7000) NOT NULL, + event_date TIMESTAMP NOT NULL, + created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + published_on TIMESTAMP, + paid BOOLEAN DEFAULT FALSE, + participant_limit INT DEFAULT 0, + request_moderation BOOLEAN DEFAULT TRUE, + state VARCHAR(20) DEFAULT 'PENDING' CHECK (state IN ('PENDING', 'PUBLISHED', 'CANCELED')), + + location_lat NUMERIC(9, 6) NOT NULL, + location_lon NUMERIC(9, 6) NOT NULL, + + category_id BIGINT NOT NULL, + initiator_id BIGINT NOT NULL, + + CONSTRAINT fk_event_category FOREIGN KEY (category_id) REFERENCES category (id), + CONSTRAINT fk_event_user FOREIGN KEY (initiator_id) REFERENCES users (id) +); + +CREATE TABLE compilation +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + title VARCHAR(50) NOT NULL, + pinned BOOLEAN DEFAULT FALSE +); + +CREATE TABLE compilation_events +( + compilation_id BIGINT NOT NULL, + event_id BIGINT NOT NULL, + PRIMARY KEY (compilation_id, event_id), + CONSTRAINT fk_compilation FOREIGN KEY (compilation_id) REFERENCES compilation (id) ON DELETE CASCADE, + CONSTRAINT fk_event FOREIGN KEY (event_id) REFERENCES event (id) ON DELETE CASCADE +); + +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, + 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/stat/stat-server/src/main/resources/schema.sql b/stat/stat-server/src/main/resources/schema.sql index c54a611..59c5a23 100644 --- a/stat/stat-server/src/main/resources/schema.sql +++ b/stat/stat-server/src/main/resources/schema.sql @@ -1,7 +1,8 @@ -CREATE TABLE IF NOT EXISTS stat ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - app VARCHAR(255) NOT NULL, - uri VARCHAR(512) NOT NULL, - ip VARCHAR(45) NOT NULL, - created TIMESTAMP NOT NULL +CREATE TABLE IF NOT EXISTS stat +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + app VARCHAR(255) NOT NULL, + uri VARCHAR(512) NOT NULL, + ip VARCHAR(45) NOT NULL, + created TIMESTAMP NOT NULL ); From 5fc9c89c72702bd2053c90c5f9b64bf4a9c81a26 Mon Sep 17 00:00:00 2001 From: Kreidl Date: Fri, 26 Dec 2025 20:39:54 +0300 Subject: [PATCH 05/10] Main svc categories (#7) * Edit application-dev.yaml * Add NotFoundException * Add CategoryMapper * Add CategoryRepository * Add UpdateCategoryDto * Add CategoryService(interface, implementation) * Add CategoryControllers(admin, public) * Add handleDataIntegrityViolationException * Add postman tests for categories * Changes for codestyle * Add json tests * Changes based on comments * Changes based on comments * Changes based on comments * Changes based on comments * Changes based on comments --- .../controller/CategoryAdminController.java | 46 + .../controller/CategoryPublicController.java | 33 + .../category/dto/UpdateCategoryDto.java | 6 + .../category/mapper/CategoryMapper.java | 18 + .../repository/CategoryRepository.java | 7 + .../category/service/CategoryService.java | 19 + .../category/service/CategoryServiceImpl.java | 70 + .../exception/NotFoundException.java | 13 + .../handler/GlobalExceptionHandler.java | 26 + .../src/main/resources/application-dev.yaml | 4 +- postman/ewm-main-service.json | 1908 +++++++++++++++++ postman/ewm-stat-service.json | 515 +++++ 12 files changed, 2663 insertions(+), 2 deletions(-) create mode 100644 main-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java create mode 100644 main-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java create mode 100644 main-service/src/main/java/ru/practicum/category/dto/UpdateCategoryDto.java create mode 100644 main-service/src/main/java/ru/practicum/category/mapper/CategoryMapper.java create mode 100644 main-service/src/main/java/ru/practicum/category/repository/CategoryRepository.java create mode 100644 main-service/src/main/java/ru/practicum/category/service/CategoryService.java create mode 100644 main-service/src/main/java/ru/practicum/category/service/CategoryServiceImpl.java create mode 100644 main-service/src/main/java/ru/practicum/exception/NotFoundException.java create mode 100644 postman/ewm-main-service.json create mode 100644 postman/ewm-stat-service.json diff --git a/main-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java b/main-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java new file mode 100644 index 0000000..4dda309 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java @@ -0,0 +1,46 @@ +package ru.practicum.category.controller; + +import jakarta.validation.Valid; + +import ru.practicum.category.dto.CategoryDto; +import ru.practicum.category.dto.NewCategoryDto; +import ru.practicum.category.dto.UpdateCategoryDto; +import ru.practicum.category.service.CategoryService; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin/categories") +public class CategoryAdminController { + private final CategoryService categoryService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public CategoryDto createCategory(@Valid @RequestBody NewCategoryDto newCategoryDto) { + log.info("Create category requested {}", newCategoryDto); + return categoryService.createCategory(newCategoryDto); + } + + @PatchMapping("/{catId}") + public CategoryDto updateCategory( + @PathVariable Long catId, @Valid @RequestBody UpdateCategoryDto updateCategoryDto) { + log.info( + "Update category with id={} requested, updated category {}", + catId, + updateCategoryDto); + return categoryService.updateCategory(catId, updateCategoryDto); + } + + @DeleteMapping("/{catId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteCategory(@PathVariable Long catId) { + log.info("Delete category with id={} requested", catId); + categoryService.deleteCategoryById(catId); + } +} diff --git a/main-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java b/main-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java new file mode 100644 index 0000000..0831614 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java @@ -0,0 +1,33 @@ +package ru.practicum.category.controller; + +import java.util.Collection; + +import ru.practicum.category.dto.CategoryDto; +import ru.practicum.category.service.CategoryService; + +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/categories") +public class CategoryPublicController { + private final CategoryService categoryService; + + @GetMapping + public Collection getAllCategoriesPaged( + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size) { + log.info("View categories page by page requested from={}, size={}", from, size); + return categoryService.getAllCategoriesPaged(from, size); + } + + @GetMapping("/{id}") + public CategoryDto getCategoryById(@PathVariable Long id) { + log.info("View category with id={} requested", id); + return categoryService.getCategoryById(id); + } +} diff --git a/main-service/src/main/java/ru/practicum/category/dto/UpdateCategoryDto.java b/main-service/src/main/java/ru/practicum/category/dto/UpdateCategoryDto.java new file mode 100644 index 0000000..58611f4 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/category/dto/UpdateCategoryDto.java @@ -0,0 +1,6 @@ +package ru.practicum.category.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record UpdateCategoryDto(@Size(min = 1, max = 50) @NotBlank String name) {} diff --git a/main-service/src/main/java/ru/practicum/category/mapper/CategoryMapper.java b/main-service/src/main/java/ru/practicum/category/mapper/CategoryMapper.java new file mode 100644 index 0000000..68035b6 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/category/mapper/CategoryMapper.java @@ -0,0 +1,18 @@ +package ru.practicum.category.mapper; + +import ru.practicum.category.dto.CategoryDto; +import ru.practicum.category.dto.NewCategoryDto; +import ru.practicum.category.model.Category; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class CategoryMapper { + public Category mapToEntity(NewCategoryDto newCategoryDto) { + return new Category(null, newCategoryDto.name()); + } + + public CategoryDto mapToDto(Category category) { + return new CategoryDto(category.getId(), category.getName()); + } +} diff --git a/main-service/src/main/java/ru/practicum/category/repository/CategoryRepository.java b/main-service/src/main/java/ru/practicum/category/repository/CategoryRepository.java new file mode 100644 index 0000000..27a0d50 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/category/repository/CategoryRepository.java @@ -0,0 +1,7 @@ +package ru.practicum.category.repository; + +import ru.practicum.category.model.Category; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CategoryRepository extends JpaRepository {} diff --git a/main-service/src/main/java/ru/practicum/category/service/CategoryService.java b/main-service/src/main/java/ru/practicum/category/service/CategoryService.java new file mode 100644 index 0000000..9058f1e --- /dev/null +++ b/main-service/src/main/java/ru/practicum/category/service/CategoryService.java @@ -0,0 +1,19 @@ +package ru.practicum.category.service; + +import java.util.List; + +import ru.practicum.category.dto.CategoryDto; +import ru.practicum.category.dto.NewCategoryDto; +import ru.practicum.category.dto.UpdateCategoryDto; + +public interface CategoryService { + CategoryDto createCategory(NewCategoryDto newCategoryDto); + + List getAllCategoriesPaged(int from, int size); + + CategoryDto getCategoryById(Long id); + + void deleteCategoryById(Long id); + + CategoryDto updateCategory(Long catId, UpdateCategoryDto updateCategoryDto); +} diff --git a/main-service/src/main/java/ru/practicum/category/service/CategoryServiceImpl.java b/main-service/src/main/java/ru/practicum/category/service/CategoryServiceImpl.java new file mode 100644 index 0000000..e91d26b --- /dev/null +++ b/main-service/src/main/java/ru/practicum/category/service/CategoryServiceImpl.java @@ -0,0 +1,70 @@ +package ru.practicum.category.service; + +import java.util.List; + +import ru.practicum.category.dto.CategoryDto; +import ru.practicum.category.dto.NewCategoryDto; +import ru.practicum.category.dto.UpdateCategoryDto; +import ru.practicum.category.mapper.CategoryMapper; +import ru.practicum.category.model.Category; +import ru.practicum.category.repository.CategoryRepository; +import ru.practicum.exception.NotFoundException; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; + +@Service +@Transactional +@RequiredArgsConstructor +public class CategoryServiceImpl implements CategoryService { + private final CategoryRepository categoryRepository; + + @Override + public CategoryDto createCategory(NewCategoryDto newCategoryDto) { + Category category = CategoryMapper.mapToEntity(newCategoryDto); + return CategoryMapper.mapToDto(categoryRepository.save(category)); + } + + @Override + public CategoryDto updateCategory(Long catId, UpdateCategoryDto updateCategoryDto) { + Category category = + categoryRepository + .findById(catId) + .orElseThrow( + NotFoundException.supplier( + "Category with id=%d was not found", catId)); + category.setName(updateCategoryDto.name()); + return CategoryMapper.mapToDto(categoryRepository.save(category)); + } + + @Override + @Transactional(readOnly = true) + public List getAllCategoriesPaged(int from, int size) { + Pageable pageable = PageRequest.of(from, size); + return categoryRepository.findAll(pageable).stream().map(CategoryMapper::mapToDto).toList(); + } + + @Override + @Transactional(readOnly = true) + public CategoryDto getCategoryById(Long id) { + Category category = + categoryRepository + .findById(id) + .orElseThrow( + NotFoundException.supplier( + "Category with id=%d was not found", id)); + return CategoryMapper.mapToDto(category); + } + + @Override + public void deleteCategoryById(Long id) { + categoryRepository + .findById(id) + .orElseThrow(NotFoundException.supplier("Category with id=%d was not found", id)); + categoryRepository.deleteById(id); + } +} diff --git a/main-service/src/main/java/ru/practicum/exception/NotFoundException.java b/main-service/src/main/java/ru/practicum/exception/NotFoundException.java new file mode 100644 index 0000000..5221e05 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/exception/NotFoundException.java @@ -0,0 +1,13 @@ +package ru.practicum.exception; + +import java.util.function.Supplier; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } + + public static Supplier supplier(String message, Object... args) { + return () -> new NotFoundException(message.formatted(args)); + } +} 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 00d1ea5..78bf9c7 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,9 +8,11 @@ import jakarta.validation.ConstraintViolationException; +import ru.practicum.exception.NotFoundException; import ru.practicum.exception.dto.ApiError; import ru.practicum.exception.dto.Violation; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -77,4 +79,28 @@ public ApiError handleMethodArgumentNotValidException(MethodArgumentNotValidExce HttpStatus.BAD_REQUEST.toString(), LocalDateTime.now()); } + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(NotFoundException.class) + public ApiError handleNotFoundException(NotFoundException e) { + log.warn("Not found: {}", e.getMessage()); + return new ApiError( + List.of(e.getMessage()), + e.getMessage(), + "The required object was not found", + HttpStatus.NOT_FOUND.toString(), + LocalDateTime.now()); + } + + @ResponseStatus(HttpStatus.CONFLICT) + @ExceptionHandler(DataIntegrityViolationException.class) + public ApiError handleDataIntegrityViolationException(DataIntegrityViolationException e) { + log.warn(e.getMessage(), e); + return new ApiError( + List.of(e.getMessage()), + e.getMessage(), + "Some fields of RequestBody for request are invalid", + HttpStatus.NOT_FOUND.toString(), + LocalDateTime.now()); + } } diff --git a/main-service/src/main/resources/application-dev.yaml b/main-service/src/main/resources/application-dev.yaml index 2745e97..06e3952 100644 --- a/main-service/src/main/resources/application-dev.yaml +++ b/main-service/src/main/resources/application-dev.yaml @@ -6,10 +6,10 @@ stats-server: app: ${STATS_SERVER_APP:ewm-main-service} spring: application: - name: stat-server + name: ewm-service datasource: - url: jdbc:h2:mem:stat-db + url: jdbc:h2:mem:ewm-db driver-class-name: org.h2.Driver username: sa password: diff --git a/postman/ewm-main-service.json b/postman/ewm-main-service.json new file mode 100644 index 0000000..e42bf58 --- /dev/null +++ b/postman/ewm-main-service.json @@ -0,0 +1,1908 @@ +{ + "info": { + "_postman_id": "26aaf842-99f3-4571-832c-13549d81b8c6", + "name": "Test Explore With Me - Main service", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", + "_exporter_id": "37174685" + }, + "item": [ + { + "name": "Validation", + "item": [ + { + "name": "Category", + "item": [ + { + "name": "Required query params", + "item": [] + }, + { + "name": "Unrequired params in body", + "item": [ + { + "name": "Получение категорий без нескольких Query params", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "categories" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество категорий, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "10000", + "description": "количество категорий в наборе", + "disabled": true + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Required params in body", + "item": [ + { + "name": "Добавление категории с именем, состоящим из пробелов", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {name: ' '};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": "{{baseUrl}}/admin/categories" + }, + "response": [] + }, + { + "name": "Добавление категории с пустым полем name", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {name: ''};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": "{{baseUrl}}/admin/categories" + }, + "response": [] + }, + { + "name": "Добавление категории без поля name", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": "{{baseUrl}}/admin/categories" + }, + "response": [] + } + ] + }, + { + "name": "Misc tests", + "item": [ + { + "name": "Изменение категории с неизменными данными", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category, categoryObj\r", + " try {\r", + " category = rnd.getCategory();\r", + " categoryObj = await api.addCategory(category);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(categoryObj.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "String length restrictions", + "item": [ + { + "name": "Добавление новой категории с name.length > 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {'name': rnd.getWord(51)};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{baseUrl}}/admin/categories", + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Добавление новой категории с name.length == 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {'name': rnd.getWord(50)};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{baseUrl}}/admin/categories", + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Изменение имени категории с name.length > 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category\r", + " try {\r", + " category = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(category.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : rnd.getWord(51)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Изменение имени категории с name.length == 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category\r", + " try {\r", + " category = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(category.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : rnd.getWord(50)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.have.status(200);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Default values check", + "item": [ + { + "name": "Проверка на значения по-умолчанию from и size(category)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " for (let i = 0; i < 11; i++){\r", + " await api.addCategory(rnd.getCategory());\r", + " }\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/categories?from=0\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {pm.collectionVariables.set('source', response.json())});\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('source');\r", + "\r", + "pm.test(\"Значение from по-умолчанию должно быть равным 0\", function () {\r", + " pm.expect(target[0].id).to.be.equal(source[0].id, 'Запросы с from=0 и без него должны начинаться с одного и того же события');\r", + "});\r", + "\r", + "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", + " pm.expect(target.length).to.be.equal(10);\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "categories" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество категорий, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество категорий в наборе", + "disabled": true + } + ] + } + }, + "response": [] + } + ] + } + ] + } + ] + }, + { + "name": "409 Conflict", + "item": [ + { + "name": "Попытка изменения имени категории на уже существующее", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category1, category2\r", + " try {\r", + " category1 = await api.addCategory(rnd.getCategory());\r", + " category2 = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", category2.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : category1.name\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Добавление новой категории с занятым именем", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = rnd.getCategory();\r", + " await api.addCategory(category);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{baseUrl}}/admin/categories", + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Изменение имени категории на уже занятое", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category1, category2\r", + " try {\r", + " category1 = await api.addCategory(rnd.getCategory());\r", + " category2 = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(category1.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : category2.name\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Category", + "item": [ + { + "name": "Добавление новой категории", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = rnd.getCategory();\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{baseUrl}}/admin/categories", + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Получение категорий", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " pm.collectionVariables.set(\"response\", category)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json();\r", + "let founded;\r", + "target.forEach(function(element){if (element.id == source.id) founded = element});\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target[0]).to.have.property('id');\r", + "pm.expect(target[0]).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.id).equal(founded.id, 'Идентификатор категории должен соответствовать идентификатору категории добавленной ранее');\r", + " pm.expect(source.name).equal(founded.name, 'Название категории должно соответствовать названию категории добавленной ранее');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/categories?from=0&size=1000", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "categories" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество категорий, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество категорий в наборе" + } + ] + } + }, + "response": [] + }, + { + "name": "Получение информации о категории по её идентификатору", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " pm.collectionVariables.set(\"response\", category)\r", + " pm.collectionVariables.set(\"catid\", category.id)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор категории должен соответствовать идентификатору в запросе');\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно соответствовать названию категории с указанным идентификатором');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}", + "description": "(Required) id категории" + } + ] + } + }, + "response": [] + }, + { + "name": "Удаление категории", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const findedCategory = await api.findCategory(category.id);\r", + " pm.collectionVariables.set(\"catid\", category.id)\r", + " pm.collectionVariables.set(\"response\", findedCategory)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 204\", function () {\r", + " pm.response.to.have.status(204);\r", + "});\r", + "\r", + "source = pm.collectionVariables.get('response');\r", + "catId = pm.collectionVariables.get('catid');\r", + "\r", + "pm.test(\"Категория должна быть найдена до удаления\", function () {\r", + " pm.expect(source.id).equal(catId, 'Идентификтор категории должен совпадать с удаляемым');\r", + "});\r", + "\r", + "pm.sendRequest({\r", + " url: pm.collectionVariables.get(\"baseUrl\") + \"/categories/\" + catId,\r", + " method: 'GET',\r", + " }, (error, response) => {\r", + " pm.test(\"Категория не должна быть найдена после удаления\", function () {\r", + " pm.expect(response.code).to.eql(404);\r", + " });\r", + " });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + }, + "description": "Обратите внимание: с категорий не должно быть связано ни одного события." + }, + "response": [] + }, + { + "name": "Изменение категории", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category\r", + " try {\r", + " category = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(category.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : rnd.getCategory().name\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "API = class {", + " constructor(postman, verbose = false, baseUrl = \"http://localhost:8080\") {", + " this.baseUrl = baseUrl;", + " this.pm = postman;", + " this._verbose = verbose;", + " }", + "", + " async addUser(user, verbose=null) {", + " return this.post(\"/admin/users\", user, \"Ошибка при добавлении нового пользователя: \", verbose);", + " }", + "", + " async addCategory(category, verbose=null) {", + " return this.post(\"/admin/categories\", category, \"Ошибка при добавлении новой категории: \", verbose);", + " }", + "", + " async addEvent(userId, event, verbose=null) {", + " return this.post(\"/users/\" + userId + \"/events\", event, \"Ошибка при добавлении нового события: \", verbose);", + " }", + "", + " async addCompilation(compilation, verbose=null) {", + " return this.post(\"/admin/compilations\", compilation, \"Ошибка при добавлении новой подборки: \", verbose);", + " }", + "", + " async publishParticipationRequest(eventId, userId, verbose=null) {", + " return this.post('/users/' + userId + '/requests?eventId=' + eventId, null, \"Ошибка при добавлении нового запроса на участие в событии\", verbose);", + " }", + "", + " async publishEvent(eventId, verbose=null) {", + " return this.patch('/admin/events/' + eventId, {stateAction: \"PUBLISH_EVENT\"}, \"Ошибка при публикации события\", verbose);", + " }", + " ", + " async rejectEvent(eventId, verbose=null) {", + " return this.patch('/admin/events/' + eventId, {stateAction: \"REJECT_EVENT\"}, \"Ошибка при отмене события\", verbose);", + " }", + "", + " async acceptParticipationRequest(eventId, userId, reqId, verbose=null) {", + " return this.patch('/users/' + userId + '/events/' + eventId + '/requests', {requestIds:[reqId], status: \"CONFIRMED\"}, \"Ошибка при принятии заявки на участие в событии\", verbose);", + " }", + "", + " async findCategory(catId, verbose=null) {", + " return this.get('/categories/' + catId, null, \"Ошибка при поиске категории по id\", verbose);", + " }", + "", + " async findCompilation(compId, verbose=null) {", + " return this.get('/compilations/' + compId, null, \"Ошибка при поиске подборки по id\", verbose);", + " }", + "", + " async findEvent(eventId, verbose=null) {", + " return this.get('/events/' + eventId, null, \"Ошибка при поиске события по id\", verbose);", + " }", + "", + " async findUser(userId, verbose=null) {", + " return this.get('/admin/users?ids=' + userId, null, \"Ошибка при поиске пользователя по id\", verbose);", + " }", + "", + " async post(path, body, errorText = \"Ошибка при выполнении post-запроса: \", verbose=null) {", + " return this.sendRequest(\"POST\", path, body, errorText, verbose);", + " }", + "", + " async patch(path, body = null, errorText = \"Ошибка при выполнении patch-запроса: \", verbose=null) {", + " return this.sendRequest(\"PATCH\", path, body, errorText, verbose);", + " }", + "", + " async get(path, body = null, errorText = \"Ошибка при выполнении get-запроса: \", verbose=null) {", + " return this.sendRequest(\"GET\", path, body, errorText, verbose);", + " }", + " async sendRequest(method, path, body=null, errorText = \"Ошибка при выполнении запроса: \", verbose=null) {", + " return new Promise((resolve, reject) => {", + " verbose = verbose == null ? this._verbose : verbose;", + " const request = {", + " url: this.baseUrl + path,", + " method: method,", + " body: body == null ? \"\" : JSON.stringify(body),", + " header: { \"Content-Type\": \"application/json\" },", + " };", + " if(verbose) {", + " console.log(\"Отправляю запрос: \", request);", + " }", + "", + " try {", + " this.pm.sendRequest(request, (error, response) => {", + " if(error || (response.code >= 400 && response.code <= 599)) {", + " let err = error ? error : JSON.stringify(response.json());", + " console.error(\"При выполнении запроса к серверу возникла ошика.\\n\", err,", + " \"\\nДля отладки проблемы повторите такой же запрос к вашей программе \" + ", + " \"на локальном компьютере. Данные запроса:\\n\", JSON.stringify(request));", + "", + " reject(new Error(errorText + err));", + " }", + " if(verbose) {", + " console.log(\"Результат обработки запроса: код состояния - \", response.code, \", тело: \", response.json());", + " }", + " if (response.stream.length === 0){", + " reject(new Error('Отправлено пустое тело ответа'))", + " }else{", + " resolve(response.json());", + " }", + " });", + " ", + " } catch(err) {", + " if(verbose) {", + " console.error(errorText, err);", + " }", + " return Promise.reject(err);", + " }", + " });", + " }", + "};", + "", + "RandomUtils = class {", + " constructor() {}", + "", + " getUser() {", + " return {", + " name: pm.variables.replaceIn('{{$randomFullName}}'),", + " email: pm.variables.replaceIn('{{$randomEmail}}')", + " };", + " }", + "", + " getCategory() {", + " return {", + " name: pm.variables.replaceIn('{{$randomWord}}') + Math.floor(Math.random() * 10000 * Math.random()).toString()", + " };", + " }", + "", + " getEvent(categoryId) {", + " return {", + " annotation: pm.variables.replaceIn('{{$randomLoremParagraph}}'),", + " category: categoryId,", + " description: pm.variables.replaceIn('{{$randomLoremParagraphs}}'),", + " eventDate: this.getFutureDateTime(),", + " location: {", + " lat: parseFloat(pm.variables.replaceIn('{{$randomLatitude}}')),", + " lon: parseFloat(pm.variables.replaceIn('{{$randomLongitude}}')),", + " },", + " paid: pm.variables.replaceIn('{{$randomBoolean}}'),", + " participantLimit: pm.variables.replaceIn('{{$randomInt}}'),", + " requestModeration: pm.variables.replaceIn('{{$randomBoolean}}'),", + " title: pm.variables.replaceIn('{{$randomLoremSentence}}'),", + " }", + " }", + "", + " getCompilation(...eventIds) {", + " return {", + " title: pm.variables.replaceIn('{{$randomLoremSentence}}').slice(0, 50),", + " pinned: pm.variables.replaceIn('{{$randomBoolean}}'),", + " events: eventIds", + " };", + " }", + "", + "", + " getFutureDateTime(hourShift = 5, minuteShift=0, yearShift=0) {", + " let moment = require('moment');", + "", + " let m = moment();", + " m.add(hourShift, 'hour');", + " m.add(minuteShift, 'minute');", + " m.add(yearShift, 'year');", + "", + " return m.format('YYYY-MM-DD HH:mm:ss');", + " }", + "", + " getWord(length = 1) {", + " let result = '';", + " const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';", + " const charactersLength = characters.length;", + " let counter = 0;", + " while (counter < length) {", + " result += characters.charAt(Math.floor(Math.random() * charactersLength));", + " counter += 1;", + " }", + " return result;", + " }", + "}" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "baseUrl", + "value": "http://localhost:8080", + "type": "string" + }, + { + "key": "name", + "value": "" + }, + { + "key": "usersCount", + "value": 1, + "type": "number" + }, + { + "key": "catid", + "value": 1, + "type": "number" + }, + { + "key": "request_body", + "value": "" + }, + { + "key": "mail", + "value": "" + }, + { + "key": "response", + "value": "" + }, + { + "key": "uid", + "value": 1, + "type": "number" + }, + { + "key": "catname", + "value": "" + }, + { + "key": "eid", + "value": 1, + "type": "number" + }, + { + "key": "compid", + "value": 1, + "type": "number" + }, + { + "key": "toCheck", + "value": "" + }, + { + "key": "newDataToSet", + "value": "" + }, + { + "key": "uid1", + "value": "" + }, + { + "key": "reqid", + "value": 1, + "type": "number" + }, + { + "key": "catId", + "value": "" + }, + { + "key": "confirmedRequests", + "value": "" + }, + { + "key": "responseArr", + "value": "" + }, + { + "key": "source1", + "value": "" + }, + { + "key": "source2", + "value": "" + }, + { + "key": "fromId", + "value": "0" + }, + { + "key": "source", + "value": "" + } + ] +} \ No newline at end of file diff --git a/postman/ewm-stat-service.json b/postman/ewm-stat-service.json new file mode 100644 index 0000000..6707a74 --- /dev/null +++ b/postman/ewm-stat-service.json @@ -0,0 +1,515 @@ +{ + "info": { + "_postman_id": "2727ee6b-c606-49ec-9d4b-549c21dbe7ae", + "name": "Tests for detatched stats service", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "23073145" + }, + "item": [ + { + "name": "Получение статистики по посещениям. (Тест на опциональность параметра uris)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try { \r", + " pm.collectionVariables.set(\"uri\", '/events'); \r", + " let post1 = rnd.getPost();\r", + " let post2 = rnd.getPost();\r", + " post1['uri'] = '/events';\r", + " post2['uri'] = '/events/5';\r", + " await api.addPost(post1);\r", + " await api.addPost(post2);\r", + "\r", + " let source = await api.getPosts(['/events']);\r", + " pm.collectionVariables.set('source', source);\r", + " \r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json при запросе без опционального параметра uris\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('source');\r", + "\r", + "pm.test(\"При запросе по конкретному uris должны получить 1 запись\", function () {\r", + " pm.expect(source.length).to.equal(1);\r", + "});\r", + "\r", + "pm.test(\"При запросе без uris должны получить больше 1 записи\", function () {\r", + " pm.expect(target.length).to.be.above(1);\r", + "});\r", + "\r", + "pm.test(\"Посты должны содержать поля: app, uri, hits\", function () {\r", + " pm.expect(target[0]).to.have.all.keys('app', 'uri', 'hits');\r", + " pm.expect(target[1]).to.have.all.keys('app', 'uri', 'hits');\r", + " pm.expect(source[0]).to.have.all.keys('app', 'uri', 'hits');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&unique=false", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "stats" + ], + "query": [ + { + "key": "start", + "value": "2020-05-05 00:00:00", + "description": "(Required) Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "end", + "value": "2035-05-05 00:00:00", + "description": "(Required) Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "uris", + "value": "{{uri}}", + "description": "Список uri для которых нужно выгрузить статистику", + "disabled": true + }, + { + "key": "uris", + "value": "aliqua o", + "description": "Список uri для которых нужно выгрузить статистику", + "disabled": true + }, + { + "key": "unique", + "value": "false", + "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)" + } + ] + } + }, + "response": [] + }, + { + "name": "Получение статистики по посещениям. (Тест на опциональность и работу параметра unique)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try { \r", + " pm.collectionVariables.set(\"uri\", '/events'); \r", + " let post = rnd.getPost();\r", + " post['uri'] = '/events';\r", + " await api.addPost(post);\r", + " await api.addPost(post);\r", + " await api.addPost(post);\r", + " \r", + " let source = await api.getPosts(['/events']);\r", + " pm.collectionVariables.set('source', source);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json при запросе без опционального параметра unique\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json()[0];\r", + "const source = pm.collectionVariables.get('source')[0];\r", + "\r", + "pm.test(\"При запросе с unique==true должен быть всего 1 уникальный запрос\", function () {\r", + " pm.expect(target.hits).to.equal(1);\r", + "});\r", + "\r", + "pm.test(\"При запросе без uniqre должны получить минимум 3 запроса(поскольку делали 3)\", function () {\r", + " pm.expect(source.hits).to.be.above(2);\r", + "});\r", + "\r", + "pm.test(\"Посты должны содержать поля: app, uri, hits\", function () {\r", + " pm.expect(target).to.have.all.keys('app', 'uri', 'hits');\r", + " pm.expect(source).to.have.all.keys('app', 'uri', 'hits');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris={{uri}}&unique=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "stats" + ], + "query": [ + { + "key": "start", + "value": "2020-05-05 00:00:00", + "description": "(Required) Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "end", + "value": "2035-05-05 00:00:00", + "description": "(Required) Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "uris", + "value": "{{uri}}", + "description": "Список uri для которых нужно выгрузить статистику" + }, + { + "key": "uris", + "value": "aliqua o", + "description": "Список uri для которых нужно выгрузить статистику", + "disabled": true + }, + { + "key": "unique", + "value": "true", + "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)" + } + ] + } + }, + "response": [] + }, + { + "name": "Тест корреткной работы сохранения и просмотра количества просмотров", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " pm.collectionVariables.set(\"uri\", '/events/1&uris=/events/2');\r", + " let post1 = rnd.getPost();\r", + " let post2 = rnd.getPost();\r", + " post1['uri'] = '/events/1';\r", + " post2['uri'] = '/events/2';\r", + " await api.addPost(post1);\r", + " await api.addPost(post2);\r", + " await api.addPost(post2);\r", + " let source = await api.getPosts(['/events/1', '/events/2']);\r", + " await api.addPost(post1);\r", + " await api.addPost(post2);\r", + " pm.collectionVariables.set('source', source);\r", + " \r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('source');\r", + "\r", + "\r", + "pm.test(\"Посты должны содержать поля: app, uri, hits\", function () {\r", + " pm.expect(target[0]).to.have.all.keys('app', 'uri', 'hits');\r", + " pm.expect(target[1]).to.have.all.keys('app', 'uri', 'hits');\r", + "});\r", + "\r", + "pm.test(\"В теле ответа должна соблюдаться сортировка по убыванию количества просмотров\", function(){\r", + " pm.expect(target[0].hits).to.be.above(target[1].hits);\r", + "});\r", + "\r", + "pm.test(\"Проверка соответствия реального количества просмотров событий и сохраненных хитов\", function(){\r", + " pm.expect(source[0].hits+1).equal(target[0].hits);\r", + " pm.expect(source[1].hits+1).equal(target[1].hits);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris={{uri}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "stats" + ], + "query": [ + { + "key": "start", + "value": "2020-05-05 00:00:00", + "description": "(Required) Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "end", + "value": "2035-05-05 00:00:00", + "description": "(Required) Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "uris", + "value": "{{uri}}", + "description": "Список uri для которых нужно выгрузить статистику" + }, + { + "key": "uris", + "value": "aliqua o", + "description": "Список uri для которых нужно выгрузить статистику", + "disabled": true + }, + { + "key": "unique", + "value": "false", + "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)", + "disabled": true + } + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "API = class {", + " constructor(postman, verbose = false, baseUrl = \"http://localhost:9090\") {", + " this.baseUrl = baseUrl;", + " this.pm = postman;", + " this._verbose = verbose;", + " }", + "", + " async addPost(post, verbose=null) {", + " return this.post(\"/hit\", post, \"Ошибка при сохранении информации о запросе к эндпойнту: \", verbose);", + " }", + "", + " async getPosts(uris=null, verbose=null) {", + " return this.get(uris == null ? \"/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00\" : \"/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris=\"+uris.join('&uris='), null, \"Ошибка при сохранении информации о запросе к эндпойнту: \", verbose);", + " }", + "", + " async post(path, body, errorText = \"Ошибка при выполнении post-запроса: \", verbose=null) {", + " return this.sendRequest(\"POST\", path, body, errorText);", + " }", + "", + " async get(path, body = null, errorText = \"Ошибка при выполнении get-запроса: \", verbose=null) {", + " return this.sendRequest(\"GET\", path, body, errorText);", + " }", + "", + " async sendRequest(method, path, body=null, errorText = \"Ошибка при выполнении запроса: \", verbose=null) {", + " return new Promise((resolve, reject) => {", + " verbose = verbose == null ? this._verbose : verbose;", + "", + " let request = {", + " url: this.baseUrl + path,", + " method: method,", + " body: body == null ? \"\" : JSON.stringify(body),", + " header: { \"Content-Type\": \"application/json\" },", + " };", + "", + " if(verbose) {", + " console.log(\"Отправляю запрос: \", request);", + " }", + "", + " try {", + " this.pm.sendRequest(request, (error, response) => {", + " if(error || (response.code >= 400 && response.code <= 599)) {", + " let err = error ? error : JSON.stringify(response.json());", + " console.error(\"При выполнении запроса к серверу возникла ошика.\\n\", err,", + " \"\\nДля отладки проблемы повторите такой же запрос к вашей программе \" + ", + " \"на локальном компьютере. Данные запроса:\\n\", JSON.stringify(request));", + "", + " reject(new Error(errorText + err));", + " }", + "", + " if(verbose) {", + " console.log(\"Результат обработки запроса: код состояния - \", response.code, \", тело: \", response.json());", + " }", + " try{", + " resolve(response.json());", + " } catch(err){", + " resolve(response);", + " }", + " ", + " });", + " } catch(err) {", + " if(verbose) {", + " console.error(errorText, err);", + " }", + " return Promise.reject(err);", + " }", + " });", + " }", + "};", + "", + "RandomUtils = class {", + " constructor() {}", + "", + " getPost() {", + " return {", + " app: \"ewm-main-service\",", + " uri: \"/events/\" + pm.variables.replaceIn('{{$randomInt}}'),", + " ip: '121.0.0.1',", + " timestamp: this.getPastDateTime()", + " }", + " }", + "", + " getPastDateTime(hourShift = 5, minuteShift=0, yearShift=0) {", + " let moment = require('moment');", + "", + " let m = moment();", + " m.subtract(hourShift, 'hour');", + " m.subtract(minuteShift, 'minute');", + " m.subtract(yearShift, 'year');", + "", + " return m.format('YYYY-MM-DD HH:mm:ss');", + " }", + "", + "}" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "baseUrl", + "value": "http://localhost:9090", + "type": "string" + }, + { + "key": "uri", + "value": "1" + }, + { + "key": "source", + "value": "" + } + ] +} From 97751428caf965461ef9df84b99bde2c18560ffc Mon Sep 17 00:00:00 2001 From: LightInTheFire <109972737+LightInTheFire@users.noreply.github.com> Date: Fri, 26 Dec 2025 22:48:08 +0300 Subject: [PATCH 06/10] Main svc users (#8) * feat: add userservice and implementation * feat: add usermapper * feat: add repository * feat: add user admin controller * fix: change return type * fix: add unique constraint * fix: set required param to false * feat: update postman tests to contain categories and users * fix: add List import * fix: change transactional to readonly --- .../user/controller/UserAdminController.java | 49 + .../ru/practicum/user/mapper/UserMapper.java | 23 + .../java/ru/practicum/user/model/User.java | 2 +- .../user/repository/UserRepository.java | 13 + .../practicum/user/service/UserService.java | 14 + .../user/service/UserServiceImpl.java | 57 + .../user/service/UsersGetRequest.java | 9 + postman/ewm-main-service.json | 2890 +++++++++++++++-- 8 files changed, 2861 insertions(+), 196 deletions(-) create mode 100644 main-service/src/main/java/ru/practicum/user/controller/UserAdminController.java create mode 100644 main-service/src/main/java/ru/practicum/user/mapper/UserMapper.java create mode 100644 main-service/src/main/java/ru/practicum/user/repository/UserRepository.java create mode 100644 main-service/src/main/java/ru/practicum/user/service/UserService.java create mode 100644 main-service/src/main/java/ru/practicum/user/service/UserServiceImpl.java create mode 100644 main-service/src/main/java/ru/practicum/user/service/UsersGetRequest.java diff --git a/main-service/src/main/java/ru/practicum/user/controller/UserAdminController.java b/main-service/src/main/java/ru/practicum/user/controller/UserAdminController.java new file mode 100644 index 0000000..f239a1e --- /dev/null +++ b/main-service/src/main/java/ru/practicum/user/controller/UserAdminController.java @@ -0,0 +1,49 @@ +package ru.practicum.user.controller; + +import java.util.Collection; +import java.util.List; + +import jakarta.validation.Valid; + +import ru.practicum.user.dto.NewUserRequest; +import ru.practicum.user.dto.UserDto; +import ru.practicum.user.service.UserService; +import ru.practicum.user.service.UsersGetRequest; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin/users") +public class UserAdminController { + private final UserService userService; + + @GetMapping() + public Collection getUsersPaged( + @RequestParam(required = false) List ids, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size) { + log.info("Get users requested with ids={} from={} size={}", ids, from, size); + UsersGetRequest request = new UsersGetRequest(from, size, ids); + return userService.getUsersPaged(request); + } + + @PostMapping() + @ResponseStatus(HttpStatus.CREATED) + public UserDto createUser(@RequestBody @Valid NewUserRequest newUserRequest) { + log.info("Create user requested: {}", newUserRequest); + return userService.createUser(newUserRequest); + } + + @DeleteMapping("/{userId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteUser(@PathVariable Long userId) { + log.info("Delete user with id={} requested", userId); + userService.deleteUserById(userId); + } +} diff --git a/main-service/src/main/java/ru/practicum/user/mapper/UserMapper.java b/main-service/src/main/java/ru/practicum/user/mapper/UserMapper.java new file mode 100644 index 0000000..79dd42f --- /dev/null +++ b/main-service/src/main/java/ru/practicum/user/mapper/UserMapper.java @@ -0,0 +1,23 @@ +package ru.practicum.user.mapper; + +import ru.practicum.user.dto.NewUserRequest; +import ru.practicum.user.dto.UserDto; +import ru.practicum.user.dto.UserShortDto; +import ru.practicum.user.model.User; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class UserMapper { + public User mapToEntity(NewUserRequest dto) { + return new User(null, dto.name(), dto.email()); + } + + public UserDto mapToUserDto(User user) { + return new UserDto(user.getEmail(), user.getId(), user.getName()); + } + + public UserShortDto mapToUserShortDto(User user) { + return new UserShortDto(user.getId(), user.getName()); + } +} diff --git a/main-service/src/main/java/ru/practicum/user/model/User.java b/main-service/src/main/java/ru/practicum/user/model/User.java index 87a328d..d58a759 100644 --- a/main-service/src/main/java/ru/practicum/user/model/User.java +++ b/main-service/src/main/java/ru/practicum/user/model/User.java @@ -22,6 +22,6 @@ public class User { @Column(nullable = false) String name; - @Column(nullable = false) + @Column(nullable = false, unique = true) String email; } diff --git a/main-service/src/main/java/ru/practicum/user/repository/UserRepository.java b/main-service/src/main/java/ru/practicum/user/repository/UserRepository.java new file mode 100644 index 0000000..030e790 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/user/repository/UserRepository.java @@ -0,0 +1,13 @@ +package ru.practicum.user.repository; + +import java.util.Collection; +import java.util.List; + +import ru.practicum.user.model.User; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { + List findAllByIdIn(Collection ids, Pageable pageable); +} diff --git a/main-service/src/main/java/ru/practicum/user/service/UserService.java b/main-service/src/main/java/ru/practicum/user/service/UserService.java new file mode 100644 index 0000000..88aa2ca --- /dev/null +++ b/main-service/src/main/java/ru/practicum/user/service/UserService.java @@ -0,0 +1,14 @@ +package ru.practicum.user.service; + +import java.util.Collection; + +import ru.practicum.user.dto.NewUserRequest; +import ru.practicum.user.dto.UserDto; + +public interface UserService { + Collection getUsersPaged(UsersGetRequest request); + + UserDto createUser(NewUserRequest newUserRequest); + + void deleteUserById(Long userId); +} diff --git a/main-service/src/main/java/ru/practicum/user/service/UserServiceImpl.java b/main-service/src/main/java/ru/practicum/user/service/UserServiceImpl.java new file mode 100644 index 0000000..3e0879c --- /dev/null +++ b/main-service/src/main/java/ru/practicum/user/service/UserServiceImpl.java @@ -0,0 +1,57 @@ +package ru.practicum.user.service; + +import java.util.Collection; + +import ru.practicum.exception.NotFoundException; +import ru.practicum.user.dto.NewUserRequest; +import ru.practicum.user.dto.UserDto; +import ru.practicum.user.mapper.UserMapper; +import ru.practicum.user.model.User; +import ru.practicum.user.repository.UserRepository; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; + +@Service +@Transactional +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + + @Override + @Transactional(readOnly = true) + public Collection getUsersPaged(UsersGetRequest request) { + Pageable pageable = PageRequest.of(request.from(), request.size()); + + if (request.hasIds()) { + return userRepository.findAllByIdIn(request.ids(), pageable).stream() + .map(UserMapper::mapToUserDto) + .toList(); + } + + return userRepository.findAll(pageable).stream().map(UserMapper::mapToUserDto).toList(); + } + + @Override + public UserDto createUser(NewUserRequest newUserRequest) { + User user = UserMapper.mapToEntity(newUserRequest); + User savedUser = userRepository.save(user); + return UserMapper.mapToUserDto(savedUser); + } + + @Override + public void deleteUserById(Long userId) { + getUserByIdOrThrow(userId); + userRepository.deleteById(userId); + } + + private User getUserByIdOrThrow(Long userId) { + return userRepository + .findById(userId) + .orElseThrow(NotFoundException.supplier("User with id=%d not found", userId)); + } +} diff --git a/main-service/src/main/java/ru/practicum/user/service/UsersGetRequest.java b/main-service/src/main/java/ru/practicum/user/service/UsersGetRequest.java new file mode 100644 index 0000000..3fec225 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/user/service/UsersGetRequest.java @@ -0,0 +1,9 @@ +package ru.practicum.user.service; + +import java.util.List; + +public record UsersGetRequest(int from, int size, List ids) { + public boolean hasIds() { + return ids != null && !ids.isEmpty(); + } +} diff --git a/postman/ewm-main-service.json b/postman/ewm-main-service.json index e42bf58..6fd7223 100644 --- a/postman/ewm-main-service.json +++ b/postman/ewm-main-service.json @@ -1,32 +1,59 @@ { "info": { - "_postman_id": "26aaf842-99f3-4571-832c-13549d81b8c6", + "_postman_id": "f778b13b-bd02-4617-8fae-282f672abb17", "name": "Test Explore With Me - Main service", - "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", - "_exporter_id": "37174685" + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "27311667", + "_collection_link": "https://api555-4802.postman.co/workspace/explore-with-me~26332bb9-fe8e-403d-8af2-e0d94c187194/collection/27311667-f778b13b-bd02-4617-8fae-282f672abb17?action=share&source=collection_link&creator=27311667" }, "item": [ { "name": "Validation", "item": [ { - "name": "Category", + "name": "Users", "item": [ { "name": "Required query params", "item": [] }, { - "name": "Unrequired params in body", + "name": "Unrequired query params", "item": [ { - "name": "Получение категорий без нескольких Query params", + "name": "Поиск пользователей без нескольких Query params", "event": [ { "listen": "prerequest", "script": { "exec": [ - "" + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" ], "type": "text/javascript" } @@ -54,38 +81,39 @@ } ], "url": { - "raw": "{{baseUrl}}/categories", + "raw": "{{baseUrl}}/admin/users?ids={{uid}}", "host": [ "{{baseUrl}}" ], "path": [ - "categories" + "admin", + "users" ], "query": [ + { + "key": "ids", + "value": "{{uid}}", + "description": "id пользователей" + }, { "key": "from", "value": "0", - "description": "количество категорий, которые нужно пропустить для формирования текущего набора", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", "disabled": true }, { "key": "size", - "value": "10000", - "description": "количество категорий в наборе", + "value": "10", + "description": "количество элементов в наборе", "disabled": true } ] } }, "response": [] - } - ] - }, - { - "name": "Required params in body", - "item": [ + }, { - "name": "Добавление категории с именем, состоящим из пробелов", + "name": "Поиск пользователей без параметра ids", "event": [ { "listen": "prerequest", @@ -95,18 +123,20 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let category;\r", + " let compilation;\r", " try {\r", - " category = {name: ' '};\r", + " let user1 = rnd.getUser();\r", + " user1 = await api.addUser(user1);\r", + "\r", + " let user2 = rnd.getUser()\r", + " user2 = await api.addUser(user2);\r", + " //pm.collectionVariables.set('fromId', user1.id);\r", + " pm.collectionVariables.set('source1', user1);\r", + " pm.collectionVariables.set('source2', user2);\r", + "\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -131,11 +161,33 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", - " pm.response.to.be.badRequest; \r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", + "\r", + "const target = pm.response.json();\r", + "const source1 = pm.collectionVariables.get('source1');\r", + "const source2 = pm.collectionVariables.get('source2');\r", + "\r", + "pm.test(\"Пользователи должны содержать поля: id, name, email\", function () {\r", + " pm.expect(target[target.length-2]).to.have.property('id');\r", + " pm.expect(target[target.length-2]).to.have.property('name');\r", + " pm.expect(target[target.length-2]).to.have.property('email');\r", + " pm.expect(target[target.length-1]).to.have.property('id');\r", + " pm.expect(target[target.length-1]).to.have.property('name');\r", + " pm.expect(target[target.length-1]).to.have.property('email');\r", + "});\r", + "\r", + "pm.test(\"Данные последних двух пользователей должны совпадать с данными добавленных пользователей\", function () {\r", + " pm.expect(target[target.length-2].id).to.equal(source1.id);\r", + " pm.expect(target[target.length-2].name).to.equal(source1.name);\r", + " pm.expect(target[target.length-2].email).to.equal(source1.email);\r", + " pm.expect(target[target.length-1].id).to.equal(source2.id);\r", + " pm.expect(target[target.length-1].name).to.equal(source2.name);\r", + " pm.expect(target[target.length-1].email).to.equal(source2.email);\r", + "});\r", "" ], "type": "text/javascript" @@ -143,18 +195,51 @@ } ], "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "" - }, - "url": "{{baseUrl}}/admin/categories" + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/users?from={{fromId}}&size=100000", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ], + "query": [ + { + "key": "ids", + "value": "{{uid}}", + "description": "id пользователей", + "disabled": true + }, + { + "key": "from", + "value": "{{fromId}}", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "100000", + "description": "количество элементов в наборе" + } + ] + } }, "response": [] - }, + } + ] + }, + { + "name": "Required params in body", + "item": [ { - "name": "Добавление категории с пустым полем name", + "name": "Добавление пользователя с электронной почтой, состоящей только из пробелов", "event": [ { "listen": "prerequest", @@ -164,16 +249,17 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let category;\r", + " let user;\r", " try {\r", - " category = {name: ''};\r", + " user = rnd.getUser();\r", + " user.email = \" \";\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", + " raw: JSON.stringify(user),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -200,12 +286,9 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", - " pm.response.to.be.badRequest; \r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "" + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" ], "type": "text/javascript" } @@ -213,17 +296,40 @@ ], "request": { "method": "POST", - "header": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], "body": { "mode": "raw", - "raw": "" + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } }, - "url": "{{baseUrl}}/admin/categories" + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } }, "response": [] }, { - "name": "Добавление категории без поля name", + "name": "Добавление пользователя с пустой электронной почтой", "event": [ { "listen": "prerequest", @@ -233,16 +339,17 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let category;\r", + " let user;\r", " try {\r", - " category = {};\r", + " user = rnd.getUser();\r", + " user.email = \"\";\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", + " raw: JSON.stringify(user),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -269,12 +376,9 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", - " pm.response.to.be.badRequest; \r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "" + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" ], "type": "text/javascript" } @@ -282,22 +386,40 @@ ], "request": { "method": "POST", - "header": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], "body": { "mode": "raw", - "raw": "" + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } }, - "url": "{{baseUrl}}/admin/categories" + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } }, "response": [] - } - ] - }, - { - "name": "Misc tests", - "item": [ + }, { - "name": "Изменение категории с неизменными данными", + "name": "Добавление пользователя без поля email", "event": [ { "listen": "prerequest", @@ -306,17 +428,18 @@ "const main = async () => {\r", " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", - " let category, categoryObj\r", + "\r", + " let user;\r", " try {\r", - " category = rnd.getCategory();\r", - " categoryObj = await api.addCategory(category);\r", + " user = rnd.getUser();\r", + " delete user.email;\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - " pm.collectionVariables.set(\"catid\", Number(categoryObj.id))\r", + "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", + " raw: JSON.stringify(user),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -343,23 +466,8 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", - " pm.response.to.be.ok; \r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "\r", - "const source = JSON.parse(pm.request.body.raw);\r", - "const target = pm.response.json();\r", - "\r", - "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", - "pm.expect(target).to.have.property('id');\r", - "pm.expect(target).to.have.property('name');\r", - "});\r", - "\r", - "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(target.id).to.not.be.null;\r", - " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", "});" ], "type": "text/javascript" @@ -367,39 +475,41 @@ } ], "request": { - "method": "PATCH", - "header": [], + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], "body": { "mode": "raw", - "raw": "{{request_body}}" + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } }, "url": { - "raw": "{{baseUrl}}/admin/categories/:catId", + "raw": "{{baseUrl}}/admin/users", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "categories", - ":catId" - ], - "variable": [ - { - "key": "catId", - "value": "{{catid}}" - } + "users" ] } }, "response": [] - } - ] - }, - { - "name": "String length restrictions", - "item": [ + }, { - "name": "Добавление новой категории с name.length > 50", + "name": "Добавление пользователя с пустым именем", "event": [ { "listen": "prerequest", @@ -409,16 +519,17 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let category;\r", + " let user;\r", " try {\r", - " category = {'name': rnd.getWord(51)};\r", + " user = rnd.getUser();\r", + " user.name = \"\";\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", + " raw: JSON.stringify(user),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -445,12 +556,9 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", - " pm.response.to.be.badRequest; \r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "" + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" ], "type": "text/javascript" } @@ -477,7 +585,1954 @@ } } }, - "url": "{{baseUrl}}/admin/categories", + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с именем, состоящим только из пробелов", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = \" \";\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя без поля name", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " delete user.name;\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Misc tests", + "item": [] + }, + { + "name": "String length restrictions", + "item": [ + { + "name": "Добавление пользователя с name.length < 2", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(1);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с name.length == 2", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(2);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с name.length > 250", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(251);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с name.length == 250", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(250);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.length < 6", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@a.r';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.length == 6", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@a.ru';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.localpart.length > 64", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(65) + '@a.ru';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.localpart.length == 64", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(59) + '@a.ru';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с domain.part.length > 63", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@' + rnd.getWord(64) + '.ru';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с domain.part.length == 63", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@' + rnd.getWord(60) + '.ru';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.length > 254", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(61);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.length == 254", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(60);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Default values check", + "item": [ + { + "name": "Проверка на значения по-умолчанию from и size(user)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " for (let i = 0; i < 11; i++){\r", + " await api.addUser(rnd.getUser());\r", + " }\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/admin/users?from=0\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {pm.collectionVariables.set('source', response.json())});\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('source');\r", + "\r", + "pm.test(\"Значение from по-умолчанию должно быть равным 0\", function () {\r", + " pm.expect(target[0].id).to.be.equal(source[0].id, 'Запросы с from=0 и без него должны начинаться с одного и того же события');\r", + "});\r", + "\r", + "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", + " pm.expect(target.length).to.be.equal(10);\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ], + "query": [ + { + "key": "ids", + "value": "{{uid}}", + "description": "id пользователей", + "disabled": true + }, + { + "key": "ids", + "value": "-10833646", + "description": "id пользователей", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "10", + "description": "количество элементов в наборе", + "disabled": true + } + ] + } + }, + "response": [] + } + ] + } + ] + }, + { + "name": "Category", + "item": [ + { + "name": "Required query params", + "item": [] + }, + { + "name": "Unrequired params in body", + "item": [ + { + "name": "Получение категорий без нескольких Query params", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "categories" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество категорий, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "10000", + "description": "количество категорий в наборе", + "disabled": true + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Required params in body", + "item": [ + { + "name": "Добавление категории с именем, состоящим из пробелов", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {name: ' '};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + } + }, + "response": [] + }, + { + "name": "Добавление категории с пустым полем name", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {name: ''};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + } + }, + "response": [] + }, + { + "name": "Добавление категории без поля name", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Misc tests", + "item": [ + { + "name": "Изменение категории с неизменными данными", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category, categoryObj\r", + " try {\r", + " category = rnd.getCategory();\r", + " categoryObj = await api.addCategory(category);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(categoryObj.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "String length restrictions", + "item": [ + { + "name": "Добавление новой категории с name.length > 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {'name': rnd.getWord(51)};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + }, "description": "Обратите внимание: имя категории должно быть уникальным" }, "response": [] @@ -561,7 +2616,16 @@ } } }, - "url": "{{baseUrl}}/admin/categories", + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + }, "description": "Обратите внимание: имя категории должно быть уникальным" }, "response": [] @@ -848,15 +2912,302 @@ } ] } - ] - } - ] - }, - { - "name": "409 Conflict", - "item": [ + ] + } + ] + }, + { + "name": "409 Conflict", + "item": [ + { + "name": "Попытка изменения имени категории на уже существующее", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category1, category2\r", + " try {\r", + " category1 = await api.addCategory(rnd.getCategory());\r", + " category2 = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", category2.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : category1.name\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Добавление новой категории с занятым именем", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = rnd.getCategory();\r", + " await api.addCategory(category);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Добавление пользователя с занятым именем почты", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(10);\r", + " await api.addUser(user);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, { - "name": "Попытка изменения имени категории на уже существующее", + "name": "Изменение имени категории на уже занятое", "event": [ { "listen": "prerequest", @@ -872,11 +3223,11 @@ " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - " pm.collectionVariables.set(\"catid\", category2.id)\r", + " pm.collectionVariables.set(\"catid\", Number(category1.id))\r", " pm.request.body.update({\r", " mode: 'raw',\r", " raw: JSON.stringify({\r", - " name : category1.name\r", + " name : category2.name\r", " }),\r", " options: { raw: { language: 'json' } }\r", " });\r", @@ -916,24 +3267,10 @@ ], "request": { "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], + "header": [], "body": { "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{{request_body}}" }, "url": { "raw": "{{baseUrl}}/admin/categories/:catId", @@ -951,13 +3288,131 @@ "value": "{{catid}}" } ] + } + }, + "response": [] + } + ] + }, + { + "name": "Users", + "item": [ + { + "name": "Поиск пользователей", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } }, - "description": "Обратите внимание: имя категории должно быть уникальным" + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Пользователи должны содержать поля: id, name, email\", function () {\r", + " pm.expect(target[0]).to.have.property('id');\r", + " pm.expect(target[0]).to.have.property('name');\r", + " pm.expect(target[0]).to.have.property('email');\r", + "});\r", + "\r", + "pm.test(\"Должен быть найден только один пользователь по заданному фильтру\", function () {\r", + " pm.expect(target.length).to.eql(1);\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target[0].id).equal(pm.collectionVariables.get(\"uid\"));\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/users?ids={{uid}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ], + "query": [ + { + "key": "ids", + "value": "{{uid}}", + "description": "id пользователей" + }, + { + "key": "ids", + "value": "-10833646", + "description": "id пользователей", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "10", + "description": "количество элементов в наборе", + "disabled": true + } + ] + } }, "response": [] }, { - "name": "Добавление новой категории с занятым именем", + "name": "Добавление нового пользователя", "event": [ { "listen": "prerequest", @@ -967,17 +3422,16 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let category;\r", + " let user;\r", " try {\r", - " category = rnd.getCategory();\r", - " await api.addCategory(category);\r", + " user = rnd.getUser();\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", + " raw: JSON.stringify(user),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -1004,10 +3458,25 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", - " pm.response.to.have.status(409);\r", + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Пользователь должен содержать поля: id, name, email\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "pm.expect(target).to.have.property('email');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Имя пользователя должно соответствовать отправленному в запросе');\r", + " pm.expect(source.email).equal(target.email, 'Почта пользователя должна соответствовать отправленной в запросе');\r", "});" ], "type": "text/javascript" @@ -1035,14 +3504,52 @@ } } }, - "url": "{{baseUrl}}/admin/categories", - "description": "Обратите внимание: имя категории должно быть уникальным" + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } }, "response": [] }, { - "name": "Изменение имени категории на уже занятое", + "name": "Удаление пользователя", "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 204\", function () {\r", + " pm.response.to.have.status(204);\r", + "});\r", + "const source = pm.collectionVariables.get('response');\r", + "const userId = pm.collectionVariables.get('uid');\r", + "\r", + "pm.test(\"Пользователь должен быть найден до выполнения запроса\", function(){\r", + " pm.expect(source.length).to.eql(1);\r", + " pm.expect(source[0].id).to.eql(userId);\r", + "});\r", + "let body\r", + "const req = {\r", + " url: \"http://localhost:8080/admin/users?ids=\" + pm.collectionVariables.get(\"uid\"),\r", + " method: \"GET\",\r", + " body: body == null ? \"\" : JSON.stringify(body),\r", + " header: { \"Content-Type\": \"application/json\" },\r", + " };\r", + "pm.sendRequest(req, (error, response) => {\r", + " pm.test(\"Пользователь должен быть удалён после выполнения запроса\", function(){\r", + " pm.expect(response.json().length).to.eql(0);\r", + " });\r", + "})" + ], + "type": "text/javascript" + } + }, { "listen": "prerequest", "script": { @@ -1050,21 +3557,16 @@ "const main = async () => {\r", " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", - " let category1, category2\r", + "\r", + " let compilation;\r", " try {\r", - " category1 = await api.addCategory(rnd.getCategory());\r", - " category2 = await api.addCategory(rnd.getCategory());\r", + " const user = await api.addUser(rnd.getUser());\r", + " const foundedUser = await api.findUser(user.id);\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"response\", foundedUser)\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - " pm.collectionVariables.set(\"catid\", Number(category1.id))\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify({\r", - " name : category2.name\r", - " }),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -1084,42 +3586,31 @@ ], "type": "text/javascript" } - }, - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", - " pm.response.to.have.status(409);\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});" - ], - "type": "text/javascript" - } } ], "request": { - "method": "PATCH", - "header": [], - "body": { - "mode": "raw", - "raw": "{{request_body}}" - }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], "url": { - "raw": "{{baseUrl}}/admin/categories/:catId", + "raw": "{{baseUrl}}/admin/users/:userId", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "categories", - ":catId" + "users", + ":userId" ], "variable": [ { - "key": "catId", - "value": "{{catid}}" + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id пользователя" } ] } @@ -1222,7 +3713,16 @@ } } }, - "url": "{{baseUrl}}/admin/categories", + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + }, "description": "Обратите внимание: имя категории должно быть уникальным" }, "response": [] From 6bab7b564db94f408fa959a2f2261fafff21fe49 Mon Sep 17 00:00:00 2001 From: LightInTheFire <109972737+LightInTheFire@users.noreply.github.com> Date: Fri, 9 Jan 2026 18:38:30 +0300 Subject: [PATCH 07/10] Main svc events (#9) * feat: add jpa eventRepo * feat: add EventMapper * feat: add Event sort options enum * feat: add event requests for services * feat: add event controllers * feat: add eventService and implementation * feat: add querydsl * fix: add logging to public controller * fix: add logging to admin controller * feat: update event private controller * feat: add EventsPrivateGetRequest * feat: add methods to event service interface * feat: add validation exception * feat: add location mapper * fix: increase limit in event model * fix: update querydsl * feat: add constraint to newEventDto eventdate to be in future * feat: separate state actions to two enums * feat: add updatableevent interface and mapper methods * fix: method names * feat: add datetime properties, correct LDT requestparam mapping and changed pattern in jsonconfig to properties * feat: add boolean predicate as static methods * chore: move methods * feat: add IllegalEventUpdateException * fix: typo in Enum * feat: add check to eventstate * feat: add httprequest param * feat: add @Future constraint * feat: implement interface methods * feat: update tests to containt events * feat: add client method to accept ip and uri strings * fix: send actual uri to service * fix: change transactional to readonly * feat: add sql params logging * fix: update pagerequest to work correctly * feat: add ForbiddenAccessException * fix: add check to private api methods * fix: add call to parent constructor in FAE * Revert "feat: add client method to accept ip and uri strings" This reverts commit 8db80490dc2359da40027d5bb9b02d62d06ec301. * feat: update stats server tests * fix: save /events endpoint when request multiple events and send null as views in other endpoints --- main-service/pom.xml | 35 + .../practicum/config/DateTimeProperties.java | 13 + .../java/ru/practicum/config/JsonConfig.java | 13 +- .../java/ru/practicum/config/WebConfig.java | 38 + .../controller/EventAdminController.java | 49 + .../controller/EventPrivateController.java | 63 + .../controller/EventPublicController.java | 65 + .../event/controller/EventSortBy.java | 6 + .../ru/practicum/event/dto/NewEventDto.java | 7 +- .../practicum/event/dto/UpdatableEvent.java | 39 + .../event/dto/UpdateEventAdminRequest.java | 10 +- .../event/dto/UpdateEventUserRequest.java | 10 +- .../practicum/event/mapper/EventMapper.java | 136 + .../event/mapper/LocationMapper.java | 18 + .../event/model/AdminStateAction.java | 6 + .../java/ru/practicum/event/model/Event.java | 6 +- .../ru/practicum/event/model/EventState.java | 2 +- ...{StateAction.java => UserStateAction.java} | 2 +- .../event/repository/EventRepository.java | 89 + .../practicum/event/service/EventService.java | 25 + .../event/service/EventServiceImpl.java | 298 + .../event/service/EventsAdminGetRequest.java | 44 + .../service/EventsPrivateGetRequest.java | 12 + .../event/service/EventsPublicGetRequest.java | 57 + .../exception/ForbiddenAccessException.java | 7 + .../IllegalEventUpdateException.java | 7 + .../exception/ValidationException.java | 7 + .../handler/GlobalExceptionHandler.java | 43 +- .../src/main/resources/application-dev.yaml | 8 + pom.xml | 11 + postman/ewm-main-service.json | 8739 +++++++++++++++-- postman/ewm-stat-service.json | 706 +- 32 files changed, 9375 insertions(+), 1196 deletions(-) create mode 100644 main-service/src/main/java/ru/practicum/config/DateTimeProperties.java create mode 100644 main-service/src/main/java/ru/practicum/config/WebConfig.java create mode 100644 main-service/src/main/java/ru/practicum/event/controller/EventAdminController.java create mode 100644 main-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java create mode 100644 main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java create mode 100644 main-service/src/main/java/ru/practicum/event/controller/EventSortBy.java create mode 100644 main-service/src/main/java/ru/practicum/event/dto/UpdatableEvent.java create mode 100644 main-service/src/main/java/ru/practicum/event/mapper/EventMapper.java create mode 100644 main-service/src/main/java/ru/practicum/event/mapper/LocationMapper.java create mode 100644 main-service/src/main/java/ru/practicum/event/model/AdminStateAction.java rename main-service/src/main/java/ru/practicum/event/model/{StateAction.java => UserStateAction.java} (71%) create mode 100644 main-service/src/main/java/ru/practicum/event/repository/EventRepository.java create mode 100644 main-service/src/main/java/ru/practicum/event/service/EventService.java create mode 100644 main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java create mode 100644 main-service/src/main/java/ru/practicum/event/service/EventsAdminGetRequest.java create mode 100644 main-service/src/main/java/ru/practicum/event/service/EventsPrivateGetRequest.java create mode 100644 main-service/src/main/java/ru/practicum/event/service/EventsPublicGetRequest.java create mode 100644 main-service/src/main/java/ru/practicum/exception/ForbiddenAccessException.java create mode 100644 main-service/src/main/java/ru/practicum/exception/IllegalEventUpdateException.java create mode 100644 main-service/src/main/java/ru/practicum/exception/ValidationException.java diff --git a/main-service/pom.xml b/main-service/pom.xml index 82a4b30..6e7b885 100644 --- a/main-service/pom.xml +++ b/main-service/pom.xml @@ -56,8 +56,43 @@ org.springframework.boot spring-boot-starter-validation + + + com.querydsl + querydsl-jpa + jakarta + + + + + com.mysema.maven + apt-maven-plugin + 1.1.3 + + + + process + + + target/generated-sources/java + com.querydsl.apt.jpa.JPAAnnotationProcessor + + + + + + com.querydsl + querydsl-apt + jakarta + ${querydsl.version} + + + + + + dev diff --git a/main-service/src/main/java/ru/practicum/config/DateTimeProperties.java b/main-service/src/main/java/ru/practicum/config/DateTimeProperties.java new file mode 100644 index 0000000..79f229f --- /dev/null +++ b/main-service/src/main/java/ru/practicum/config/DateTimeProperties.java @@ -0,0 +1,13 @@ +package ru.practicum.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import lombok.Getter; +import lombok.Setter; + +@Component +@ConfigurationProperties(prefix = "app") +public class DateTimeProperties { + @Getter @Setter private String dateTimeFormat = "yyyy-MM-dd HH:mm:ss"; +} diff --git a/main-service/src/main/java/ru/practicum/config/JsonConfig.java b/main-service/src/main/java/ru/practicum/config/JsonConfig.java index feaa326..82059fa 100644 --- a/main-service/src/main/java/ru/practicum/config/JsonConfig.java +++ b/main-service/src/main/java/ru/practicum/config/JsonConfig.java @@ -9,18 +9,23 @@ import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import lombok.RequiredArgsConstructor; + @Configuration +@RequiredArgsConstructor public class JsonConfig { - private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; + private final DateTimeProperties properties; @Bean public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { return builder -> { - builder.simpleDateFormat(DATE_TIME_PATTERN); + builder.simpleDateFormat(properties.getDateTimeFormat()); builder.serializers( - new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN))); + new LocalDateTimeSerializer( + DateTimeFormatter.ofPattern(properties.getDateTimeFormat()))); builder.deserializers( - new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN))); + new LocalDateTimeDeserializer( + DateTimeFormatter.ofPattern(properties.getDateTimeFormat()))); }; } } diff --git a/main-service/src/main/java/ru/practicum/config/WebConfig.java b/main-service/src/main/java/ru/practicum/config/WebConfig.java new file mode 100644 index 0000000..6fcbd80 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/config/WebConfig.java @@ -0,0 +1,38 @@ +package ru.practicum.config; + +import java.text.ParseException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +import org.springframework.context.annotation.Configuration; +import org.springframework.format.Formatter; +import org.springframework.format.FormatterRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import lombok.RequiredArgsConstructor; + +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + private final DateTimeProperties properties; + + @Override + public void addFormatters(FormatterRegistry registry) { + registry.addFormatter( + new Formatter() { + + @Override + public String print(LocalDateTime object, Locale locale) { + return object.format( + DateTimeFormatter.ofPattern(properties.getDateTimeFormat())); + } + + @Override + public LocalDateTime parse(String text, Locale locale) throws ParseException { + return LocalDateTime.parse( + text, DateTimeFormatter.ofPattern(properties.getDateTimeFormat())); + } + }); + } +} diff --git a/main-service/src/main/java/ru/practicum/event/controller/EventAdminController.java b/main-service/src/main/java/ru/practicum/event/controller/EventAdminController.java new file mode 100644 index 0000000..31fd7e7 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/controller/EventAdminController.java @@ -0,0 +1,49 @@ +package ru.practicum.event.controller; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +import jakarta.validation.Valid; + +import ru.practicum.event.dto.EventFullDto; +import ru.practicum.event.dto.UpdateEventAdminRequest; +import ru.practicum.event.model.EventState; +import ru.practicum.event.service.EventService; +import ru.practicum.event.service.EventsAdminGetRequest; + +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin/events") +public class EventAdminController { + private final EventService eventService; + + @GetMapping + public Collection getEventsFiltered( + @RequestParam(required = false) List users, + @RequestParam(required = false) List states, + @RequestParam(required = false) List categories, + @RequestParam(required = false) LocalDateTime rangeStart, + @RequestParam(required = false) LocalDateTime rangeEnd, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size) { + EventsAdminGetRequest getRequest = + new EventsAdminGetRequest( + users, states, categories, rangeStart, rangeEnd, from, size); + log.info("Admin get events requested with params= {}", getRequest); + return eventService.getEvents(getRequest); + } + + @PatchMapping("/{eventId}") + public EventFullDto updateEvent( + @PathVariable Long eventId, @RequestBody @Valid UpdateEventAdminRequest updateRequest) { + log.info("Admin update event requested with body= {}", updateRequest); + return eventService.updateEvent(eventId, updateRequest); + } +} diff --git a/main-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java b/main-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java new file mode 100644 index 0000000..d0c0976 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java @@ -0,0 +1,63 @@ +package ru.practicum.event.controller; + +import java.util.Collection; + +import jakarta.validation.Valid; + +import ru.practicum.event.dto.EventFullDto; +import ru.practicum.event.dto.EventShortDto; +import ru.practicum.event.dto.NewEventDto; +import ru.practicum.event.dto.UpdateEventUserRequest; +import ru.practicum.event.service.EventService; +import ru.practicum.event.service.EventsPrivateGetRequest; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/users/{userId}/events") +public class EventPrivateController { + private final EventService eventService; + + @GetMapping() + public Collection getEventsOfUserPaged( + @PathVariable Long userId, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size) { + EventsPrivateGetRequest getRequest = new EventsPrivateGetRequest(userId, from, size); + log.info("Private get events requested with params= {}", getRequest); + return eventService.getEvents(getRequest); + } + + @PostMapping() + @ResponseStatus(HttpStatus.CREATED) + public EventFullDto createEvent( + @PathVariable Long userId, @RequestBody @Valid NewEventDto newEventDto) { + log.info("Create event requested with body= {}", newEventDto); + return eventService.createEvent(userId, newEventDto); + } + + @GetMapping("/{eventId}") + public EventFullDto getEvent(@PathVariable Long userId, @PathVariable Long eventId) { + log.info("Private get event requested with userId= {}, eventId= {}", userId, eventId); + return eventService.getByUserById(userId, eventId); + } + + @PatchMapping("/{eventId}") + public EventFullDto updateEvent( + @PathVariable Long userId, + @PathVariable Long eventId, + @RequestBody @Valid UpdateEventUserRequest updateRequest) { + log.info( + "Private update event requested with userId= {}, eventId= {}, body = {}", + userId, + eventId, + updateRequest); + return eventService.updateEventByUser(userId, eventId, updateRequest); + } +} diff --git a/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java b/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java new file mode 100644 index 0000000..8501170 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java @@ -0,0 +1,65 @@ +package ru.practicum.event.controller; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +import jakarta.servlet.http.HttpServletRequest; + +import ru.practicum.event.dto.EventFullDto; +import ru.practicum.event.dto.EventShortDto; +import ru.practicum.event.service.EventService; +import ru.practicum.event.service.EventsPublicGetRequest; +import ru.practicum.exception.ValidationException; + +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/events") +public class EventPublicController { + private final EventService eventService; + + @GetMapping() + public Collection getEventsFiltered( + @RequestParam(required = false) String text, + @RequestParam(required = false) List categories, + @RequestParam(required = false) Boolean paid, + @RequestParam(required = false) LocalDateTime rangeStart, + @RequestParam(required = false) LocalDateTime rangeEnd, + @RequestParam(defaultValue = "false") boolean onlyAvailable, + @RequestParam(required = false) EventSortBy sort, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size, + HttpServletRequest request) { + EventsPublicGetRequest getRequest = + new EventsPublicGetRequest( + text, + categories, + paid, + rangeStart, + rangeEnd, + onlyAvailable, + sort, + from, + size, + request); + if (getRequest.hasRangeStart() && getRequest.hasRangeEnd()) { + if (getRequest.rangeEnd().isBefore(getRequest.rangeStart())) { + throw new ValidationException("End date must be before start date"); + } + } + log.info("Public get events requested with params= {}", getRequest); + return eventService.getEvents(getRequest); + } + + @GetMapping("/{eventId}") + public EventFullDto getEventById(@PathVariable Long eventId, HttpServletRequest request) { + log.info("Public get event with eventId={} requested", eventId); + return eventService.getById(eventId, request); + } +} diff --git a/main-service/src/main/java/ru/practicum/event/controller/EventSortBy.java b/main-service/src/main/java/ru/practicum/event/controller/EventSortBy.java new file mode 100644 index 0000000..0ff03c7 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/controller/EventSortBy.java @@ -0,0 +1,6 @@ +package ru.practicum.event.controller; + +public enum EventSortBy { + EVENT_DATE, + VIEWS +} diff --git a/main-service/src/main/java/ru/practicum/event/dto/NewEventDto.java b/main-service/src/main/java/ru/practicum/event/dto/NewEventDto.java index 323e75f..1d9de92 100644 --- a/main-service/src/main/java/ru/practicum/event/dto/NewEventDto.java +++ b/main-service/src/main/java/ru/practicum/event/dto/NewEventDto.java @@ -2,16 +2,13 @@ import java.time.LocalDateTime; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.PositiveOrZero; -import jakarta.validation.constraints.Size; +import jakarta.validation.constraints.*; public record NewEventDto( @NotBlank @Size(min = 20, max = 2000) String annotation, @NotNull Long category, @NotBlank @Size(min = 20, max = 7000) String description, - @NotNull LocalDateTime eventDate, + @NotNull @Future LocalDateTime eventDate, @NotNull LocationDto location, Boolean paid, @PositiveOrZero Integer participantLimit, diff --git a/main-service/src/main/java/ru/practicum/event/dto/UpdatableEvent.java b/main-service/src/main/java/ru/practicum/event/dto/UpdatableEvent.java new file mode 100644 index 0000000..6c1b17c --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/dto/UpdatableEvent.java @@ -0,0 +1,39 @@ +package ru.practicum.event.dto; + +import java.time.LocalDateTime; + +public interface UpdatableEvent { + boolean hasAnnotation(); + + String annotation(); + + boolean hasEventDate(); + + LocalDateTime eventDate(); + + boolean hasCategory(); + + boolean hasLocation(); + + LocationDto location(); + + boolean hasParticipantLimit(); + + Integer participantLimit(); + + boolean hasPaid(); + + Boolean paid(); + + boolean hasRequestModeration(); + + Boolean requestModeration(); + + boolean hasTitle(); + + String title(); + + boolean hasDescription(); + + String description(); +} diff --git a/main-service/src/main/java/ru/practicum/event/dto/UpdateEventAdminRequest.java b/main-service/src/main/java/ru/practicum/event/dto/UpdateEventAdminRequest.java index 23b0f72..c39faff 100644 --- a/main-service/src/main/java/ru/practicum/event/dto/UpdateEventAdminRequest.java +++ b/main-service/src/main/java/ru/practicum/event/dto/UpdateEventAdminRequest.java @@ -2,22 +2,24 @@ import java.time.LocalDateTime; +import jakarta.validation.constraints.Future; import jakarta.validation.constraints.PositiveOrZero; import jakarta.validation.constraints.Size; -import ru.practicum.event.model.StateAction; +import ru.practicum.event.model.AdminStateAction; public record UpdateEventAdminRequest( @Size(min = 20, max = 2000) String annotation, Long category, @Size(min = 20, max = 7000) String description, - LocalDateTime eventDate, + @Future LocalDateTime eventDate, LocationDto location, Boolean paid, @PositiveOrZero Integer participantLimit, Boolean requestModeration, - StateAction stateAction, - @Size(min = 3, max = 120) String title) { + AdminStateAction stateAction, + @Size(min = 3, max = 120) String title) + implements UpdatableEvent { public boolean hasAnnotation() { return annotation != null && !annotation.isBlank(); diff --git a/main-service/src/main/java/ru/practicum/event/dto/UpdateEventUserRequest.java b/main-service/src/main/java/ru/practicum/event/dto/UpdateEventUserRequest.java index 1c95bbd..82aee57 100644 --- a/main-service/src/main/java/ru/practicum/event/dto/UpdateEventUserRequest.java +++ b/main-service/src/main/java/ru/practicum/event/dto/UpdateEventUserRequest.java @@ -2,22 +2,24 @@ import java.time.LocalDateTime; +import jakarta.validation.constraints.Future; import jakarta.validation.constraints.PositiveOrZero; import jakarta.validation.constraints.Size; -import ru.practicum.event.model.StateAction; +import ru.practicum.event.model.UserStateAction; public record UpdateEventUserRequest( @Size(min = 20, max = 2000) String annotation, Long category, @Size(min = 20, max = 7000) String description, - LocalDateTime eventDate, + @Future LocalDateTime eventDate, LocationDto location, Boolean paid, @PositiveOrZero Integer participantLimit, Boolean requestModeration, - StateAction stateAction, - @Size(min = 3, max = 120) String title) { + UserStateAction stateAction, + @Size(min = 3, max = 120) String title) + implements UpdatableEvent { public boolean hasAnnotation() { return annotation != null && !annotation.isBlank(); diff --git a/main-service/src/main/java/ru/practicum/event/mapper/EventMapper.java b/main-service/src/main/java/ru/practicum/event/mapper/EventMapper.java new file mode 100644 index 0000000..4df4931 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/mapper/EventMapper.java @@ -0,0 +1,136 @@ +package ru.practicum.event.mapper; + +import java.time.LocalDateTime; + +import ru.practicum.category.mapper.CategoryMapper; +import ru.practicum.category.model.Category; +import ru.practicum.event.dto.*; +import ru.practicum.event.model.Event; +import ru.practicum.event.model.EventState; +import ru.practicum.event.model.Location; +import ru.practicum.user.mapper.UserMapper; +import ru.practicum.user.model.User; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class EventMapper { + + public Event mapToEntity( + NewEventDto newEventDto, Category category, User initiator, Location location) { + return new Event( + null, + newEventDto.annotation(), + category, + LocalDateTime.now(), + newEventDto.description(), + newEventDto.eventDate(), + initiator, + location, + newEventDto.paid(), + newEventDto.participantLimit(), + null, + newEventDto.requestModeration(), + EventState.PENDING, + newEventDto.title()); + } + + public EventFullDto mapToFullDto(Event event, long confirmedRequests, Long views) { + return new EventFullDto( + event.getAnnotation(), + CategoryMapper.mapToDto(event.getCategory()), + confirmedRequests, + event.getCreatedOn(), + event.getDescription(), + event.getEventDate(), + event.getId(), + UserMapper.mapToUserShortDto(event.getInitiator()), + LocationMapper.mapToDto(event.getLocation()), + event.getPaid(), + event.getParticipantLimit(), + event.getPublishedOn(), + event.getRequestModeration(), + event.getState(), + event.getTitle(), + views); + } + + public EventShortDto mapToShortDto(Event event, long confirmedRequests, Long views) { + return new EventShortDto( + event.getAnnotation(), + CategoryMapper.mapToDto(event.getCategory()), + confirmedRequests, + event.getEventDate(), + event.getId(), + UserMapper.mapToUserShortDto(event.getInitiator()), + event.getPaid(), + event.getTitle(), + views); + } + + public void updateEventFromDto( + Event event, UpdateEventAdminRequest updateDto, Category newCategory) { + if (updateDto.hasStateAction()) { + switch (updateDto.stateAction()) { + case PUBLISH_EVENT -> { + event.setState(EventState.PUBLISHED); + event.setPublishedOn(LocalDateTime.now()); + } + case REJECT_EVENT -> event.setState(EventState.CANCELED); + } + } + + updateCommonFields(event, updateDto, newCategory); + } + + public void updateEventFromDto( + Event event, UpdateEventUserRequest updateDto, Category newCategory) { + if (updateDto.hasStateAction()) { + switch (updateDto.stateAction()) { + case SEND_TO_REVIEW -> event.setState(EventState.PENDING); + case CANCEL_REVIEW -> event.setState(EventState.CANCELED); + } + } + + updateCommonFields(event, updateDto, newCategory); + } + + private void updateCommonFields(Event event, UpdatableEvent updateDto, Category newCategory) { + if (updateDto.hasAnnotation()) { + event.setAnnotation(updateDto.annotation()); + } + + if (updateDto.hasEventDate()) { + event.setEventDate(updateDto.eventDate()); + } + + if (updateDto.hasCategory()) { + event.setCategory(newCategory); + } + + if (updateDto.hasLocation()) { + Location location = LocationMapper.mapToEntity(updateDto.location()); + event.setLocation(location); + } + + if (updateDto.hasParticipantLimit()) { + event.setParticipantLimit(updateDto.participantLimit()); + } + + if (updateDto.hasPaid()) { + event.setPaid(updateDto.paid()); + } + + if (updateDto.hasRequestModeration()) { + event.setRequestModeration(updateDto.requestModeration()); + } + + if (updateDto.hasTitle()) { + event.setTitle(updateDto.title()); + } + + if (updateDto.hasDescription()) { + event.setDescription(updateDto.description()); + } + } +} diff --git a/main-service/src/main/java/ru/practicum/event/mapper/LocationMapper.java b/main-service/src/main/java/ru/practicum/event/mapper/LocationMapper.java new file mode 100644 index 0000000..15472d9 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/mapper/LocationMapper.java @@ -0,0 +1,18 @@ +package ru.practicum.event.mapper; + +import ru.practicum.event.dto.LocationDto; +import ru.practicum.event.model.Location; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class LocationMapper { + + public Location mapToEntity(LocationDto locationDto) { + return new Location(locationDto.lat(), locationDto.lon()); + } + + public LocationDto mapToDto(Location location) { + return new LocationDto(location.getLat(), location.getLon()); + } +} diff --git a/main-service/src/main/java/ru/practicum/event/model/AdminStateAction.java b/main-service/src/main/java/ru/practicum/event/model/AdminStateAction.java new file mode 100644 index 0000000..b6574b1 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/model/AdminStateAction.java @@ -0,0 +1,6 @@ +package ru.practicum.event.model; + +public enum AdminStateAction { + PUBLISH_EVENT, + REJECT_EVENT +} diff --git a/main-service/src/main/java/ru/practicum/event/model/Event.java b/main-service/src/main/java/ru/practicum/event/model/Event.java index 4b5eb7d..923ba49 100644 --- a/main-service/src/main/java/ru/practicum/event/model/Event.java +++ b/main-service/src/main/java/ru/practicum/event/model/Event.java @@ -24,7 +24,7 @@ public class Event { @GeneratedValue(strategy = GenerationType.IDENTITY) Long id; - @Column(nullable = false) + @Column(nullable = false, length = 2000) String annotation; @ManyToOne(fetch = FetchType.LAZY) @@ -34,7 +34,7 @@ public class Event { @Column(nullable = false) LocalDateTime createdOn; - @Column(nullable = false) + @Column(nullable = false, length = 7000) String description; @Column(nullable = false) @@ -61,6 +61,6 @@ public class Event { @Column(nullable = false) EventState state; - @Column(nullable = false) + @Column(nullable = false, length = 120) String title; } diff --git a/main-service/src/main/java/ru/practicum/event/model/EventState.java b/main-service/src/main/java/ru/practicum/event/model/EventState.java index e7dc6de..9cfbb30 100644 --- a/main-service/src/main/java/ru/practicum/event/model/EventState.java +++ b/main-service/src/main/java/ru/practicum/event/model/EventState.java @@ -3,5 +3,5 @@ public enum EventState { PENDING, PUBLISHED, - CANCELLED + CANCELED } diff --git a/main-service/src/main/java/ru/practicum/event/model/StateAction.java b/main-service/src/main/java/ru/practicum/event/model/UserStateAction.java similarity index 71% rename from main-service/src/main/java/ru/practicum/event/model/StateAction.java rename to main-service/src/main/java/ru/practicum/event/model/UserStateAction.java index 9019e86..114d5a7 100644 --- a/main-service/src/main/java/ru/practicum/event/model/StateAction.java +++ b/main-service/src/main/java/ru/practicum/event/model/UserStateAction.java @@ -1,6 +1,6 @@ package ru.practicum.event.model; -public enum StateAction { +public enum UserStateAction { SEND_TO_REVIEW, CANCEL_REVIEW } diff --git a/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java b/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java new file mode 100644 index 0000000..c9917e8 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java @@ -0,0 +1,89 @@ +package ru.practicum.event.repository; + +import java.time.LocalDateTime; +import java.util.Optional; + +import ru.practicum.event.model.Event; +import ru.practicum.event.model.EventState; +import ru.practicum.event.model.QEvent; +import ru.practicum.event.service.EventsAdminGetRequest; +import ru.practicum.event.service.EventsPublicGetRequest; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; + +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Predicate; + +public interface EventRepository + extends JpaRepository, QuerydslPredicateExecutor { + static Predicate createPredicate(EventsAdminGetRequest request) { + QEvent event = QEvent.event; + BooleanBuilder builder = new BooleanBuilder(); + + if (request.hasUsers()) { + builder.and(event.initiator.id.in(request.users())); + } + + if (request.hasStates()) { + builder.and(event.state.in(request.states())); + } + + if (request.hasCategories()) { + builder.and(event.category.id.in(request.categories())); + } + + if (request.hasRangeStart()) { + builder.and(event.eventDate.goe(request.rangeStart())); + } + + if (request.hasRangeEnd()) { + builder.and(event.eventDate.loe(request.rangeEnd())); + } + + return builder; + } + + static Predicate createPredicate(EventsPublicGetRequest request) { + QEvent event = QEvent.event; + BooleanBuilder builder = new BooleanBuilder(); + + builder.and(event.state.eq(EventState.PUBLISHED)); + + if (request.hasPaid()) { + builder.and(event.paid.eq(request.paid())); + } + + if (request.hasText()) { + String text = request.text(); + builder.and( + event.annotation + .containsIgnoreCase(text) + .or(event.description.containsIgnoreCase(text))); + } + + if (request.hasCategories()) { + builder.and(event.category.id.in(request.categories())); + } + + if (request.hasRangeStart()) { + builder.and(event.eventDate.goe(request.rangeStart())); + } + + if (request.hasRangeEnd()) { + builder.and(event.eventDate.loe(request.rangeEnd())); + } + + if (!(request.hasRangeStart() && request.hasRangeEnd())) { + builder.and(event.eventDate.goe(LocalDateTime.now())); + } + + return builder; + } + + Optional findByIdAndState(Long id, EventState state); + + Page findByInitiator_Id(Long initiatorId, Pageable pageable); +} diff --git a/main-service/src/main/java/ru/practicum/event/service/EventService.java b/main-service/src/main/java/ru/practicum/event/service/EventService.java new file mode 100644 index 0000000..be8a801 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/service/EventService.java @@ -0,0 +1,25 @@ +package ru.practicum.event.service; + +import java.util.Collection; + +import jakarta.servlet.http.HttpServletRequest; + +import ru.practicum.event.dto.*; + +public interface EventService { + EventFullDto getById(Long eventId, HttpServletRequest request); + + Collection getEvents(EventsPublicGetRequest getRequest); + + Collection getEvents(EventsAdminGetRequest getRequest); + + Collection getEvents(EventsPrivateGetRequest getRequest); + + EventFullDto createEvent(Long userId, NewEventDto newEventDto); + + EventFullDto getByUserById(Long userId, Long eventId); + + EventFullDto updateEvent(Long eventId, UpdateEventAdminRequest updateRequest); + + EventFullDto updateEventByUser(Long userId, Long eventId, UpdateEventUserRequest updateRequest); +} diff --git a/main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java b/main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java new file mode 100644 index 0000000..f5a456e --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java @@ -0,0 +1,298 @@ +package ru.practicum.event.service; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.stream.Collectors; +import java.util.*; + +import jakarta.servlet.http.HttpServletRequest; + +import ru.practicum.category.model.Category; +import ru.practicum.category.repository.CategoryRepository; +import ru.practicum.client.StatsClient; +import ru.practicum.dto.ViewStatsDto; +import ru.practicum.event.controller.EventSortBy; +import ru.practicum.event.dto.*; +import ru.practicum.event.mapper.EventMapper; +import ru.practicum.event.mapper.LocationMapper; +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.ForbiddenAccessException; +import ru.practicum.exception.IllegalEventUpdateException; +import ru.practicum.exception.NotFoundException; +import ru.practicum.exception.ValidationException; +import ru.practicum.user.model.User; +import ru.practicum.user.repository.UserRepository; + +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class EventServiceImpl implements EventService { + private static final Duration MIN_TIME_BEFORE_EVENT = Duration.ofHours(2); + private static final LocalDateTime MINIMAL_LOCAL_DATE_TIME = + LocalDateTime.of(1000, 1, 1, 0, 0, 0); + private static final LocalDateTime MAXIMUM_LOCAL_DATE_TIME = + LocalDateTime.of(9999, 1, 1, 0, 0, 0); + private static final String EVENTS_URI = "/events/%d"; + private final EventRepository eventRepository; + private final UserRepository userRepository; + private final CategoryRepository categoryRepository; + private final StatsClient statsClient; + + @Override + public EventFullDto getById(Long eventId, HttpServletRequest request) { + Optional eventOptional = + eventRepository.findByIdAndState(eventId, EventState.PUBLISHED); + Event event = + eventOptional.orElseThrow( + NotFoundException.supplier("Event with id=%d not found", eventId)); + + statsClient.hit(request); + String uri = request.getRequestURI(); + + ViewStatsDto statsDto = getStatsForEvent(event, uri); + + return EventMapper.mapToFullDto( + event, 0, statsDto.hits()); // change 0 to actual number of requests + } + + @Override + public Collection getEvents(EventsPublicGetRequest getRequest) { + Page events = + eventRepository.findAll( + EventRepository.createPredicate(getRequest), getRequest.getPageable()); + + statsClient.hit(getRequest.httpRequest()); + + LocalDateTime statsFrom = + getRequest.hasRangeStart() ? getRequest.rangeStart() : LocalDateTime.now(); + LocalDateTime statsTo = + getRequest.hasRangeEnd() ? getRequest.rangeEnd() : MAXIMUM_LOCAL_DATE_TIME; + + Map statsForEvents = getStatsMapForEvents(events, statsFrom, statsTo); + + List eventsList = + events.stream() + .map( + event -> + EventMapper.mapToShortDto( + event, + 0, + statsForEvents.get( + event.getId()))) // change 0 to actual + // number of requests + .toList(); + + if (EventSortBy.VIEWS.equals(getRequest.sort())) { + return eventsList.stream().sorted(Comparator.comparing(EventShortDto::views)).toList(); + } + + return eventsList; + } + + @Override + public Collection getEvents(EventsAdminGetRequest getRequest) { + Page events = + eventRepository.findAll( + EventRepository.createPredicate(getRequest), getRequest.getPageable()); + + Map statsForEvents = + getStatsMapForEvents(events, MINIMAL_LOCAL_DATE_TIME, MAXIMUM_LOCAL_DATE_TIME); + + return events.stream() + .map( + event -> + EventMapper.mapToFullDto( + event, + 0, + statsForEvents.get( + event.getId()))) // change 0 to actual number of + // requests + .toList(); + } + + @Override + public Collection getEvents(EventsPrivateGetRequest getRequest) { + getUserByIdOrThrow(getRequest.userId()); + Page events = + eventRepository.findByInitiator_Id(getRequest.userId(), getRequest.getPageable()); + + Map statsForEvents = + getStatsMapForEvents(events, MINIMAL_LOCAL_DATE_TIME, MAXIMUM_LOCAL_DATE_TIME); + + return events.stream() + .map( + event -> + EventMapper.mapToShortDto( + event, + 0, + statsForEvents.get( + event.getId()))) // change 0 to actual number of + // requests + .toList(); + } + + @Override + @Transactional + public EventFullDto createEvent(Long userId, NewEventDto newEventDto) { + Location location = LocationMapper.mapToEntity(newEventDto.location()); + Category category = getCategoryByIdOrThrow(newEventDto.category()); + User initiator = getUserByIdOrThrow(userId); + Event event = EventMapper.mapToEntity(newEventDto, category, initiator, location); + + LocalDateTime now = LocalDateTime.now(); + if (event.getEventDate().isBefore(now.plus(MIN_TIME_BEFORE_EVENT))) { + throw new ValidationException( + "The event must be scheduled at least %d hours from now." + .formatted(MIN_TIME_BEFORE_EVENT.toHours())); + } + Event saved = eventRepository.save(event); + + return EventMapper.mapToFullDto(saved, 0, 0L); + } + + @Override + public EventFullDto getByUserById(Long userId, Long eventId) { + User user = getUserByIdOrThrow(userId); + + Event event = getEventByIdOrThrow(eventId); + + if (!event.getInitiator().getId().equals(user.getId())) { + throw new ForbiddenAccessException("You can't view event that's not yours"); + } + + ViewStatsDto statsDto = getStatsForEvent(event, EVENTS_URI.formatted(eventId)); + + return EventMapper.mapToFullDto( + event, 0, statsDto.hits()); // change 0 to actual number of requests + } + + @Override + @Transactional + public EventFullDto updateEvent(Long eventId, UpdateEventAdminRequest updateRequest) { + Event event = getEventByIdOrThrow(eventId); + + if ((event.getState().equals(EventState.PUBLISHED) + || event.getState().equals(EventState.CANCELED)) + && updateRequest.hasStateAction()) { + throw new IllegalEventUpdateException( + "Forbidden to update event that already %s" + .formatted(event.getState().toString())); + } + + Category newCategory = null; + if (updateRequest.hasCategory()) { + newCategory = getCategoryByIdOrThrow(updateRequest.category()); + } + EventMapper.updateEventFromDto(event, updateRequest, newCategory); + + Event saved = eventRepository.save(event); + + return EventMapper.mapToFullDto(saved, 0, null); // change 0 to actual number of requests + } + + @Override + @Transactional + public EventFullDto updateEventByUser( + Long userId, Long eventId, UpdateEventUserRequest updateRequest) { + Event event = getEventByIdOrThrow(eventId); + User user = getUserByIdOrThrow(userId); + + if (!event.getInitiator().getId().equals(user.getId())) { + throw new ForbiddenAccessException("You can't update event that's not yours"); + } + + if ((event.getState().equals(EventState.PUBLISHED) + || event.getState().equals(EventState.CANCELED)) + && !updateRequest.hasStateAction()) { + throw new IllegalEventUpdateException( + "Forbidden to update event that already %s" + .formatted(event.getState().toString())); + } + + Category newCategory = null; + if (updateRequest.hasCategory()) { + newCategory = getCategoryByIdOrThrow(updateRequest.category()); + } + EventMapper.updateEventFromDto(event, updateRequest, newCategory); + + Event saved = eventRepository.save(event); + + return EventMapper.mapToFullDto(saved, 0, null); // change 0 to actual number of requests + } + + private Map getStatsMapForEvents( + Page events, LocalDateTime from, LocalDateTime to) { + List listOfUris = + events.stream().map(event -> EVENTS_URI.formatted(event.getId())).toList(); + return getStatsForEvents(listOfUris, from, to).stream() + .collect( + Collectors.toMap( + statsDto -> + Long.valueOf( + statsDto.uri() + .substring( + statsDto.uri().lastIndexOf('/') + + 1)), + ViewStatsDto::hits)); + } + + private List getStatsForEvents( + List uris, LocalDateTime from, LocalDateTime to) { + try { + return statsClient.getStats(from, to, uris, true); + } catch (Exception e) { + log.error("Error during getting stats for events", e); + } + return List.of(); + } + + private ViewStatsDto getStatsForEvent(Event event, String uri) { + ViewStatsDto statsDto; + try { + statsDto = + statsClient + .getStats( + MINIMAL_LOCAL_DATE_TIME, + MAXIMUM_LOCAL_DATE_TIME, + List.of(uri), + true) + .getFirst(); + } catch (NoSuchElementException e) { + log.trace("No stats for event with id={} found", event.getId()); + statsDto = new ViewStatsDto(null, null, 0L); + } catch (Exception e) { + log.error("Error during getting stats for event with id={}", event.getId(), e); + statsDto = new ViewStatsDto(null, null, null); + } + return statsDto; + } + + private Event getEventByIdOrThrow(Long eventId) { + Optional eventOptional = eventRepository.findById(eventId); + return eventOptional.orElseThrow( + NotFoundException.supplier("Event with id=%d not found", eventId)); + } + + private User getUserByIdOrThrow(Long userId) { + Optional optionalUser = userRepository.findById(userId); + return optionalUser.orElseThrow( + NotFoundException.supplier("User with id=%d not found", userId)); + } + + private Category getCategoryByIdOrThrow(Long categoryId) { + Optional optionalCategory = categoryRepository.findById(categoryId); + return optionalCategory.orElseThrow( + NotFoundException.supplier("Category with id=%d not found", categoryId)); + } +} diff --git a/main-service/src/main/java/ru/practicum/event/service/EventsAdminGetRequest.java b/main-service/src/main/java/ru/practicum/event/service/EventsAdminGetRequest.java new file mode 100644 index 0000000..18bd132 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/service/EventsAdminGetRequest.java @@ -0,0 +1,44 @@ +package ru.practicum.event.service; + +import java.time.LocalDateTime; +import java.util.List; + +import ru.practicum.event.model.EventState; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +public record EventsAdminGetRequest( + List users, + List states, + List categories, + LocalDateTime rangeStart, + LocalDateTime rangeEnd, + int from, + int size) { + + public boolean hasUsers() { + return users != null && !users.isEmpty(); + } + + public boolean hasStates() { + return states != null && !states.isEmpty(); + } + + public boolean hasCategories() { + return categories != null && !categories.isEmpty(); + } + + public boolean hasRangeStart() { + return rangeStart != null; + } + + public boolean hasRangeEnd() { + return rangeEnd != null; + } + + public Pageable getPageable() { + int page = from / size; + return PageRequest.of(page, size); + } +} diff --git a/main-service/src/main/java/ru/practicum/event/service/EventsPrivateGetRequest.java b/main-service/src/main/java/ru/practicum/event/service/EventsPrivateGetRequest.java new file mode 100644 index 0000000..3df3186 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/service/EventsPrivateGetRequest.java @@ -0,0 +1,12 @@ +package ru.practicum.event.service; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +public record EventsPrivateGetRequest(Long userId, int from, int size) { + + public Pageable getPageable() { + int page = from / size; + return PageRequest.of(page, size); + } +} diff --git a/main-service/src/main/java/ru/practicum/event/service/EventsPublicGetRequest.java b/main-service/src/main/java/ru/practicum/event/service/EventsPublicGetRequest.java new file mode 100644 index 0000000..b4a8cfa --- /dev/null +++ b/main-service/src/main/java/ru/practicum/event/service/EventsPublicGetRequest.java @@ -0,0 +1,57 @@ +package ru.practicum.event.service; + +import java.time.LocalDateTime; +import java.util.List; + +import jakarta.servlet.http.HttpServletRequest; + +import ru.practicum.event.controller.EventSortBy; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +public record EventsPublicGetRequest( + String text, + List categories, + Boolean paid, + LocalDateTime rangeStart, + LocalDateTime rangeEnd, + boolean onlyAvailable, + EventSortBy sort, + int from, + int size, + HttpServletRequest httpRequest) { + + public boolean hasText() { + return text != null && !text.isEmpty(); + } + + public boolean hasCategories() { + return categories != null && !categories.isEmpty(); + } + + public boolean hasPaid() { + return paid != null; + } + + public boolean hasRangeStart() { + return rangeStart != null; + } + + public boolean hasRangeEnd() { + return rangeEnd != null; + } + + public boolean hasSortBy() { + return sort != null; + } + + public Pageable getPageable() { + int page = from / size; + if (EventSortBy.EVENT_DATE.equals(sort)) { + return PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "eventDate")); + } + return PageRequest.of(page, size); + } +} diff --git a/main-service/src/main/java/ru/practicum/exception/ForbiddenAccessException.java b/main-service/src/main/java/ru/practicum/exception/ForbiddenAccessException.java new file mode 100644 index 0000000..1e7f8b5 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/exception/ForbiddenAccessException.java @@ -0,0 +1,7 @@ +package ru.practicum.exception; + +public class ForbiddenAccessException extends RuntimeException { + public ForbiddenAccessException(String message) { + super(message); + } +} diff --git a/main-service/src/main/java/ru/practicum/exception/IllegalEventUpdateException.java b/main-service/src/main/java/ru/practicum/exception/IllegalEventUpdateException.java new file mode 100644 index 0000000..7e961c7 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/exception/IllegalEventUpdateException.java @@ -0,0 +1,7 @@ +package ru.practicum.exception; + +public class IllegalEventUpdateException extends RuntimeException { + public IllegalEventUpdateException(String message) { + super(message); + } +} diff --git a/main-service/src/main/java/ru/practicum/exception/ValidationException.java b/main-service/src/main/java/ru/practicum/exception/ValidationException.java new file mode 100644 index 0000000..d751d21 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/exception/ValidationException.java @@ -0,0 +1,7 @@ +package ru.practicum.exception; + +public class ValidationException extends RuntimeException { + public ValidationException(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 78bf9c7..bf746af 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,7 +8,10 @@ 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; @@ -85,7 +88,7 @@ public ApiError handleMethodArgumentNotValidException(MethodArgumentNotValidExce public ApiError handleNotFoundException(NotFoundException e) { log.warn("Not found: {}", e.getMessage()); return new ApiError( - List.of(e.getMessage()), + null, e.getMessage(), "The required object was not found", HttpStatus.NOT_FOUND.toString(), @@ -97,10 +100,46 @@ public ApiError handleNotFoundException(NotFoundException e) { public ApiError handleDataIntegrityViolationException(DataIntegrityViolationException e) { log.warn(e.getMessage(), e); return new ApiError( - List.of(e.getMessage()), + null, e.getMessage(), "Some fields of RequestBody for request are invalid", HttpStatus.NOT_FOUND.toString(), LocalDateTime.now()); } + + @ResponseStatus(HttpStatus.CONFLICT) + @ExceptionHandler(IllegalEventUpdateException.class) + public ApiError handleIllegalEventUpdateException(IllegalEventUpdateException e) { + log.warn(e.getMessage(), e); + return new ApiError( + null, + e.getMessage(), + "Trying to update event that already Published or Canceled", + HttpStatus.CONFLICT.toString(), + LocalDateTime.now()); + } + + @ResponseStatus(HttpStatus.FORBIDDEN) + @ExceptionHandler(ForbiddenAccessException.class) + public ApiError handleForbiddenAccessException(ForbiddenAccessException e) { + log.warn(e.getMessage(), e); + return new ApiError( + null, + e.getMessage(), + "Forbidden", + HttpStatus.FORBIDDEN.toString(), + LocalDateTime.now()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(ValidationException.class) + public ApiError handleValidationException(ValidationException e) { + log.warn(e.getMessage(), e); + return new ApiError( + null, + e.getMessage(), + "Incorrect request", + HttpStatus.BAD_REQUEST.toString(), + LocalDateTime.now()); + } } diff --git a/main-service/src/main/resources/application-dev.yaml b/main-service/src/main/resources/application-dev.yaml index 06e3952..0c0b25f 100644 --- a/main-service/src/main/resources/application-dev.yaml +++ b/main-service/src/main/resources/application-dev.yaml @@ -4,6 +4,7 @@ server: stats-server: url: http://localhost:9090 app: ${STATS_SERVER_APP:ewm-main-service} + spring: application: name: ewm-service @@ -25,3 +26,10 @@ spring: sql: init: mode: embedded +logging: + level: + org: + hibernate: + orm: + jdbc: + bind: trace diff --git a/pom.xml b/pom.xml index bf1fdc6..268da56 100644 --- a/pom.xml +++ b/pom.xml @@ -25,8 +25,19 @@ 21 UTF-8 + 5.1.0 + + + + com.querydsl + querydsl-jpa + ${querydsl.version} + jakarta + + + diff --git a/postman/ewm-main-service.json b/postman/ewm-main-service.json index 6fd7223..9c84a28 100644 --- a/postman/ewm-main-service.json +++ b/postman/ewm-main-service.json @@ -1,27 +1,23 @@ { "info": { - "_postman_id": "f778b13b-bd02-4617-8fae-282f672abb17", - "name": "Test Explore With Me - Main service", + "_postman_id": "788f4375-147e-4e45-ab6d-7baa8e542674", + "name": "Test Explore With Me - Main service Copy", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "_exporter_id": "27311667", - "_collection_link": "https://api555-4802.postman.co/workspace/explore-with-me~26332bb9-fe8e-403d-8af2-e0d94c187194/collection/27311667-f778b13b-bd02-4617-8fae-282f672abb17?action=share&source=collection_link&creator=27311667" + "_collection_link": "https://api555-4802.postman.co/workspace/explore-with-me~26332bb9-fe8e-403d-8af2-e0d94c187194/collection/27311667-788f4375-147e-4e45-ab6d-7baa8e542674?action=share&source=collection_link&creator=27311667" }, "item": [ { "name": "Validation", "item": [ { - "name": "Users", + "name": "Event", "item": [ - { - "name": "Required query params", - "item": [] - }, { "name": "Unrequired query params", "item": [ { - "name": "Поиск пользователей без нескольких Query params", + "name": "Получение событий, добавленных текущим пользователем без нескольких Query params", "event": [ { "listen": "prerequest", @@ -31,10 +27,11 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let compilation;\r", " try {\r", " const user = await api.addUser(rnd.getUser());\r", " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -81,20 +78,16 @@ } ], "url": { - "raw": "{{baseUrl}}/admin/users?ids={{uid}}", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events" ], "query": [ - { - "key": "ids", - "value": "{{uid}}", - "description": "id пользователей" - }, { "key": "from", "value": "0", @@ -103,17 +96,24 @@ }, { "key": "size", - "value": "10", + "value": "1000", "description": "количество элементов в наборе", "disabled": true } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] } }, "response": [] }, { - "name": "Поиск пользователей без параметра ids", + "name": "Получение событий с возможностью фильтрации без нескольких Query params", "event": [ { "listen": "prerequest", @@ -123,17 +123,13 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let compilation;\r", " try {\r", - " let user1 = rnd.getUser();\r", - " user1 = await api.addUser(user1);\r", - "\r", - " let user2 = rnd.getUser()\r", - " user2 = await api.addUser(user2);\r", - " //pm.collectionVariables.set('fromId', user1.id);\r", - " pm.collectionVariables.set('source1', user1);\r", - " pm.collectionVariables.set('source2', user2);\r", - "\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.request.removeQueryParams(['text', 'categories', 'paid']);\r", + " pm.request.addQueryParams([`text=` + event.annotation, 'categories=' + category.id, 'paid=' + event.paid]);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -144,6 +140,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -165,30 +162,7 @@ " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", - "});\r", - "\r", - "const target = pm.response.json();\r", - "const source1 = pm.collectionVariables.get('source1');\r", - "const source2 = pm.collectionVariables.get('source2');\r", - "\r", - "pm.test(\"Пользователи должны содержать поля: id, name, email\", function () {\r", - " pm.expect(target[target.length-2]).to.have.property('id');\r", - " pm.expect(target[target.length-2]).to.have.property('name');\r", - " pm.expect(target[target.length-2]).to.have.property('email');\r", - " pm.expect(target[target.length-1]).to.have.property('id');\r", - " pm.expect(target[target.length-1]).to.have.property('name');\r", - " pm.expect(target[target.length-1]).to.have.property('email');\r", - "});\r", - "\r", - "pm.test(\"Данные последних двух пользователей должны совпадать с данными добавленных пользователей\", function () {\r", - " pm.expect(target[target.length-2].id).to.equal(source1.id);\r", - " pm.expect(target[target.length-2].name).to.equal(source1.name);\r", - " pm.expect(target[target.length-2].email).to.equal(source1.email);\r", - " pm.expect(target[target.length-1].id).to.equal(source2.id);\r", - " pm.expect(target[target.length-1].name).to.equal(source2.name);\r", - " pm.expect(target[target.length-1].email).to.equal(source2.email);\r", - "});\r", - "" + "});" ], "type": "text/javascript" } @@ -203,33 +177,71 @@ } ], "url": { - "raw": "{{baseUrl}}/admin/users?from={{fromId}}&size=100000", + "raw": "{{baseUrl}}/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "events" ], "query": [ { - "key": "ids", - "value": "{{uid}}", - "description": "id пользователей", + "key": "text", + "value": "0", + "description": "текст для поиска в содержимом аннотации и подробном описании события", + "disabled": true + }, + { + "key": "categories", + "value": "0", + "description": "список идентификаторов категорий в которых будет вестись поиск", + "disabled": true + }, + { + "key": "paid", + "value": "true", + "description": "поиск только платных/бесплатных событий", + "disabled": true + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие", + "disabled": true + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие", + "disabled": true + }, + { + "key": "onlyAvailable", + "value": "false", + "description": "только события у которых не исчерпан лимит запросов на участие", + "disabled": true + }, + { + "key": "sort", + "value": "EVENT_DATE", + "description": "Вариант сортировки: по дате события или по количеству просмотров", "disabled": true }, { "key": "from", - "value": "{{fromId}}", - "description": "количество элементов, которые нужно пропустить для формирования текущего набора" + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора", + "disabled": true }, { "key": "size", - "value": "100000", - "description": "количество элементов в наборе" + "value": "1000", + "description": "количество событий в наборе", + "disabled": true } ] - } + }, + "description": "Обратите внимание: \n- это публичный эндпоинт, соответственно в выдаче должны быть только опубликованные события\n- текстовый поиск (по аннотации и подробному описанию) должен быть без учета регистра букв\n- если в запросе не указан диапазон дат [rangeStart-rangeEnd], то нужно выгружать события, которые произойдут позже текущей даты и времени\n- информация о каждом событии должна включать в себя количество просмотров и количество уже одобренных заявок на участие\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" }, "response": [] } @@ -239,7 +251,7 @@ "name": "Required params in body", "item": [ { - "name": "Добавление пользователя с электронной почтой, состоящей только из пробелов", + "name": "Добавление события без поля description", "event": [ { "listen": "prerequest", @@ -249,17 +261,19 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", + " let event;\r", " try {\r", - " user = rnd.getUser();\r", - " user.email = \" \";\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " delete event[\"description\"];\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", + " raw: JSON.stringify(event),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -286,9 +300,12 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", - " pm.response.to.have.status(400);\r", - "});" + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" ], "type": "text/javascript" } @@ -316,20 +333,29 @@ } }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] }, { - "name": "Добавление пользователя с пустой электронной почтой", + "name": "Добавление события с пустым описанием", "event": [ { "listen": "prerequest", @@ -339,17 +365,19 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", + " let event;\r", " try {\r", - " user = rnd.getUser();\r", - " user.email = \"\";\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event[\"description\"] = '';\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", + " raw: JSON.stringify(event),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -376,9 +404,12 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", - " pm.response.to.have.status(400);\r", - "});" + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" ], "type": "text/javascript" } @@ -406,20 +437,29 @@ } }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] }, { - "name": "Добавление пользователя без поля email", + "name": "Добавление события со строкой из пробелов в качестве описания", "event": [ { "listen": "prerequest", @@ -429,17 +469,19 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", + " let event;\r", " try {\r", - " user = rnd.getUser();\r", - " delete user.email;\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event[\"description\"] = ' ';\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", + " raw: JSON.stringify(event),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -466,9 +508,12 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", - " pm.response.to.have.status(400);\r", - "});" + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" ], "type": "text/javascript" } @@ -496,20 +541,29 @@ } }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] }, { - "name": "Добавление пользователя с пустым именем", + "name": "Добавление события без поля annotation", "event": [ { "listen": "prerequest", @@ -519,17 +573,19 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", + " let event;\r", " try {\r", - " user = rnd.getUser();\r", - " user.name = \"\";\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " delete event[\"annotation\"];\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", + " raw: JSON.stringify(event),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -556,9 +612,12 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", - " pm.response.to.have.status(400);\r", - "});" + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" ], "type": "text/javascript" } @@ -586,20 +645,29 @@ } }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] }, { - "name": "Добавление пользователя с именем, состоящим только из пробелов", + "name": "Добавление события с пустой аннотацией", "event": [ { "listen": "prerequest", @@ -609,17 +677,19 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", + " let event;\r", " try {\r", - " user = rnd.getUser();\r", - " user.name = \" \";\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event[\"annotation\"] = '';\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", + " raw: JSON.stringify(event),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -646,9 +716,12 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", - " pm.response.to.have.status(400);\r", - "});" + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" ], "type": "text/javascript" } @@ -676,20 +749,29 @@ } }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] }, { - "name": "Добавление пользователя без поля name", + "name": "Добавление события со строкой из пробелов в качестве аннотации", "event": [ { "listen": "prerequest", @@ -699,17 +781,19 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", + " let event;\r", " try {\r", - " user = rnd.getUser();\r", - " delete user.name;\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event[\"annotation\"] = ' ';\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", + " raw: JSON.stringify(event),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -736,9 +820,12 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", - " pm.response.to.have.status(400);\r", - "});" + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" ], "type": "text/javascript" } @@ -766,29 +853,29 @@ } }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] - } - ] - }, - { - "name": "Misc tests", - "item": [] - }, - { - "name": "String length restrictions", - "item": [ + }, { - "name": "Добавление пользователя с name.length < 2", + "name": "Добавление события с отрицательным лимитом участников", "event": [ { "listen": "prerequest", @@ -798,17 +885,19 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", + " let event;\r", " try {\r", - " user = rnd.getUser();\r", - " user.name = rnd.getWord(1);\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event[\"participantLimit\"] = -6;\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", + " raw: JSON.stringify(event),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -868,20 +957,29 @@ } }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] }, { - "name": "Добавление пользователя с name.length == 2", + "name": "Изменение события добавленного текущим пользователем. Изменение лимита участников на отрицательное значение", "event": [ { "listen": "prerequest", @@ -891,19 +989,23 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", " try {\r", - " user = rnd.getUser();\r", - " user.name = rnd.getWord(2);\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.collectionVariables.set(\"response\", event);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " participantLimit : -156\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -928,8 +1030,8 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", - " pm.response.to.have.status(201);\r", + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.have.status(400);\r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", @@ -940,7 +1042,7 @@ } ], "request": { - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", @@ -961,20 +1063,40 @@ } }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/users/:userId/events/:eventId", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } ] - } + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." }, "response": [] - }, + } + ] + }, + { + "name": "Misc tests", + "item": [ { - "name": "Добавление пользователя с name.length > 250", + "name": "Отклонение публикации события", "event": [ { "listen": "prerequest", @@ -984,19 +1106,34 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", + " let new_event, event;\r", " try {\r", - " user = rnd.getUser();\r", - " user.name = rnd.getWord(251);\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/admin/events/\" + event.id,\r", + " method : \"PATCH\",\r", + " header: { \"Content-Type\": \"application/json\" },\r", + " body: JSON.stringify({\r", + " stateAction: \"REJECT_EVENT\"\r", + " })\r", + " }, (error, response) => {\r", + "\r", + " });\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.collectionVariables.set(\"response\", event);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " stateAction: \"SEND_TO_REVIEW\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -1021,19 +1158,53 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", - " pm.response.to.be.badRequest; \r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", - "" + "\r", + "const source = pm.collectionVariables.get(\"response\");\r", + "const target = pm.response.json();\r", + "\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация отменённого события должна соответствовать аннотации события до отмены');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Категория отменённого события должна соответствовать категории события до отмены');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость отменённого события должна соответствовать стоимости события до отмены');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения отменённого события должна соответствовать дате проведения события до отмены');\r", + " pm.expect(source.description).equal(target.description, 'Описание отменённого события должно соответствовать описанию события до отмены');\r", + " pm.expect(source.title).equal(target.title, 'Название отменённого события должно соответствовать названию события до отмены');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Лимит участников отменённого события должен соответствовать лимиту участников события до отмены');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость отменённого события должна соответствовать стоимости события до отмены');\r", + "});\r", + "\r", + "pm.test(\"Событие должно иметь статус CANCELED при возвращении от администратора и статус PENDING после выполнения запроса\", function () {\r", + " pm.expect(target.state).equal(\"PENDING\");\r", + "});" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", @@ -1054,20 +1225,35 @@ } }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/users/:userId/events/:eventId", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } ] - } + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." }, "response": [] }, { - "name": "Добавление пользователя с name.length == 250", + "name": "Попытка получения информации о событии по публичному эндпоинту без публикации", "event": [ { "listen": "prerequest", @@ -1077,19 +1263,15 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", " try {\r", - " user = rnd.getUser();\r", - " user.name = rnd.getWord(250);\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.collectionVariables.set('response', event);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -1097,6 +1279,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -1114,8 +1297,8 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", - " pm.response.to.have.status(201);\r", + "pm.test(\"Ответ должен содержать код статуса 404 и данные в формате json\", function () {\r", + " pm.response.to.be.notFound; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", @@ -1126,41 +1309,36 @@ } ], "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/events/:id", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "events", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "{{eid}}", + "description": "(Required) id события" + } ] - } + }, + "description": "Обратите внимание:\n- событие должно быть опубликовано\n- информация о событии должна включать в себя количество просмотров и количество подтвержденных запросов\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" }, "response": [] }, { - "name": "Добавление пользователя с email.length < 6", + "name": "Получение событий с возможностью фильтрации и проверкой на валидацию", "event": [ { "listen": "prerequest", @@ -1170,19 +1348,11 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", " try {\r", - " user = rnd.getUser();\r", - " user.email = rnd.getWord(1) + '@a.r';\r", + " \r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -1190,6 +1360,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -1211,49 +1382,82 @@ " pm.response.to.be.badRequest; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", - "});\r", - "" + "});" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/events?text=0&categories=0&paid=true&rangeStart=2022-01-06%2013%3A30%3A38&rangeEnd=2007-09-06%2013%3A30%3A38&onlyAvailable=false&sort=EVENT_DATE&from=0&size=1000", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "events" + ], + "query": [ + { + "key": "text", + "value": "0", + "description": "текст для поиска в содержимом аннотации и подробном описании события" + }, + { + "key": "categories", + "value": "0", + "description": "список идентификаторов категорий в которых будет вестись поиск" + }, + { + "key": "paid", + "value": "true", + "description": "поиск только платных/бесплатных событий" + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие" + }, + { + "key": "rangeEnd", + "value": "2007-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие" + }, + { + "key": "onlyAvailable", + "value": "false", + "description": "только события у которых не исчерпан лимит запросов на участие" + }, + { + "key": "sort", + "value": "EVENT_DATE", + "description": "Вариант сортировки: по дате события или по количеству просмотров" + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе" + } ] - } + }, + "description": "Обратите внимание: \n- это публичный эндпоинт, соответственно в выдаче должны быть только опубликованные события\n- текстовый поиск (по аннотации и подробному описанию) должен быть без учета регистра букв\n- если в запросе не указан диапазон дат [rangeStart-rangeEnd], то нужно выгружать события, которые произойдут позже текущей даты и времени\n- информация о каждом событии должна включать в себя количество просмотров и количество уже одобренных заявок на участие\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" }, "response": [] }, { - "name": "Добавление пользователя с email.length == 6", + "name": "Проверка работы поля views", "event": [ { "listen": "prerequest", @@ -1263,19 +1467,17 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", " try {\r", - " user = rnd.getUser();\r", - " user.email = rnd.getWord(1) + '@a.ru';\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " await api.findEvent(event.id)\r", + " await api.findEvent(event.id)\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -1283,6 +1485,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -1300,53 +1503,74 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", - " pm.response.to.have.status(201);\r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", - "" + "\r", + "\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('views');\r", + "pm.expect(target).to.have.property('confirmedRequests');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('publishedOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", + "});\r", + "\r", + "pm.test(\"Значение поля views должно увеличится на 1 после выполнения GET запроса с уникального IP к событию\", function () {\r", + " pm.expect(target.views).equal(1);\r", + " \r", + "});" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/events/:id", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "events", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "{{eid}}", + "description": "(Required) id события" + } ] - } + }, + "description": "Обратите внимание:\n- событие должно быть опубликовано\n- информация о событии должна включать в себя количество просмотров и количество подтвержденных запросов\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" }, "response": [] }, { - "name": "Добавление пользователя с email.localpart.length > 64", + "name": "Изменение даты события на уже наступившую", "event": [ { "listen": "prerequest", @@ -1356,19 +1580,21 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", " try {\r", - " user = rnd.getUser();\r", - " user.email = rnd.getWord(65) + '@a.ru';\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " eventDate : \"2020-10-11 23:10:05\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -1376,6 +1602,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -1394,10 +1621,11 @@ "script": { "exec": [ "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", - " pm.response.to.be.badRequest; \r", + " pm.response.to.have.status(400);\r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", + "\r", "" ], "type": "text/javascript" @@ -1405,12 +1633,8 @@ } ], "request": { - "method": "POST", + "method": "PATCH", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" @@ -1418,28 +1642,32 @@ ], "body": { "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{{request_body}}" }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/admin/events/:eventId", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "users" + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } ] - } + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" }, "response": [] }, { - "name": "Добавление пользователя с email.localpart.length == 64", + "name": "Изменение события добавленного текущим пользователем. Изменение даты не неподходящую", "event": [ { "listen": "prerequest", @@ -1449,19 +1677,23 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", " try {\r", - " user = rnd.getUser();\r", - " user.email = rnd.getWord(59) + '@a.ru';\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.collectionVariables.set(\"response\", event);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " eventDate : \"2020-10-11 23:10:05\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -1486,8 +1718,8 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", - " pm.response.to.have.status(201);\r", + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.have.status(400);\r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", @@ -1498,7 +1730,7 @@ } ], "request": { - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", @@ -1519,20 +1751,35 @@ } }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/users/:userId/events/:eventId", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } ] - } + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." }, "response": [] }, { - "name": "Добавление пользователя с domain.part.length > 63", + "name": "Добавление события на неподходящую дату", "event": [ { "listen": "prerequest", @@ -1542,17 +1789,20 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", + " let event;\r", " try {\r", - " user = rnd.getUser();\r", - " user.email = rnd.getWord(1) + '@' + rnd.getWord(64) + '.ru';\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.eventDate = \"2020-12-31 15:10:05\";\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", + " raw: JSON.stringify(event),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -1580,7 +1830,7 @@ "script": { "exec": [ "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", - " pm.response.to.be.badRequest; \r", + " pm.response.to.have.status(400);\r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", @@ -1612,20 +1862,34 @@ } }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] - }, + } + ] + }, + { + "name": "String length restrictions", + "item": [ { - "name": "Добавление пользователя с domain.part.length == 63", + "name": "Добавление нового события с description.length < 20", "event": [ { "listen": "prerequest", @@ -1635,17 +1899,20 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", + " let event;\r", " try {\r", - " user = rnd.getUser();\r", - " user.email = rnd.getWord(1) + '@' + rnd.getWord(60) + '.ru';\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.description = rnd.getWord(19);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", + " raw: JSON.stringify(event),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -1672,8 +1939,8 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", - " pm.response.to.have.status(201);\r", + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", @@ -1705,20 +1972,29 @@ } }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] }, { - "name": "Добавление пользователя с email.length > 254", + "name": "Добавление нового события с description.length < 20", "event": [ { "listen": "prerequest", @@ -1728,17 +2004,20 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", + " let event;\r", " try {\r", - " user = rnd.getUser();\r", - " user.email = rnd.getWord(1) + '@' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(61);\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.description = rnd.getWord(19);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", + " raw: JSON.stringify(event),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -1798,20 +2077,29 @@ } }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] }, { - "name": "Добавление пользователя с email.length == 254", + "name": "Добавление нового события с annotation.length < 20", "event": [ { "listen": "prerequest", @@ -1821,17 +2109,20 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", + " let event;\r", " try {\r", - " user = rnd.getUser();\r", - " user.email = rnd.getWord(1) + '@' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(60);\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.annotation = rnd.getWord(19);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", + " raw: JSON.stringify(event),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -1858,8 +2149,8 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", - " pm.response.to.have.status(201);\r", + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", @@ -1891,25 +2182,29 @@ } }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] - } - ] - }, - { - "name": "Default values check", - "item": [ + }, { - "name": "Проверка на значения по-умолчанию from и size(user)", + "name": "Добавление нового события с annotation.length > 2000", "event": [ { "listen": "prerequest", @@ -1919,18 +2214,22 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", + " let event;\r", " try {\r", - " for (let i = 0; i < 11; i++){\r", - " await api.addUser(rnd.getUser());\r", - " }\r", - " await pm.sendRequest({\r", - " url : \"http://localhost:8080/admin/users?from=0\",\r", - " method : \"GET\",\r", - " header: { \"Content-Type\": \"application/json\" }\r", - " }, (error, response) => {pm.collectionVariables.set('source', response.json())});\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.annotation = rnd.getWord(2001);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -1955,22 +2254,11 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", - " pm.response.to.be.ok; \r", + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", - "\r", - "const target = pm.response.json();\r", - "const source = pm.collectionVariables.get('source');\r", - "\r", - "pm.test(\"Значение from по-умолчанию должно быть равным 0\", function () {\r", - " pm.expect(target[0].id).to.be.equal(source[0].id, 'Запросы с from=0 и без него должны начинаться с одного и того же события');\r", - "});\r", - "\r", - "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", - " pm.expect(target.length).to.be.equal(10);\r", - "});\r", "" ], "type": "text/javascript" @@ -1978,133 +2266,50 @@ } ], "request": { - "method": "GET", + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events" ], - "query": [ + "variable": [ { - "key": "ids", + "key": "userId", "value": "{{uid}}", - "description": "id пользователей", - "disabled": true - }, - { - "key": "ids", - "value": "-10833646", - "description": "id пользователей", - "disabled": true - }, - { - "key": "from", - "value": "0", - "description": "количество элементов, которые нужно пропустить для формирования текущего набора", - "disabled": true - }, - { - "key": "size", - "value": "10", - "description": "количество элементов в наборе", - "disabled": true + "description": "(Required) id текущего пользователя" } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] - } - ] - } - ] - }, - { - "name": "Category", - "item": [ - { - "name": "Required query params", - "item": [] - }, - { - "name": "Unrequired params in body", - "item": [ + }, { - "name": "Получение категорий без нескольких Query params", - "event": [ - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - }, - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", - " pm.response.to.be.ok; \r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/categories", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "categories" - ], - "query": [ - { - "key": "from", - "value": "0", - "description": "количество категорий, которые нужно пропустить для формирования текущего набора", - "disabled": true - }, - { - "key": "size", - "value": "10000", - "description": "количество категорий в наборе", - "disabled": true - } - ] - } - }, - "response": [] - } - ] - }, - { - "name": "Required params in body", - "item": [ - { - "name": "Добавление категории с именем, состоящим из пробелов", + "name": "Добавление нового события с description.length == 20 && annotation.length == 20 && title.length == 3", "event": [ { "listen": "prerequest", @@ -2114,16 +2319,22 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let category;\r", + " let event;\r", " try {\r", - " category = {name: ' '};\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.description = rnd.getWord(20);\r", + " event.annotation = rnd.getWord(20);\r", + " event.title = rnd.getWord(3);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", + " raw: JSON.stringify(event),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -2150,8 +2361,8 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", - " pm.response.to.be.badRequest; \r", + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", @@ -2163,26 +2374,49 @@ ], "request": { "method": "POST", - "header": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], "body": { "mode": "raw", - "raw": "" + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } }, "url": { - "raw": "{{baseUrl}}/admin/categories", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "categories" + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] }, { - "name": "Добавление категории с пустым полем name", + "name": "Добавление нового события с description.length == 7000 && annotation.length == 2000 && title.length == 120", "event": [ { "listen": "prerequest", @@ -2192,16 +2426,22 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let category;\r", + " let event;\r", " try {\r", - " category = {name: ''};\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.description = rnd.getWord(7000);\r", + " event.annotation = rnd.getWord(2000);\r", + " event.title = rnd.getWord(120);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", + " raw: JSON.stringify(event),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -2228,8 +2468,8 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", - " pm.response.to.be.badRequest; \r", + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", @@ -2241,26 +2481,49 @@ ], "request": { "method": "POST", - "header": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], "body": { "mode": "raw", - "raw": "" + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } }, "url": { - "raw": "{{baseUrl}}/admin/categories", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "categories" + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] }, { - "name": "Добавление категории без поля name", + "name": "Добавление нового события с title.length < 3", "event": [ { "listen": "prerequest", @@ -2270,16 +2533,20 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let category;\r", + " let event;\r", " try {\r", - " category = {};\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.title = rnd.getWord(2);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", + " raw: JSON.stringify(event),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -2319,31 +2586,49 @@ ], "request": { "method": "POST", - "header": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], "body": { "mode": "raw", - "raw": "" + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } }, "url": { - "raw": "{{baseUrl}}/admin/categories", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "categories" + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] - } - ] - }, - { - "name": "Misc tests", - "item": [ + }, { - "name": "Изменение категории с неизменными данными", + "name": "Добавление нового события с title.length > 120", "event": [ { "listen": "prerequest", @@ -2352,17 +2637,21 @@ "const main = async () => {\r", " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", - " let category, categoryObj\r", + "\r", + " let event;\r", " try {\r", - " category = rnd.getCategory();\r", - " categoryObj = await api.addCategory(category);\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.title = rnd.getWord(121);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - " pm.collectionVariables.set(\"catid\", Number(categoryObj.id))\r", + "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", + " raw: JSON.stringify(event),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -2389,63 +2678,62 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", - " pm.response.to.be.ok; \r", + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", - "\r", - "const source = JSON.parse(pm.request.body.raw);\r", - "const target = pm.response.json();\r", - "\r", - "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", - "pm.expect(target).to.have.property('id');\r", - "pm.expect(target).to.have.property('name');\r", - "});\r", - "\r", - "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(target.id).to.not.be.null;\r", - " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", - "});" + "" ], "type": "text/javascript" } } ], "request": { - "method": "PATCH", - "header": [], + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], "body": { "mode": "raw", - "raw": "{{request_body}}" + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } }, "url": { - "raw": "{{baseUrl}}/admin/categories/:catId", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "categories", - ":catId" + "users", + ":userId", + "events" ], "variable": [ { - "key": "catId", - "value": "{{catid}}" + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] - } - ] - }, - { - "name": "String length restrictions", - "item": [ + }, { - "name": "Добавление новой категории с name.length > 50", + "name": "Изменение заголовка события с title.length < 3 (admin endpoint)", "event": [ { "listen": "prerequest", @@ -2455,18 +2743,21 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let category;\r", " try {\r", - " category = {'name': rnd.getWord(51)};\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.title = rnd.getWord(2);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -2474,6 +2765,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -2503,12 +2795,8 @@ } ], "request": { - "method": "POST", + "method": "PATCH", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" @@ -2516,29 +2804,32 @@ ], "body": { "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{{request_body}}" }, "url": { - "raw": "{{baseUrl}}/admin/categories", + "raw": "{{baseUrl}}/admin/events/:eventId", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "categories" + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } ] }, - "description": "Обратите внимание: имя категории должно быть уникальным" + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" }, "response": [] }, { - "name": "Добавление новой категории с name.length == 50", + "name": "Изменение заголовка события с title.length > 120 (admin endpoint)", "event": [ { "listen": "prerequest", @@ -2548,18 +2839,21 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let category;\r", " try {\r", - " category = {'name': rnd.getWord(50)};\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.title = rnd.getWord(121);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -2567,6 +2861,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -2584,8 +2879,8 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", - " pm.response.to.have.status(201);\r", + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", @@ -2596,12 +2891,8 @@ } ], "request": { - "method": "POST", + "method": "PATCH", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" @@ -2609,29 +2900,32 @@ ], "body": { "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{{request_body}}" }, "url": { - "raw": "{{baseUrl}}/admin/categories", + "raw": "{{baseUrl}}/admin/events/:eventId", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "categories" + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } ] }, - "description": "Обратите внимание: имя категории должно быть уникальным" + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" }, "response": [] }, { - "name": "Изменение имени категории с name.length > 50", + "name": "Изменение описания события с description.length < 20 (admin endpoint)", "event": [ { "listen": "prerequest", @@ -2640,20 +2934,22 @@ "const main = async () => {\r", " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", - " let category\r", + "\r", " try {\r", - " category = await api.addCategory(rnd.getCategory());\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.description = rnd.getWord(19);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - " pm.collectionVariables.set(\"catid\", Number(category.id))\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify({\r", - " name : rnd.getWord(51)\r", - " }),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -2661,6 +2957,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -2691,33 +2988,40 @@ ], "request": { "method": "PATCH", - "header": [], + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], "body": { "mode": "raw", "raw": "{{request_body}}" }, "url": { - "raw": "{{baseUrl}}/admin/categories/:catId", + "raw": "{{baseUrl}}/admin/events/:eventId", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "categories", - ":catId" + "events", + ":eventId" ], "variable": [ { - "key": "catId", - "value": "{{catid}}" + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" } ] - } + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" }, "response": [] }, { - "name": "Изменение имени категории с name.length == 50", + "name": "Изменение описания события с description.length > 7000 (admin endpoint)", "event": [ { "listen": "prerequest", @@ -2726,20 +3030,22 @@ "const main = async () => {\r", " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", - " let category\r", + "\r", " try {\r", - " category = await api.addCategory(rnd.getCategory());\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.description = rnd.getWord(7001);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - " pm.collectionVariables.set(\"catid\", Number(category.id))\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify({\r", - " name : rnd.getWord(50)\r", - " }),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -2747,6 +3053,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -2764,8 +3071,8 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", - " pm.response.to.have.status(200);\r", + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", @@ -2777,38 +3084,40 @@ ], "request": { "method": "PATCH", - "header": [], + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], "body": { "mode": "raw", "raw": "{{request_body}}" }, "url": { - "raw": "{{baseUrl}}/admin/categories/:catId", + "raw": "{{baseUrl}}/admin/events/:eventId", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "categories", - ":catId" + "events", + ":eventId" ], "variable": [ { - "key": "catId", - "value": "{{catid}}" + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" } ] - } + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" }, "response": [] - } - ] - }, - { - "name": "Default values check", - "item": [ + }, { - "name": "Проверка на значения по-умолчанию from и size(category)", + "name": "Изменение аннотации события с annotation.length < 20 (admin endpoint)", "event": [ { "listen": "prerequest", @@ -2819,14 +3128,17 @@ " const rnd = new RandomUtils();\r", "\r", " try {\r", - " for (let i = 0; i < 11; i++){\r", - " await api.addCategory(rnd.getCategory());\r", - " }\r", - " await pm.sendRequest({\r", - " url : \"http://localhost:8080/categories?from=0\",\r", - " method : \"GET\",\r", - " header: { \"Content-Type\": \"application/json\" }\r", - " }, (error, response) => {pm.collectionVariables.set('source', response.json())});\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.annotation = rnd.getWord(19);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -2837,6 +3149,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -2854,21 +3167,106 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", - " pm.response.to.be.ok; \r", + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Изменение аннотации события с annotation.length > 2000 (admin endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", "\r", - "const target = pm.response.json();\r", - "const source = pm.collectionVariables.get('source');\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.annotation = rnd.getWord(2001);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", "\r", - "pm.test(\"Значение from по-умолчанию должно быть равным 0\", function () {\r", - " pm.expect(target[0].id).to.be.equal(source[0].id, 'Запросы с from=0 и без него должны начинаться с одного и того же события');\r", - "});\r", + "const interval = setInterval(() => {}, 1000);\r", "\r", - "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", - " pm.expect(target.length).to.be.equal(10);\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", "});\r", "" ], @@ -2877,71 +3275,5598 @@ } ], "request": { - "method": "GET", + "method": "PATCH", "header": [ { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, "url": { - "raw": "{{baseUrl}}/categories", + "raw": "{{baseUrl}}/admin/events/:eventId", "host": [ "{{baseUrl}}" ], "path": [ - "categories" + "admin", + "events", + ":eventId" ], - "query": [ - { - "key": "from", - "value": "0", - "description": "количество категорий, которые нужно пропустить для формирования текущего набора", - "disabled": true - }, + "variable": [ { - "key": "size", - "value": "1000", - "description": "количество категорий в наборе", - "disabled": true + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" } ] - } + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" }, "response": [] - } - ] - } - ] - } - ] - }, - { - "name": "409 Conflict", - "item": [ - { - "name": "Попытка изменения имени категории на уже существующее", - "event": [ - { - "listen": "prerequest", - "script": { - "exec": [ - "const main = async () => {\r", - " const api = new API(pm);\r", - " const rnd = new RandomUtils();\r", - " let category1, category2\r", - " try {\r", - " category1 = await api.addCategory(rnd.getCategory());\r", - " category2 = await api.addCategory(rnd.getCategory());\r", - " } catch(err) {\r", - " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", - " }\r", - " pm.collectionVariables.set(\"catid\", category2.id)\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify({\r", - " name : category1.name\r", - " }),\r", + }, + { + "name": "Изменение события с description.length == 20 && annotation.length == 20 && title.length == 3 (admin endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.annotation = rnd.getWord(20);\r", + " event2.description = rnd.getWord(20);\r", + " event2.title = rnd.getWord(3);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.have.status(200);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Изменение события с description.length == 7000 && annotation.length == 2000 && title.length == 120 (admin endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.annotation = rnd.getWord(2000);\r", + " event2.description = rnd.getWord(7000);\r", + " event2.title = rnd.getWord(120);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.have.status(200);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Изменение заголовка события с title.length < 3 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " title: rnd.getWord(2)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Изменение заголовка события с title.length > 120 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " title: rnd.getWord(121)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Изменение описания события с description.length < 20 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " description: rnd.getWord(19)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Изменение описания события с description.length > 7000 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " description: rnd.getWord(7001)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Изменение аннотации события с annotation.length < 20 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " annotation: rnd.getWord(19)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Изменение аннотации события с annotation.length > 2000 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " annotation: rnd.getWord(2001)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Изменение события с description.length == 20 && annotation.length == 20 && title.length == 3 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " description: rnd.getWord(20),\r", + " annotation: rnd.getWord(20),\r", + " title: rnd.getWord(3)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.have.status(200);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Изменение события с description.length == 7000 && annotation.length == 2000 && title.length == 120 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " description: rnd.getWord(7000),\r", + " annotation: rnd.getWord(2000),\r", + " title: rnd.getWord(120)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.have.status(200);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + } + ] + }, + { + "name": "Default values check", + "item": [ + { + "name": "Добавление нового события без paid, participantLimit, requestModeration", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let usersArr = Array.from({length: 10}, () => rnd.getUser());\r", + " let categoriesArr = Array.from({length: 10}, () => rnd.getCategory());\r", + " let usersResponseArr = [], categoriesResponseArr = [], eventArr, eventResponseArr = [];\r", + " try {\r", + " for (const u of usersArr){\r", + " usersResponseArr.push(await api.addUser(u));\r", + " }\r", + " for (const c of categoriesArr){\r", + " categoriesResponseArr.push(await api.addCategory(c));\r", + " }\r", + " eventArr = Array.from(categoriesResponseArr, (x) => rnd.getEvent(x.id));\r", + " for (let i = 0; i < 10; i++){\r", + " delete eventArr[i].requestModeration;\r", + " delete eventArr[i].paid;\r", + " delete eventArr[i].participantLimit;\r", + " }\r", + " for (let i = 0; i < 10; i++){\r", + " eventResponseArr.push(await api.addEvent(usersResponseArr[i].id, eventArr[i]));\r", + " }\r", + " pm.collectionVariables.set('responseArr', eventResponseArr)\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201); \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('responseArr');\r", + "\r", + "\r", + "pm.test(\"У каждого созданного события paid должно принять значение по умолчанию(false)\", function () {\r", + " source.forEach(function(x){pm.expect(x.paid).to.be.equal(false)});\r", + "});\r", + "\r", + "pm.test(\"У каждого созданного события participantLimit должен принять значение по умолчанию(0)\", function () {\r", + " source.forEach(function(x){pm.expect(x.participantLimit).to.be.equal(0)});\r", + "});\r", + "\r", + "pm.test(\"У каждого созданного события requestModeration должно принять значение по умолчанию(true)\", function () {\r", + " source.forEach(function(x){pm.expect(x.requestModeration).to.be.equal(true)});\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Проверка на значения по-умолчанию from и size(event)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user, category, eventArr;\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " category = await api.addCategory(rnd.getCategory());\r", + " eventArr = Array.from({length:11}, () => rnd.getEvent(category.id));\r", + " for (let i = 0; i < 11; i++){\r", + " await api.addEvent(user.id, eventArr[i]);\r", + " }\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/admin/events?from=0\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {pm.collectionVariables.set('source', response.json())});\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('source');\r", + "\r", + "pm.test(\"Значение from по-умолчанию должно быть равным 0\", function () {\r", + " pm.expect(target[0].id).to.be.equal(source[0].id, 'Запросы с from=0 и без него должны начинаться с одного и того же события');\r", + "});\r", + "\r", + "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", + " pm.expect(target.length).to.be.equal(10);\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events" + ], + "query": [ + { + "key": "users", + "value": "0", + "description": "список id пользователей, чьи события нужно найти", + "disabled": true + }, + { + "key": "states", + "value": "PUBLISHED", + "description": "список состояний в которых находятся искомые события", + "disabled": true + }, + { + "key": "categories", + "value": "0", + "description": "список id категорий в которых будет вестись поиск", + "disabled": true + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие", + "disabled": true + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе", + "disabled": true + } + ] + }, + "description": "Эндпоинт возвращает полную информацию обо всех событиях подходящих под переданные условия" + }, + "response": [] + }, + { + "name": "Получение событий, добавленных текущим пользователем. Проверка на значения по-умолчанию size и from", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let eventArr, user, category, eventResponseArr = [];\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " category = await api.addCategory(rnd.getCategory());\r", + " eventArr = Array.from({length:11}, () => rnd.getEvent(category.id));\r", + " for (let i = 0; i < 11; i++){\r", + " eventResponseArr.push(await api.addEvent(user.id, eventArr[i]));\r", + " }\r", + " pm.collectionVariables.set('responseArr', eventResponseArr)\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/users/\" + user.id + \"/events?from=0\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {pm.collectionVariables.set('source', response.json())});\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('responseArr');\r", + "newSourceArr = Array.from(source, (x) => x.id);\r", + "const responseWithFrom = pm.collectionVariables.get('source');\r", + "\r", + "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", + " pm.expect(target.length).to.equal(10);\r", + "});\r", + "\r", + "pm.test(\"Значение from по-умолчанию должно быть равным 0\", function () {\r", + " pm.expect(target[0].id).to.be.equal(responseWithFrom[0].id, 'Запросы с from=0 и без него должны начинаться с одного и того же события');\r", + "});\r", + "\r", + "pm.test(\"Все найденные события должны быть в списке добавленных\", function () {\r", + " source.forEach(function(x){pm.expect(newSourceArr).to.include(x.id)});\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество элементов в наборе", + "disabled": true + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + } + }, + "response": [] + }, + { + "name": "Получение событий с возможностью фильтрации. Проверка на значение по-умолчанию size", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user, category, eventArr, eventResponseArr = [], publishEventResponseArr = [];\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " category = await api.addCategory(rnd.getCategory());\r", + " eventArr = Array.from({length:11}, () => rnd.getEvent(category.id));\r", + " for (let i = 0; i < 11; i++){\r", + " eventResponseArr.push(await api.addEvent(user.id, eventArr[i]));\r", + " }\r", + " for (let i = 0; i < 11; i++){\r", + " publishEventResponseArr.push(await api.publishEvent(eventResponseArr[i].id));\r", + " }\r", + " pm.collectionVariables.set('responseArr', eventResponseArr);\r", + " pm.collectionVariables.set('catid', category.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('responseArr');\r", + "newSourceArr = Array.from(source, (x) => x.id);\r", + "\r", + "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", + " pm.expect(target.length).to.equal(10);\r", + "});\r", + "\r", + "pm.test(\"Все найденные события должны быть в списке добавленных\", function () {\r", + " source.forEach(function(x){pm.expect(newSourceArr).to.include(x.id)});\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/events?categories={{catid}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "events" + ], + "query": [ + { + "key": "text", + "value": "0", + "description": "текст для поиска в содержимом аннотации и подробном описании события", + "disabled": true + }, + { + "key": "categories", + "value": "{{catid}}", + "description": "список идентификаторов категорий в которых будет вестись поиск" + }, + { + "key": "paid", + "value": "true", + "description": "поиск только платных/бесплатных событий", + "disabled": true + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие", + "disabled": true + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие", + "disabled": true + }, + { + "key": "onlyAvailable", + "value": "false", + "description": "только события у которых не исчерпан лимит запросов на участие", + "disabled": true + }, + { + "key": "sort", + "value": "EVENT_DATE", + "description": "Вариант сортировки: по дате события или по количеству просмотров", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе", + "disabled": true + } + ] + }, + "description": "Обратите внимание: \n- это публичный эндпоинт, соответственно в выдаче должны быть только опубликованные события\n- текстовый поиск (по аннотации и подробному описанию) должен быть без учета регистра букв\n- если в запросе не указан диапазон дат [rangeStart-rangeEnd], то нужно выгружать события, которые произойдут позже текущей даты и времени\n- информация о каждом событии должна включать в себя количество просмотров и количество уже одобренных заявок на участие\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" + }, + "response": [] + } + ] + } + ] + }, + { + "name": "Users", + "item": [ + { + "name": "Required query params", + "item": [] + }, + { + "name": "Unrequired query params", + "item": [ + { + "name": "Поиск пользователей без нескольких Query params", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/users?ids={{uid}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ], + "query": [ + { + "key": "ids", + "value": "{{uid}}", + "description": "id пользователей" + }, + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "10", + "description": "количество элементов в наборе", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Поиск пользователей без параметра ids", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " let user1 = rnd.getUser();\r", + " user1 = await api.addUser(user1);\r", + "\r", + " let user2 = rnd.getUser()\r", + " user2 = await api.addUser(user2);\r", + " //pm.collectionVariables.set('fromId', user1.id);\r", + " pm.collectionVariables.set('source1', user1);\r", + " pm.collectionVariables.set('source2', user2);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source1 = pm.collectionVariables.get('source1');\r", + "const source2 = pm.collectionVariables.get('source2');\r", + "\r", + "pm.test(\"Пользователи должны содержать поля: id, name, email\", function () {\r", + " pm.expect(target[target.length-2]).to.have.property('id');\r", + " pm.expect(target[target.length-2]).to.have.property('name');\r", + " pm.expect(target[target.length-2]).to.have.property('email');\r", + " pm.expect(target[target.length-1]).to.have.property('id');\r", + " pm.expect(target[target.length-1]).to.have.property('name');\r", + " pm.expect(target[target.length-1]).to.have.property('email');\r", + "});\r", + "\r", + "pm.test(\"Данные последних двух пользователей должны совпадать с данными добавленных пользователей\", function () {\r", + " pm.expect(target[target.length-2].id).to.equal(source1.id);\r", + " pm.expect(target[target.length-2].name).to.equal(source1.name);\r", + " pm.expect(target[target.length-2].email).to.equal(source1.email);\r", + " pm.expect(target[target.length-1].id).to.equal(source2.id);\r", + " pm.expect(target[target.length-1].name).to.equal(source2.name);\r", + " pm.expect(target[target.length-1].email).to.equal(source2.email);\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/users?from={{fromId}}&size=100000", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ], + "query": [ + { + "key": "ids", + "value": "{{uid}}", + "description": "id пользователей", + "disabled": true + }, + { + "key": "from", + "value": "{{fromId}}", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "100000", + "description": "количество элементов в наборе" + } + ] + } + }, + "response": [] + }, + { + "name": "Поиск событий без нескольких Query params", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.request.removeQueryParams(['users', 'categories']);\r", + " pm.request.addQueryParams([`users=` + user.id, 'categories=' + category.id]);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events" + ], + "query": [ + { + "key": "users", + "value": "0", + "description": "список id пользователей, чьи события нужно найти", + "disabled": true + }, + { + "key": "states", + "value": "PUBLISHED", + "description": "список состояний в которых находятся искомые события", + "disabled": true + }, + { + "key": "categories", + "value": "0", + "description": "список id категорий в которых будет вестись поиск", + "disabled": true + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие", + "disabled": true + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе", + "disabled": true + } + ] + }, + "description": "Эндпоинт возвращает полную информацию обо всех событиях подходящих под переданные условия" + }, + "response": [] + } + ] + }, + { + "name": "Required params in body", + "item": [ + { + "name": "Добавление пользователя с электронной почтой, состоящей только из пробелов", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = \" \";\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с пустой электронной почтой", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = \"\";\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя без поля email", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " delete user.email;\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с пустым именем", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = \"\";\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с именем, состоящим только из пробелов", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = \" \";\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя без поля name", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " delete user.name;\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Misc tests", + "item": [] + }, + { + "name": "String length restrictions", + "item": [ + { + "name": "Добавление пользователя с name.length < 2", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(1);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с name.length == 2", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(2);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с name.length > 250", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(251);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с name.length == 250", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(250);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.length < 6", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@a.r';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.length == 6", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@a.ru';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.localpart.length > 64", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(65) + '@a.ru';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.localpart.length == 64", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(59) + '@a.ru';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с domain.part.length > 63", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@' + rnd.getWord(64) + '.ru';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с domain.part.length == 63", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@' + rnd.getWord(60) + '.ru';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.length > 254", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(61);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.length == 254", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(60);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Default values check", + "item": [ + { + "name": "Проверка на значения по-умолчанию from и size(user)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " for (let i = 0; i < 11; i++){\r", + " await api.addUser(rnd.getUser());\r", + " }\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/admin/users?from=0\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {pm.collectionVariables.set('source', response.json())});\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('source');\r", + "\r", + "pm.test(\"Значение from по-умолчанию должно быть равным 0\", function () {\r", + " pm.expect(target[0].id).to.be.equal(source[0].id, 'Запросы с from=0 и без него должны начинаться с одного и того же события');\r", + "});\r", + "\r", + "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", + " pm.expect(target.length).to.be.equal(10);\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ], + "query": [ + { + "key": "ids", + "value": "{{uid}}", + "description": "id пользователей", + "disabled": true + }, + { + "key": "ids", + "value": "-10833646", + "description": "id пользователей", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "10", + "description": "количество элементов в наборе", + "disabled": true + } + ] + } + }, + "response": [] + } + ] + } + ] + }, + { + "name": "Category", + "item": [ + { + "name": "Required query params", + "item": [] + }, + { + "name": "Unrequired params in body", + "item": [ + { + "name": "Получение категорий без нескольких Query params", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "categories" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество категорий, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "10000", + "description": "количество категорий в наборе", + "disabled": true + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Required params in body", + "item": [ + { + "name": "Добавление категории с именем, состоящим из пробелов", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {name: ' '};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + } + }, + "response": [] + }, + { + "name": "Добавление категории с пустым полем name", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {name: ''};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + } + }, + "response": [] + }, + { + "name": "Добавление категории без поля name", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Misc tests", + "item": [ + { + "name": "Изменение категории с неизменными данными", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category, categoryObj\r", + " try {\r", + " category = rnd.getCategory();\r", + " categoryObj = await api.addCategory(category);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(categoryObj.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "String length restrictions", + "item": [ + { + "name": "Добавление новой категории с name.length > 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {'name': rnd.getWord(51)};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Добавление новой категории с name.length == 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {'name': rnd.getWord(50)};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Изменение имени категории с name.length > 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category\r", + " try {\r", + " category = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(category.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : rnd.getWord(51)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Изменение имени категории с name.length == 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category\r", + " try {\r", + " category = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(category.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : rnd.getWord(50)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.have.status(200);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Default values check", + "item": [ + { + "name": "Проверка на значения по-умолчанию from и size(category)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " for (let i = 0; i < 11; i++){\r", + " await api.addCategory(rnd.getCategory());\r", + " }\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/categories?from=0\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {pm.collectionVariables.set('source', response.json())});\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('source');\r", + "\r", + "pm.test(\"Значение from по-умолчанию должно быть равным 0\", function () {\r", + " pm.expect(target[0].id).to.be.equal(source[0].id, 'Запросы с from=0 и без него должны начинаться с одного и того же события');\r", + "});\r", + "\r", + "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", + " pm.expect(target.length).to.be.equal(10);\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "categories" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество категорий, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество категорий в наборе", + "disabled": true + } + ] + } + }, + "response": [] + } + ] + } + ] + } + ] + }, + { + "name": "409 Conflict", + "item": [ + { + "name": "Попытка изменения имени категории на уже существующее", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category1, category2\r", + " try {\r", + " category1 = await api.addCategory(rnd.getCategory());\r", + " category2 = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", category2.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : category1.name\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Добавление новой категории с занятым именем", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = rnd.getCategory();\r", + " await api.addCategory(category);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Добавление пользователя с занятым именем почты", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(10);\r", + " await api.addUser(user);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Удаление категории с привязанными событиями", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const user = await api.addUser(rnd.getUser());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set('catid', category.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + }, + "description": "Обратите внимание: с категорий не должно быть связано ни одного события." + }, + "response": [] + }, + { + "name": "Изменение имени категории на уже занятое", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category1, category2\r", + " try {\r", + " category1 = await api.addCategory(rnd.getCategory());\r", + " category2 = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(category1.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : category2.name\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Публикация уже опубликованного события", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " stateAction : \"PUBLISH_EVENT\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Публикация отмененного события", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.rejectEvent(event.id);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " stateAction : \"PUBLISH_EVENT\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Отмена опубликованного события", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " stateAction : \"REJECT_EVENT\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Изменение опубликованного события от имени пользователя", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.collectionVariables.set(\"response\", event);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " eventDate : rnd.getFutureDateTime(6)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + } + ] + }, + { + "name": "Category", + "item": [ + { + "name": "Добавление новой категории", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = rnd.getCategory();\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -2968,37 +8893,330 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", - " pm.response.to.have.status(409);\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});" + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Получение категорий", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " pm.collectionVariables.set(\"response\", category)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json();\r", + "let founded;\r", + "target.forEach(function(element){if (element.id == source.id) founded = element});\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target[0]).to.have.property('id');\r", + "pm.expect(target[0]).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.id).equal(founded.id, 'Идентификатор категории должен соответствовать идентификатору категории добавленной ранее');\r", + " pm.expect(source.name).equal(founded.name, 'Название категории должно соответствовать названию категории добавленной ранее');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/categories?from=0&size=1000", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "categories" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество категорий, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество категорий в наборе" + } + ] + } + }, + "response": [] + }, + { + "name": "Получение информации о категории по её идентификатору", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " pm.collectionVariables.set(\"response\", category)\r", + " pm.collectionVariables.set(\"catid\", category.id)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор категории должен соответствовать идентификатору в запросе');\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно соответствовать названию категории с указанным идентификатором');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}", + "description": "(Required) id категории" + } + ] + } + }, + "response": [] + }, + { + "name": "Удаление категории", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const findedCategory = await api.findCategory(category.id);\r", + " pm.collectionVariables.set(\"catid\", category.id)\r", + " pm.collectionVariables.set(\"response\", findedCategory)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 204\", function () {\r", + " pm.response.to.have.status(204);\r", + "});\r", + "\r", + "source = pm.collectionVariables.get('response');\r", + "catId = pm.collectionVariables.get('catid');\r", + "\r", + "pm.test(\"Категория должна быть найдена до удаления\", function () {\r", + " pm.expect(source.id).equal(catId, 'Идентификтор категории должен совпадать с удаляемым');\r", + "});\r", + "\r", + "pm.sendRequest({\r", + " url: pm.collectionVariables.get(\"baseUrl\") + \"/categories/\" + catId,\r", + " method: 'GET',\r", + " }, (error, response) => {\r", + " pm.test(\"Категория не должна быть найдена после удаления\", function () {\r", + " pm.expect(response.code).to.eql(404);\r", + " });\r", + " });" ], "type": "text/javascript" } } ], "request": { - "method": "PATCH", + "method": "DELETE", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { "raw": "{{baseUrl}}/admin/categories/:catId", "host": [ @@ -3016,12 +9234,12 @@ } ] }, - "description": "Обратите внимание: имя категории должно быть уникальным" + "description": "Обратите внимание: с категорий не должно быть связано ни одного события." }, "response": [] }, { - "name": "Добавление новой категории с занятым именем", + "name": "Изменение категории", "event": [ { "listen": "prerequest", @@ -3030,18 +9248,18 @@ "const main = async () => {\r", " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", - "\r", - " let category;\r", + " let category\r", " try {\r", - " category = rnd.getCategory();\r", - " await api.addCategory(category);\r", + " category = await api.addCategory(rnd.getCategory());\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", + " pm.collectionVariables.set(\"catid\", Number(category.id))\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", + " raw: JSON.stringify({\r", + " name : rnd.getCategory().name\r", + " }),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -3068,10 +9286,23 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", - " pm.response.to.have.status(409);\r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", "});" ], "type": "text/javascript" @@ -3079,42 +9310,153 @@ } ], "request": { - "method": "POST", + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Users", + "item": [ + { + "name": "Поиск пользователей", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Пользователи должны содержать поля: id, name, email\", function () {\r", + " pm.expect(target[0]).to.have.property('id');\r", + " pm.expect(target[0]).to.have.property('name');\r", + " pm.expect(target[0]).to.have.property('email');\r", + "});\r", + "\r", + "pm.test(\"Должен быть найден только один пользователь по заданному фильтру\", function () {\r", + " pm.expect(target.length).to.eql(1);\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target[0].id).equal(pm.collectionVariables.get(\"uid\"));\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "{{baseUrl}}/admin/categories", + "raw": "{{baseUrl}}/admin/users?ids={{uid}}", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "categories" + "users" + ], + "query": [ + { + "key": "ids", + "value": "{{uid}}", + "description": "id пользователей" + }, + { + "key": "ids", + "value": "-10833646", + "description": "id пользователей", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "10", + "description": "количество элементов в наборе", + "disabled": true + } ] - }, - "description": "Обратите внимание: имя категории должно быть уникальным" + } }, "response": [] }, { - "name": "Добавление пользователя с занятым именем почты", + "name": "Добавление нового пользователя", "event": [ { "listen": "prerequest", @@ -3127,8 +9469,6 @@ " let user;\r", " try {\r", " user = rnd.getUser();\r", - " user.name = rnd.getWord(10);\r", - " await api.addUser(user);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -3162,10 +9502,25 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", - " pm.response.to.have.status(409);\r", + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Пользователь должен содержать поля: id, name, email\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "pm.expect(target).to.have.property('email');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Имя пользователя должно соответствовать отправленному в запросе');\r", + " pm.expect(source.email).equal(target.email, 'Почта пользователя должна соответствовать отправленной в запросе');\r", "});" ], "type": "text/javascript" @@ -3207,8 +9562,38 @@ "response": [] }, { - "name": "Изменение имени категории на уже занятое", + "name": "Удаление пользователя", "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 204\", function () {\r", + " pm.response.to.have.status(204);\r", + "});\r", + "const source = pm.collectionVariables.get('response');\r", + "const userId = pm.collectionVariables.get('uid');\r", + "\r", + "pm.test(\"Пользователь должен быть найден до выполнения запроса\", function(){\r", + " pm.expect(source.length).to.eql(1);\r", + " pm.expect(source[0].id).to.eql(userId);\r", + "});\r", + "let body\r", + "const req = {\r", + " url: \"http://localhost:8080/admin/users?ids=\" + pm.collectionVariables.get(\"uid\"),\r", + " method: \"GET\",\r", + " body: body == null ? \"\" : JSON.stringify(body),\r", + " header: { \"Content-Type\": \"application/json\" },\r", + " };\r", + "pm.sendRequest(req, (error, response) => {\r", + " pm.test(\"Пользователь должен быть удалён после выполнения запроса\", function(){\r", + " pm.expect(response.json().length).to.eql(0);\r", + " });\r", + "})" + ], + "type": "text/javascript" + } + }, { "listen": "prerequest", "script": { @@ -3216,21 +9601,16 @@ "const main = async () => {\r", " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", - " let category1, category2\r", + "\r", + " let compilation;\r", " try {\r", - " category1 = await api.addCategory(rnd.getCategory());\r", - " category2 = await api.addCategory(rnd.getCategory());\r", + " const user = await api.addUser(rnd.getUser());\r", + " const foundedUser = await api.findUser(user.id);\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"response\", foundedUser)\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - " pm.collectionVariables.set(\"catid\", Number(category1.id))\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify({\r", - " name : category2.name\r", - " }),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -3250,42 +9630,31 @@ ], "type": "text/javascript" } - }, - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", - " pm.response.to.have.status(409);\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});" - ], - "type": "text/javascript" - } } ], "request": { - "method": "PATCH", - "header": [], - "body": { - "mode": "raw", - "raw": "{{request_body}}" - }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], "url": { - "raw": "{{baseUrl}}/admin/categories/:catId", + "raw": "{{baseUrl}}/admin/users/:userId", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "categories", - ":catId" + "users", + ":userId" ], "variable": [ { - "key": "catId", - "value": "{{catid}}" + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id пользователя" } ] } @@ -3295,10 +9664,10 @@ ] }, { - "name": "Users", + "name": "Event", "item": [ { - "name": "Поиск пользователей", + "name": "Добавление нового события", "event": [ { "listen": "prerequest", @@ -3308,13 +9677,21 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let compilation;\r", + " let event;\r", " try {\r", " const user = await api.addUser(rnd.getUser());\r", " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -3339,26 +9716,42 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", - " pm.response.to.be.ok; \r", + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201); \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", "\r", + "const source = JSON.parse(pm.request.body.raw);\r", "const target = pm.response.json();\r", "\r", - "pm.test(\"Пользователи должны содержать поля: id, name, email\", function () {\r", - " pm.expect(target[0]).to.have.property('id');\r", - " pm.expect(target[0]).to.have.property('name');\r", - " pm.expect(target[0]).to.have.property('email');\r", - "});\r", - "\r", - "pm.test(\"Должен быть найден только один пользователь по заданному фильтру\", function () {\r", - " pm.expect(target.length).to.eql(1);\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(target[0].id).equal(pm.collectionVariables.get(\"uid\"));\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(target.title).equal(source.title, 'Название события должно соответствовать названию события в запросе');\r", + " pm.expect(target.annotation).equal(source.annotation, 'Аннотация события должна соответствовать аннотации события в запросе');\r", + " pm.expect(target.paid.toString()).equal(source.paid.toString(), 'Стоимость события должна соответствовать стоимости события в запросе');\r", + " pm.expect(target.eventDate).equal(source.eventDate, 'Дата проведения события должна соответствовать дате проведения события в запросе');\r", + " pm.expect(target.description).equal(source.description, 'Описание события должно соответствовать описание события в запросе');\r", + " pm.expect(target.participantLimit.toString()).equal(source.participantLimit.toString(), 'Лимит участников события должно соответствовать лимиту участников события в запросе');\r", + " pm.expect(target.location.lat.toString()).equal(source.location.lat.toString(), 'Широта локации проведения события должна соответствовать широте локации проведения события в запросе');\r", + " pm.expect(target.location.lon.toString()).equal(source.location.lon.toString(), 'Долгота локации проведения события должна соответствовать долготе локации проведения события в запросе');\r", + " pm.expect(target.requestModeration.toString()).equal(source.requestModeration.toString(), 'Необходимость модерации события должна соответствовать необходимости модерации события в запросе');\r", "});" ], "type": "text/javascript" @@ -3366,53 +9759,50 @@ } ], "request": { - "method": "GET", + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { - "raw": "{{baseUrl}}/admin/users?ids={{uid}}", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events" ], - "query": [ + "variable": [ { - "key": "ids", + "key": "userId", "value": "{{uid}}", - "description": "id пользователей" - }, - { - "key": "ids", - "value": "-10833646", - "description": "id пользователей", - "disabled": true - }, - { - "key": "from", - "value": "0", - "description": "количество элементов, которые нужно пропустить для формирования текущего набора", - "disabled": true - }, - { - "key": "size", - "value": "10", - "description": "количество элементов в наборе", - "disabled": true + "description": "(Required) id текущего пользователя" } ] - } + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] }, { - "name": "Добавление нового пользователя", + "name": "Поиск событий", "event": [ { "listen": "prerequest", @@ -3422,18 +9812,17 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", " try {\r", - " user = rnd.getUser();\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.request.removeQueryParams(['users', 'categories']);\r", + " pm.request.addQueryParams([`users=` + user.id, 'categories=' + category.id]);\r", + " pm.collectionVariables.set('response', event);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -3441,6 +9830,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -3458,25 +9848,42 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", - " pm.response.to.have.status(201);\r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", "\r", - "const source = JSON.parse(pm.request.body.raw);\r", - "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json()[0];\r", "\r", - "pm.test(\"Пользователь должен содержать поля: id, name, email\", function () {\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", "pm.expect(target).to.have.property('id');\r", - "pm.expect(target).to.have.property('name');\r", - "pm.expect(target).to.have.property('email');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('views');\r", + "pm.expect(target).to.have.property('confirmedRequests');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('publishedOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(target.id).to.not.be.null;\r", - " pm.expect(source.name).equal(target.name, 'Имя пользователя должно соответствовать отправленному в запросе');\r", - " pm.expect(source.email).equal(target.email, 'Почта пользователя должна соответствовать отправленной в запросе');\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать искомому событию');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Идентификатор категории должен соответствовать искомой категории');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость посещения события должна соответствовать искомому событию');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать дате искомого события');\r", + " pm.expect(source.description).equal(target.description, 'Описание события должно соответствовать искомому событию');\r", + " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать искомому событию');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Число участников события должно соответствовать искомому событию');\r", "});" ], "type": "text/javascript" @@ -3484,72 +9891,67 @@ } ], "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/admin/events?users=0&states=PUBLISHED&categories=0&rangeStart=2022-01-06%2013%3A30%3A38&rangeEnd=2097-09-06%2013%3A30%3A38&from=0&size=1000", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "users" + "events" + ], + "query": [ + { + "key": "users", + "value": "0", + "description": "список id пользователей, чьи события нужно найти" + }, + { + "key": "states", + "value": "PUBLISHED", + "description": "список состояний в которых находятся искомые события" + }, + { + "key": "categories", + "value": "0", + "description": "список id категорий в которых будет вестись поиск" + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие" + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие" + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе" + } ] - } + }, + "description": "Эндпоинт возвращает полную информацию обо всех событиях подходящих под переданные условия" }, "response": [] }, { - "name": "Удаление пользователя", + "name": "Получение событий, добавленных текущим пользователем", "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Ответ должен содержать код статуса 204\", function () {\r", - " pm.response.to.have.status(204);\r", - "});\r", - "const source = pm.collectionVariables.get('response');\r", - "const userId = pm.collectionVariables.get('uid');\r", - "\r", - "pm.test(\"Пользователь должен быть найден до выполнения запроса\", function(){\r", - " pm.expect(source.length).to.eql(1);\r", - " pm.expect(source[0].id).to.eql(userId);\r", - "});\r", - "let body\r", - "const req = {\r", - " url: \"http://localhost:8080/admin/users?ids=\" + pm.collectionVariables.get(\"uid\"),\r", - " method: \"GET\",\r", - " body: body == null ? \"\" : JSON.stringify(body),\r", - " header: { \"Content-Type\": \"application/json\" },\r", - " };\r", - "pm.sendRequest(req, (error, response) => {\r", - " pm.test(\"Пользователь должен быть удалён после выполнения запроса\", function(){\r", - " pm.expect(response.json().length).to.eql(0);\r", - " });\r", - "})" - ], - "type": "text/javascript" - } - }, { "listen": "prerequest", "script": { @@ -3558,12 +9960,11 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let compilation;\r", " try {\r", - " const user = await api.addUser(rnd.getUser());\r", - " const foundedUser = await api.findUser(user.id);\r", - " pm.collectionVariables.set(\"uid\", user.id);\r", - " pm.collectionVariables.set(\"response\", foundedUser)\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -3586,10 +9987,33 @@ ], "type": "text/javascript" } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json()[0];\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate\", function () {\r", + " pm.expect(target).to.contain.keys('id', 'title', 'annotation', 'category', 'paid', 'eventDate');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + "});" + ], + "type": "text/javascript" + } } ], "request": { - "method": "DELETE", + "method": "GET", "header": [ { "key": "Accept", @@ -3597,33 +10021,40 @@ } ], "url": { - "raw": "{{baseUrl}}/admin/users/:userId", + "raw": "{{baseUrl}}/users/:userId/events?from=0&size=1000", "host": [ "{{baseUrl}}" ], "path": [ - "admin", "users", - ":userId" + ":userId", + "events" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество элементов в наборе" + } ], "variable": [ { "key": "userId", "value": "{{uid}}", - "description": "(Required) id пользователя" + "description": "(Required) id текущего пользователя" } ] } }, "response": [] - } - ] - }, - { - "name": "Category", - "item": [ + }, { - "name": "Добавление новой категории", + "name": "Получение событий с возможностью фильтрации", "event": [ { "listen": "prerequest", @@ -3633,18 +10064,17 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let category;\r", " try {\r", - " category = rnd.getCategory();\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.request.removeQueryParams(['text', 'categories', 'paid']);\r", + " pm.request.addQueryParams([`text=` + event.annotation, 'categories=' + category.id, 'paid=' + event.paid]);\r", + " pm.collectionVariables.set('response', event);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -3652,6 +10082,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -3669,23 +10100,33 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", - " pm.response.to.have.status(201);\r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", "\r", - "const source = JSON.parse(pm.request.body.raw);\r", - "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json()[0];\r", "\r", - "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests\", function () {\r", "pm.expect(target).to.have.property('id');\r", - "pm.expect(target).to.have.property('name');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('views');\r", + "pm.expect(target).to.have.property('confirmedRequests');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(target.id).to.not.be.null;\r", - " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать аннотации события с указанным идентификатором');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Категория события должна соответствовать категории события с указанным идентификатором');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость события должна соответствовать стоимости события с указанным идентификатором');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать дате проведения события с указанным идентификатором');\r", + " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать названию события с указанным идентификатором');\r", "});" ], "type": "text/javascript" @@ -3693,42 +10134,75 @@ } ], "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "{{baseUrl}}/admin/categories", + "raw": "{{baseUrl}}/events?text=0&categories=0&paid=true&rangeStart=2022-01-06%2013%3A30%3A38&rangeEnd=2097-09-06%2013%3A30%3A38&onlyAvailable=false&sort=EVENT_DATE&from=0&size=1000", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "categories" + "events" + ], + "query": [ + { + "key": "text", + "value": "0", + "description": "текст для поиска в содержимом аннотации и подробном описании события" + }, + { + "key": "categories", + "value": "0", + "description": "список идентификаторов категорий в которых будет вестись поиск" + }, + { + "key": "paid", + "value": "true", + "description": "поиск только платных/бесплатных событий" + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие" + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие" + }, + { + "key": "onlyAvailable", + "value": "false", + "description": "только события у которых не исчерпан лимит запросов на участие" + }, + { + "key": "sort", + "value": "EVENT_DATE", + "description": "Вариант сортировки: по дате события или по количеству просмотров" + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе" + } ] }, - "description": "Обратите внимание: имя категории должно быть уникальным" + "description": "Обратите внимание: \n- это публичный эндпоинт, соответственно в выдаче должны быть только опубликованные события\n- текстовый поиск (по аннотации и подробному описанию) должен быть без учета регистра букв\n- если в запросе не указан диапазон дат [rangeStart-rangeEnd], то нужно выгружать события, которые произойдут позже текущей даты и времени\n- информация о каждом событии должна включать в себя количество просмотров и количество уже одобренных заявок на участие\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" }, "response": [] }, { - "name": "Получение категорий", + "name": "Получение подробной информации об опубликованном событии по его идентификатору", "event": [ { "listen": "prerequest", @@ -3739,8 +10213,12 @@ " const rnd = new RandomUtils();\r", "\r", " try {\r", + " const user = await api.addUser(rnd.getUser());\r", " const category = await api.addCategory(rnd.getCategory());\r", - " pm.collectionVariables.set(\"response\", category)\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.collectionVariables.set('response', event);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -3751,6 +10229,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -3776,17 +10255,34 @@ "\r", "const source = pm.collectionVariables.get('response');\r", "const target = pm.response.json();\r", - "let founded;\r", - "target.forEach(function(element){if (element.id == source.id) founded = element});\r", "\r", - "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", - "pm.expect(target[0]).to.have.property('id');\r", - "pm.expect(target[0]).to.have.property('name');\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('views');\r", + "pm.expect(target).to.have.property('confirmedRequests');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('publishedOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(source.id).equal(founded.id, 'Идентификатор категории должен соответствовать идентификатору категории добавленной ранее');\r", - " pm.expect(source.name).equal(founded.name, 'Название категории должно соответствовать названию категории добавленной ранее');\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать аннотации события с указанным идентификатором');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Категория события должна соответствовать категории события с указанным идентификатором');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость события должна соответствовать стоимости события с указанным идентификатором');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать дате проведения события с указанным идентификатором');\r", + " pm.expect(source.description).equal(target.description, 'Описание события должно соответствовать описанию события с указанным идентификатором');\r", + " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать названию события с указанным идентификатором');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Лимит участников события должен соответствовать лимиту участников события с указанным идентификатором');\r", "});" ], "type": "text/javascript" @@ -3802,31 +10298,28 @@ } ], "url": { - "raw": "{{baseUrl}}/categories?from=0&size=1000", + "raw": "{{baseUrl}}/events/:id", "host": [ "{{baseUrl}}" ], "path": [ - "categories" + "events", + ":id" ], - "query": [ - { - "key": "from", - "value": "0", - "description": "количество категорий, которые нужно пропустить для формирования текущего набора" - }, + "variable": [ { - "key": "size", - "value": "1000", - "description": "количество категорий в наборе" + "key": "id", + "value": "{{eid}}", + "description": "(Required) id события" } ] - } + }, + "description": "Обратите внимание:\n- событие должно быть опубликовано\n- информация о событии должна включать в себя количество просмотров и количество подтвержденных запросов\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" }, "response": [] }, { - "name": "Получение информации о категории по её идентификатору", + "name": "Получение полной информации о событии добавленном текущим пользователем", "event": [ { "listen": "prerequest", @@ -3837,9 +10330,11 @@ " const rnd = new RandomUtils();\r", "\r", " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", " const category = await api.addCategory(rnd.getCategory());\r", - " pm.collectionVariables.set(\"response\", category)\r", - " pm.collectionVariables.set(\"catid\", category.id)\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -3873,17 +10368,26 @@ " pm.response.to.be.json;\r", "});\r", "\r", - "const source = pm.collectionVariables.get('response');\r", "const target = pm.response.json();\r", "\r", - "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, location, requestModeration\", function () {\r", "pm.expect(target).to.have.property('id');\r", - "pm.expect(target).to.have.property('name');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(source.id).equal(target.id, 'Идентификатор категории должен соответствовать идентификатору в запросе');\r", - " pm.expect(source.name).equal(target.name, 'Название категории должно соответствовать названию категории с указанным идентификатором');\r", + " pm.expect(target.id).to.not.be.null;\r", "});" ], "type": "text/javascript" @@ -3899,19 +10403,26 @@ } ], "url": { - "raw": "{{baseUrl}}/categories/:catId", + "raw": "{{baseUrl}}/users/:userId/events/:eventId", "host": [ "{{baseUrl}}" ], "path": [ - "categories", - ":catId" + "users", + ":userId", + "events", + ":eventId" ], "variable": [ { - "key": "catId", - "value": "{{catid}}", - "description": "(Required) id категории" + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" } ] } @@ -3919,7 +10430,7 @@ "response": [] }, { - "name": "Удаление категории", + "name": "Редактирование данных события и его статуса (отклонение/публикация).", "event": [ { "listen": "prerequest", @@ -3930,10 +10441,18 @@ " const rnd = new RandomUtils();\r", "\r", " try {\r", + " const user = await api.addUser(rnd.getUser());\r", " const category = await api.addCategory(rnd.getCategory());\r", - " const findedCategory = await api.findCategory(category.id);\r", - " pm.collectionVariables.set(\"catid\", category.id)\r", - " pm.collectionVariables.set(\"response\", findedCategory)\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.stateAction = \"PUBLISH_EVENT\"\r", + " pm.collectionVariables.set('response', event2);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -3944,6 +10463,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -3961,61 +10481,81 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 204\", function () {\r", - " pm.response.to.have.status(204);\r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", "});\r", "\r", - "source = pm.collectionVariables.get('response');\r", - "catId = pm.collectionVariables.get('catid');\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json();\r", "\r", - "pm.test(\"Категория должна быть найдена до удаления\", function () {\r", - " pm.expect(source.id).equal(catId, 'Идентификтор категории должен совпадать с удаляемым');\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('publishedOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", "});\r", "\r", - "pm.sendRequest({\r", - " url: pm.collectionVariables.get(\"baseUrl\") + \"/categories/\" + catId,\r", - " method: 'GET',\r", - " }, (error, response) => {\r", - " pm.test(\"Категория не должна быть найдена после удаления\", function () {\r", - " pm.expect(response.code).to.eql(404);\r", - " });\r", - " });" + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать искомому событию');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость события должна соответствовать искомому событию');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать искомому событию');\r", + " pm.expect(source.description).equal(target.description, 'Описание события должно соответствовать искомому событию');\r", + " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать искомому событию');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Лимит участников события должен соответствовать искомому событию');\r", + "});" ], "type": "text/javascript" } } ], "request": { - "method": "DELETE", + "method": "PATCH", "header": [ { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, "url": { - "raw": "{{baseUrl}}/admin/categories/:catId", + "raw": "{{baseUrl}}/admin/events/:eventId", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "categories", - ":catId" + "events", + ":eventId" ], "variable": [ { - "key": "catId", - "value": "{{catid}}" + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" } ] }, - "description": "Обратите внимание: с категорий не должно быть связано ни одного события." + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" }, "response": [] }, { - "name": "Изменение категории", + "name": "Изменение события добавленного текущим пользователем", "event": [ { "listen": "prerequest", @@ -4024,20 +10564,24 @@ "const main = async () => {\r", " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", - " let category\r", + "\r", " try {\r", - " category = await api.addCategory(rnd.getCategory());\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.collectionVariables.set(\"response\", event);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " stateAction: \"CANCEL_REVIEW\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - " pm.collectionVariables.set(\"catid\", Number(category.id))\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify({\r", - " name : rnd.getCategory().name\r", - " }),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -4068,17 +10612,40 @@ " pm.response.to.be.json;\r", "});\r", "\r", - "const source = JSON.parse(pm.request.body.raw);\r", + "const source = pm.collectionVariables.get(\"response\");\r", "const target = pm.response.json();\r", "\r", - "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, location, requestModeration\", function () {\r", "pm.expect(target).to.have.property('id');\r", - "pm.expect(target).to.have.property('name');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(target.id).to.not.be.null;\r", - " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация отменённого события должна соответствовать аннотации события до отмены');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Категория отменённого события должна соответствовать категории события до отмены');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость отменённого события должна соответствовать стоимости события до отмены');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения отменённого события должна соответствовать дате проведения события до отмены');\r", + " pm.expect(source.description).equal(target.description, 'Описание отменённого события должно соответствовать описанию события до отмены');\r", + " pm.expect(source.title).equal(target.title, 'Название отменённого события должно соответствовать названию события до отмены');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Лимит участников отменённого события должен соответствовать лимиту участников события до отмены');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость отменённого события должна соответствовать стоимости события до отмены');\r", + "});\r", + "\r", + "pm.test(\"Событие должно иметь статус PENDING при создании и статус CANCELED после выполнения запроса\", function () {\r", + " pm.expect(source.state).equal(\"PENDING\");\r", + " pm.expect(target.state).equal(\"CANCELED\");\r", "});" ], "type": "text/javascript" @@ -4087,28 +10654,50 @@ ], "request": { "method": "PATCH", - "header": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], "body": { "mode": "raw", - "raw": "{{request_body}}" + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } }, "url": { - "raw": "{{baseUrl}}/admin/categories/:catId", + "raw": "{{baseUrl}}/users/:userId/events/:eventId", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "categories", - ":catId" + "users", + ":userId", + "events", + ":eventId" ], "variable": [ { - "key": "catId", - "value": "{{catid}}" + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" } ] - } + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." }, "response": [] } diff --git a/postman/ewm-stat-service.json b/postman/ewm-stat-service.json index 6707a74..08614b5 100644 --- a/postman/ewm-stat-service.json +++ b/postman/ewm-stat-service.json @@ -1,13 +1,14 @@ { "info": { - "_postman_id": "2727ee6b-c606-49ec-9d4b-549c21dbe7ae", - "name": "Tests for detatched stats service", + "_postman_id": "f5371ebb-b14e-45d3-bf4c-f72a79f56ea0", + "name": "\"Explore with me\" API статистика", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "23073145" + "_exporter_id": "27311667", + "_collection_link": "https://api555-4802.postman.co/workspace/explore-with-me~26332bb9-fe8e-403d-8af2-e0d94c187194/collection/27311667-f5371ebb-b14e-45d3-bf4c-f72a79f56ea0?action=share&source=collection_link&creator=27311667" }, "item": [ { - "name": "Получение статистики по посещениям. (Тест на опциональность параметра uris)", + "name": "Сохранение информации о том, что к эндпоинту был запрос", "event": [ { "listen": "prerequest", @@ -17,17 +18,117 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " try { \r", - " pm.collectionVariables.set(\"uri\", '/events'); \r", - " let post1 = rnd.getPost();\r", - " let post2 = rnd.getPost();\r", - " post1['uri'] = '/events';\r", - " post2['uri'] = '/events/5';\r", - " await api.addPost(post1);\r", - " await api.addPost(post2);\r", - "\r", - " let source = await api.getPosts(['/events']);\r", - " pm.collectionVariables.set('source', source);\r", + " let post;\r", + " try {\r", + " post = rnd.getPost();\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(post),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/hit", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "hit" + ] + }, + "description": "Сохранение информации о том, что на uri конкретного сервиса был отправлен запрос пользователем. Название сервиса, uri и ip пользователя указаны в теле запроса." + }, + "response": [] + }, + { + "name": "Получение статистики по посещениям. Обратите внимание: значение даты и времени нужно закодировать (например используя java.net.URLEncoder.encode)(Тест на /events/{id})", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try { \r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.collectionVariables.set(\"uri\", '/events/' + event.id);\r", + "\r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events/\" + event.id,\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " pm.sendRequest({\r", + " url : \"http://localhost:9090/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris=/events/\" + event.id.toString() + \"&unique=false\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " pm.collectionVariables.set('source', response.json());\r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events/\" + event.id,\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " });\r", + " });\r", + " });\r", " \r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", @@ -56,27 +157,157 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json при запросе без опционального параметра uris\", function () {\r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", "\r", - "const target = pm.response.json();\r", - "const source = pm.collectionVariables.get('source');\r", + "const target = pm.response.json()[0];\r", "\r", - "pm.test(\"При запросе по конкретному uris должны получить 1 запись\", function () {\r", - " pm.expect(source.length).to.equal(1);\r", + "pm.test(\"Посты должны содержать поля: app, uri, hits\", function () {\r", + " pm.expect(target).to.have.all.keys('app', 'uri', 'hits');\r", "});\r", "\r", - "pm.test(\"При запросе без uris должны получить больше 1 записи\", function () {\r", - " pm.expect(target.length).to.be.above(1);\r", + "const source = pm.collectionVariables.get(\"source\")[0];\r", + "\r", + "\r", + "pm.test(\"После выполнения запроса GET /events/{id} должно увеличиться количество хитов.\", function(){\r", + " pm.expect(source.hits + 1).equal(target.hits, 'Количество хитов после выполнения запроса GET /events/{id} должно быть больше на 1.');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris={{uri}}&unique=false", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "stats" + ], + "query": [ + { + "key": "start", + "value": "2020-05-05 00:00:00", + "description": "(Required) Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "end", + "value": "2035-05-05 00:00:00", + "description": "(Required) Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "uris", + "value": "{{uri}}", + "description": "Список uri для которых нужно выгрузить статистику" + }, + { + "key": "unique", + "value": "false", + "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)" + } + ] + } + }, + "response": [] + }, + { + "name": "Тест взаимодействия сервисов", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try { \r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event1 = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event1 = await api.publishEvent(event1.id);\r", + " let event2 = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event2 = await api.publishEvent(event2.id);\r", + " pm.collectionVariables.set(\"uri\", '/events/' + event1.id + '&uris=/events/' + event2.id);\r", + "\r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events/\" + event1.id,\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events/\" + event2.id,\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events/\" + event2.id,\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " });\r", + " });\r", + " });\r", + " \r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", "});\r", "\r", + "const target = pm.response.json();\r", + "\r", "pm.test(\"Посты должны содержать поля: app, uri, hits\", function () {\r", " pm.expect(target[0]).to.have.all.keys('app', 'uri', 'hits');\r", " pm.expect(target[1]).to.have.all.keys('app', 'uri', 'hits');\r", - " pm.expect(source[0]).to.have.all.keys('app', 'uri', 'hits');\r", + "});\r", + "\r", + "pm.test(\"В теле ответа должна соблюдаться сортировка по убыванию количества просмотров\", function(){\r", + " pm.expect(target[0].hits).to.be.above(target[1].hits);\r", + "});\r", + "\r", + "pm.test(\"Проверка соответствия реального количества просмотров событий и сохраненных хитов\", function(){\r", + " pm.expect(target[0].hits).equal(2);\r", + " pm.expect(target[1].hits).equal(1);\r", "});" ], "type": "text/javascript" @@ -92,7 +323,7 @@ } ], "url": { - "raw": "{{baseUrl}}/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&unique=false", + "raw": "{{baseUrl}}/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris={{uri}}", "host": [ "{{baseUrl}}" ], @@ -113,8 +344,7 @@ { "key": "uris", "value": "{{uri}}", - "description": "Список uri для которых нужно выгрузить статистику", - "disabled": true + "description": "Список uri для которых нужно выгрузить статистику" }, { "key": "uris", @@ -125,7 +355,8 @@ { "key": "unique", "value": "false", - "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)" + "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)", + "disabled": true } ] } @@ -133,7 +364,7 @@ "response": [] }, { - "name": "Получение статистики по посещениям. (Тест на опциональность и работу параметра unique)", + "name": "Получение статистики по посещениям. Обратите внимание: значение даты и времени нужно закодировать (например используя java.net.URLEncoder.encode)(Тест на /events)", "event": [ { "listen": "prerequest", @@ -145,15 +376,27 @@ "\r", " try { \r", " pm.collectionVariables.set(\"uri\", '/events'); \r", - " let post = rnd.getPost();\r", - " post['uri'] = '/events';\r", - " await api.addPost(post);\r", - " await api.addPost(post);\r", - " await api.addPost(post);\r", " \r", - " let source = await api.getPosts(['/events']);\r", - " pm.collectionVariables.set('source', source);\r", - "\r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " \r", + " pm.sendRequest({\r", + " url : \"http://localhost:9090/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris=/events&unique=false\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " pm.collectionVariables.set('source', response.json());\r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " });\r", + " });\r", + " });\r", + " \r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -181,26 +424,23 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json при запросе без опционального параметра unique\", function () {\r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", "\r", "const target = pm.response.json()[0];\r", - "const source = pm.collectionVariables.get('source')[0];\r", "\r", - "pm.test(\"При запросе с unique==true должен быть всего 1 уникальный запрос\", function () {\r", - " pm.expect(target.hits).to.equal(1);\r", + "pm.test(\"Посты должны содержать поля: app, uri, hits\", function () {\r", + " pm.expect(target).to.have.all.keys('app', 'uri', 'hits');\r", "});\r", "\r", - "pm.test(\"При запросе без uniqre должны получить минимум 3 запроса(поскольку делали 3)\", function () {\r", - " pm.expect(source.hits).to.be.above(2);\r", - "});\r", + "const source = pm.collectionVariables.get(\"source\")[0];\r", "\r", - "pm.test(\"Посты должны содержать поля: app, uri, hits\", function () {\r", - " pm.expect(target).to.have.all.keys('app', 'uri', 'hits');\r", - " pm.expect(source).to.have.all.keys('app', 'uri', 'hits');\r", + "\r", + "pm.test(\"После выполнения запроса GET /events должно увеличиться количество хитов.\", function(){\r", + " pm.expect(source.hits + 1).equal(target.hits, 'Количество хитов после выполнения запроса GET /events должно быть больше на 1.');\r", "});" ], "type": "text/javascript" @@ -216,7 +456,7 @@ } ], "url": { - "raw": "{{baseUrl}}/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris={{uri}}&unique=true", + "raw": "{{baseUrl}}/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris={{uri}}&unique=false", "host": [ "{{baseUrl}}" ], @@ -239,15 +479,9 @@ "value": "{{uri}}", "description": "Список uri для которых нужно выгрузить статистику" }, - { - "key": "uris", - "value": "aliqua o", - "description": "Список uri для которых нужно выгрузить статистику", - "disabled": true - }, { "key": "unique", - "value": "true", + "value": "false", "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)" } ] @@ -256,7 +490,7 @@ "response": [] }, { - "name": "Тест корреткной работы сохранения и просмотра количества просмотров", + "name": "Получение статистики по посещениям. (Тест на опциональность и работу параметра unique)", "event": [ { "listen": "prerequest", @@ -266,20 +500,29 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " try {\r", - " pm.collectionVariables.set(\"uri\", '/events/1&uris=/events/2');\r", - " let post1 = rnd.getPost();\r", - " let post2 = rnd.getPost();\r", - " post1['uri'] = '/events/1';\r", - " post2['uri'] = '/events/2';\r", - " await api.addPost(post1);\r", - " await api.addPost(post2);\r", - " await api.addPost(post2);\r", - " let source = await api.getPosts(['/events/1', '/events/2']);\r", - " await api.addPost(post1);\r", - " await api.addPost(post2);\r", - " pm.collectionVariables.set('source', source);\r", + " try { \r", + " pm.collectionVariables.set(\"uri\", '/events'); \r", " \r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " \r", + " pm.sendRequest({\r", + " url : \"http://localhost:9090/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris=/events&unique=true\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " pm.collectionVariables.set('source', response.json());\r", + " });\r", + " });\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -307,29 +550,27 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json при запросе без опционального параметра unique\", function () {\r", " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", "\r", - "const target = pm.response.json();\r", - "const source = pm.collectionVariables.get('source');\r", - "\r", + "const target = pm.response.json()[0];\r", "\r", "pm.test(\"Посты должны содержать поля: app, uri, hits\", function () {\r", - " pm.expect(target[0]).to.have.all.keys('app', 'uri', 'hits');\r", - " pm.expect(target[1]).to.have.all.keys('app', 'uri', 'hits');\r", + " pm.expect(target).to.have.all.keys('app', 'uri', 'hits');\r", "});\r", "\r", - "pm.test(\"В теле ответа должна соблюдаться сортировка по убыванию количества просмотров\", function(){\r", - " pm.expect(target[0].hits).to.be.above(target[1].hits);\r", - "});\r", + "const source = pm.collectionVariables.get(\"source\")[0];\r", "\r", - "pm.test(\"Проверка соответствия реального количества просмотров событий и сохраненных хитов\", function(){\r", - " pm.expect(source[0].hits+1).equal(target[0].hits);\r", - " pm.expect(source[1].hits+1).equal(target[1].hits);\r", - "});" + "pm.test(\"Количество уникальных просмотров с одного ip должно равняться 1\", function () {\r", + " pm.expect(source.hits == 1);\r", + "})\r", + "\r", + "pm.test(\"Количество просмотров с одного ip должно быть больше 1\", function () {\r", + " pm.expect(target.hits > 1);\r", + "})" ], "type": "text/javascript" } @@ -383,6 +624,209 @@ } }, "response": [] + }, + { + "name": "Получение статистики по посещениям. (Тест на верную обработку запроса с неверными датами начала и конца диапазона времени)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.be.badRequest;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/stats?start=2035-05-05 00:00:00&end=2020-05-05 00:00:00&uris={{uri}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "stats" + ], + "query": [ + { + "key": "start", + "value": "2035-05-05 00:00:00", + "description": "(Required) Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "end", + "value": "2020-05-05 00:00:00", + "description": "(Required) Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "uris", + "value": "{{uri}}", + "description": "Список uri для которых нужно выгрузить статистику" + }, + { + "key": "unique", + "value": "false", + "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Тест на верную обработку запроса без даты начала", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.be.badRequest;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/stats?end=2020-05-05 00:00:00&uris={{uri}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "stats" + ], + "query": [ + { + "key": "start", + "value": "2035-05-05 00:00:00", + "description": "(Required) Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")", + "disabled": true + }, + { + "key": "end", + "value": "2020-05-05 00:00:00", + "description": "(Required) Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "uris", + "value": "{{uri}}", + "description": "Список uri для которых нужно выгрузить статистику" + }, + { + "key": "unique", + "value": "false", + "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Тест на верную обработку запроса без даты конца", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.be.badRequest;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/stats?start=2035-05-05 00:00:00&uris={{uri}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "stats" + ], + "query": [ + { + "key": "start", + "value": "2035-05-05 00:00:00", + "description": "(Required) Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "end", + "value": "2020-05-05 00:00:00", + "description": "(Required) Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")", + "disabled": true + }, + { + "key": "uris", + "value": "{{uri}}", + "description": "Список uri для которых нужно выгрузить статистику" + }, + { + "key": "unique", + "value": "false", + "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)", + "disabled": true + } + ] + } + }, + "response": [] } ], "event": [ @@ -402,28 +846,48 @@ " return this.post(\"/hit\", post, \"Ошибка при сохранении информации о запросе к эндпойнту: \", verbose);", " }", "", - " async getPosts(uris=null, verbose=null) {", - " return this.get(uris == null ? \"/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00\" : \"/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris=\"+uris.join('&uris='), null, \"Ошибка при сохранении информации о запросе к эндпойнту: \", verbose);", + " async addUser(user, verbose=null) {", + " return this.post(\"/admin/users\", user, \"http://localhost:8080\", \"Ошибка при добавлении нового пользователя: \", verbose);", + " }", + "", + " async addCategory(category, verbose=null) {", + " return this.post(\"/admin/categories\", category, \"http://localhost:8080\", \"Ошибка при добавлении новой категории: \", verbose);", " }", "", - " async post(path, body, errorText = \"Ошибка при выполнении post-запроса: \", verbose=null) {", - " return this.sendRequest(\"POST\", path, body, errorText);", + " async addEvent(userId, event, verbose=null) {", + " return this.post(\"/users/\" + userId + \"/events\", event, \"http://localhost:8080\", \"Ошибка при добавлении нового события: \", verbose);", " }", "", - " async get(path, body = null, errorText = \"Ошибка при выполнении get-запроса: \", verbose=null) {", - " return this.sendRequest(\"GET\", path, body, errorText);", + " async publishEvent(eventId, verbose=null) {", + " return this.patch('/admin/events/' + eventId, {stateAction: \"PUBLISH_EVENT\"},\"Ошибка при публикации события\", verbose);", " }", "", - " async sendRequest(method, path, body=null, errorText = \"Ошибка при выполнении запроса: \", verbose=null) {", + " async patch(path, body = null, errorText = \"Ошибка при выполнении patch-запроса: \", verbose=null) {", + " return this.sendRequest(\"PATCH\", path, \"http://localhost:8080\", body, errorText);", + " }", + "", + " async post(path, body, newBaseUrl=null, errorText = \"Ошибка при выполнении post-запроса: \", verbose=null) {", + " return this.sendRequest(\"POST\", path, newBaseUrl, body, errorText);", + " }", + "", + " async sendRequest(method, path, newBaseUrl=null, body=null, errorText = \"Ошибка при выполнении запроса: \", verbose=null) {", " return new Promise((resolve, reject) => {", " verbose = verbose == null ? this._verbose : verbose;", - "", - " let request = {", - " url: this.baseUrl + path,", - " method: method,", - " body: body == null ? \"\" : JSON.stringify(body),", - " header: { \"Content-Type\": \"application/json\" },", - " };", + " let request;", + " if (newBaseUrl==null)", + " request = {", + " url: this.baseUrl + path,", + " method: method,", + " body: body == null ? \"\" : JSON.stringify(body),", + " header: { \"Content-Type\": \"application/json\" },", + " };", + " else", + " request = {", + " url: newBaseUrl + path,", + " method: method,", + " body: body == null ? \"\" : JSON.stringify(body),", + " header: { \"Content-Type\": \"application/json\" },", + " };", "", " if(verbose) {", " console.log(\"Отправляю запрос: \", request);", @@ -467,11 +931,49 @@ " return {", " app: \"ewm-main-service\",", " uri: \"/events/\" + pm.variables.replaceIn('{{$randomInt}}'),", - " ip: '121.0.0.1',", + " ip: pm.variables.replaceIn('{{$randomIP}}'),", " timestamp: this.getPastDateTime()", " }", " }", "", + " getUser() {", + " return {", + " name: pm.variables.replaceIn('{{$randomFullName}}'),", + " email: pm.variables.replaceIn('{{$randomEmail}}')", + " };", + " }", + "", + " getCategory() {", + " return {", + " name: pm.variables.replaceIn('{{$randomWord}}') + Math.floor(Math.random() * 100).toString()", + " };", + " }", + "", + " getEvent(categoryId) {", + " return {", + " annotation: pm.variables.replaceIn('{{$randomLoremParagraph}}'),", + " category: categoryId,", + " description: pm.variables.replaceIn('{{$randomLoremParagraphs}}'),", + " eventDate: this.getFutureDateTime(),", + " location: {", + " lat: parseFloat(pm.variables.replaceIn('{{$randomLatitude}}')),", + " lon: parseFloat(pm.variables.replaceIn('{{$randomLongitude}}')),", + " },", + " paid: pm.variables.replaceIn('{{$randomBoolean}}'),", + " participantLimit: pm.variables.replaceIn('{{$randomInt}}'),", + " requestModeration: pm.variables.replaceIn('{{$randomBoolean}}'),", + " title: pm.variables.replaceIn('{{$randomLoremSentence}}'),", + " }", + " }", + " ", + " getCompilation(...eventIds) { ", + " return { ", + " title: pm.variables.replaceIn('{{$randomLoremSentence}}'), ", + " pinned: pm.variables.replaceIn('{{$randomBoolean}}'), ", + " events: eventIds ", + " }; ", + " }", + "", " getPastDateTime(hourShift = 5, minuteShift=0, yearShift=0) {", " let moment = require('moment');", "", @@ -483,6 +985,16 @@ " return m.format('YYYY-MM-DD HH:mm:ss');", " }", "", + " getFutureDateTime(hourShift = 5, minuteShift=0, yearShift=0) {", + " let moment = require('moment');", + "", + " let m = moment();", + " m.add(hourShift, 'hour');", + " m.add(minuteShift, 'minute');", + " m.add(yearShift, 'year');", + "", + " return m.format('YYYY-MM-DD HH:mm:ss');", + " }", "}" ] } @@ -512,4 +1024,4 @@ "value": "" } ] -} +} \ No newline at end of file From df582f41f1f9429daab61c23c1673f23f0a5667a Mon Sep 17 00:00:00 2001 From: Basar <74671944+Basarus@users.noreply.github.com> Date: Sat, 17 Jan 2026 10:01:46 +0300 Subject: [PATCH 08/10] Add requests (#10) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Добавлены реквесты * Фикс checkstyle * Фикс checkstyle * Фикс checkstyle * Фикс checkstyle * Фикс checkstyle * Фикс checkstyle * Фикс checkstyle * Фикс checkstyle * spotless apply * chore: change mapper to @utilityclass * Фикс checkstyle * - Доработан ExceptionHandler * - fix codestyle * fixes --------- Co-authored-by: Ilia Egorov --- main-service/pom.xml | 7 + .../exception/ConflictException.java | 7 + .../handler/GlobalExceptionHandler.java | 34 +- ...cipationRequestEventPrivateController.java | 50 +++ ...ParticipationRequestPrivateController.java | 46 ++ .../mapper/ParticipationRequestMapper.java | 31 ++ .../request/model/EventRequestStatus.java | 3 +- .../request/model/ParticipationRequest.java | 21 +- .../ParticipationRequestRepository.java | 23 + .../service/ParticipationRequestService.java | 20 + .../ParticipationRequestServiceImpl.java | 246 ++++++++++ main-service/src/main/resources/schema.sql | 9 +- .../ParticipationRequestBatchUpdateIT.java | 136 ++++++ .../ParticipationRequestServiceIT.java | 122 +++++ .../ParticipationRequestsControllerIT.java | 421 ++++++++++++++++++ .../src/test/resources/application-test.yaml | 26 ++ 16 files changed, 1189 insertions(+), 13 deletions(-) create mode 100644 main-service/src/main/java/ru/practicum/exception/ConflictException.java create mode 100644 main-service/src/main/java/ru/practicum/request/controller/ParticipationRequestEventPrivateController.java create mode 100644 main-service/src/main/java/ru/practicum/request/controller/ParticipationRequestPrivateController.java create mode 100644 main-service/src/main/java/ru/practicum/request/mapper/ParticipationRequestMapper.java create mode 100644 main-service/src/main/java/ru/practicum/request/repository/ParticipationRequestRepository.java create mode 100644 main-service/src/main/java/ru/practicum/request/service/ParticipationRequestService.java create mode 100644 main-service/src/main/java/ru/practicum/request/service/ParticipationRequestServiceImpl.java create mode 100644 main-service/src/test/java/ru/practicum/request/ParticipationRequestBatchUpdateIT.java create mode 100644 main-service/src/test/java/ru/practicum/request/ParticipationRequestServiceIT.java create mode 100644 main-service/src/test/java/ru/practicum/request/ParticipationRequestsControllerIT.java create mode 100644 main-service/src/test/resources/application-test.yaml 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 From e42fbcf87c02d48fd5d5c4e71651066270057e88 Mon Sep 17 00:00:00 2001 From: LightInTheFire <109972737+LightInTheFire@users.noreply.github.com> Date: Sat, 17 Jan 2026 10:47:26 +0300 Subject: [PATCH 09/10] Main svc compilations (#11) * feat: add compilations * feat: add all postman tests * feat: add compilations --- .../CompilationsAdminController.java | 43 + .../CompilationsPublicController.java | 37 + .../mapper/CompilationsMapper.java | 45 + .../repository/CompilationsRepository.java | 20 + .../service/CompilationsPublicGetRequest.java | 16 + .../service/CompilationsService.java | 21 + .../service/CompilationsServiceImpl.java | 202 + .../event/repository/EventRepository.java | 4 + .../event/service/EventServiceImpl.java | 82 +- ...ParticipationRequestPrivateController.java | 10 +- .../repository/ConfirmedRequestsCount.java | 8 + .../ParticipationRequestRepository.java | 28 + postman/ewm-main-service.json | 5751 ++++++++++++++--- 13 files changed, 5167 insertions(+), 1100 deletions(-) create mode 100644 main-service/src/main/java/ru/practicum/compilation/controller/CompilationsAdminController.java create mode 100644 main-service/src/main/java/ru/practicum/compilation/controller/CompilationsPublicController.java create mode 100644 main-service/src/main/java/ru/practicum/compilation/mapper/CompilationsMapper.java create mode 100644 main-service/src/main/java/ru/practicum/compilation/repository/CompilationsRepository.java create mode 100644 main-service/src/main/java/ru/practicum/compilation/service/CompilationsPublicGetRequest.java create mode 100644 main-service/src/main/java/ru/practicum/compilation/service/CompilationsService.java create mode 100644 main-service/src/main/java/ru/practicum/compilation/service/CompilationsServiceImpl.java create mode 100644 main-service/src/main/java/ru/practicum/request/repository/ConfirmedRequestsCount.java diff --git a/main-service/src/main/java/ru/practicum/compilation/controller/CompilationsAdminController.java b/main-service/src/main/java/ru/practicum/compilation/controller/CompilationsAdminController.java new file mode 100644 index 0000000..3ffd8fa --- /dev/null +++ b/main-service/src/main/java/ru/practicum/compilation/controller/CompilationsAdminController.java @@ -0,0 +1,43 @@ +package ru.practicum.compilation.controller; + +import jakarta.validation.Valid; + +import ru.practicum.compilation.dto.CompilationDto; +import ru.practicum.compilation.dto.NewCompilationDto; +import ru.practicum.compilation.dto.UpdateCompilationRequest; +import ru.practicum.compilation.service.CompilationsService; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin/compilations") +public class CompilationsAdminController { + private final CompilationsService compService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public CompilationDto saveCompilation(@RequestBody @Valid NewCompilationDto newCompilationDto) { + log.info("Admin save compilation requested with body={}", newCompilationDto); + return compService.save(newCompilationDto); + } + + @DeleteMapping("/{compId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteCompilation(@PathVariable long compId) { + log.info("Admin delete compilation requested with id={}", compId); + compService.deleteById(compId); + } + + @PatchMapping("/{compId}") + public CompilationDto updateCompilation( + @PathVariable long compId, @RequestBody @Valid UpdateCompilationRequest updateRequest) { + log.info("Admin update compilation requested with id={}, body={}", compId, updateRequest); + return compService.update(compId, updateRequest); + } +} diff --git a/main-service/src/main/java/ru/practicum/compilation/controller/CompilationsPublicController.java b/main-service/src/main/java/ru/practicum/compilation/controller/CompilationsPublicController.java new file mode 100644 index 0000000..0f6be83 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/compilation/controller/CompilationsPublicController.java @@ -0,0 +1,37 @@ +package ru.practicum.compilation.controller; + +import java.util.Collection; + +import ru.practicum.compilation.dto.CompilationDto; +import ru.practicum.compilation.service.CompilationsPublicGetRequest; +import ru.practicum.compilation.service.CompilationsService; + +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/compilations") +public class CompilationsPublicController { + private final CompilationsService compService; + + @GetMapping + public Collection getCompilations( + @RequestParam(required = false) Boolean pinned, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size) { + CompilationsPublicGetRequest getRequest = + new CompilationsPublicGetRequest(pinned, from, size); + log.info("Public get compilations requested with params= {}", getRequest); + return compService.findAll(getRequest); + } + + @GetMapping("/{compId}") + public CompilationDto getById(@PathVariable long compId) { + log.info("Public get compilation by id requested with id={}", compId); + return compService.findById(compId); + } +} diff --git a/main-service/src/main/java/ru/practicum/compilation/mapper/CompilationsMapper.java b/main-service/src/main/java/ru/practicum/compilation/mapper/CompilationsMapper.java new file mode 100644 index 0000000..4424c0d --- /dev/null +++ b/main-service/src/main/java/ru/practicum/compilation/mapper/CompilationsMapper.java @@ -0,0 +1,45 @@ +package ru.practicum.compilation.mapper; + +import java.util.List; +import java.util.Set; + +import ru.practicum.compilation.dto.CompilationDto; +import ru.practicum.compilation.dto.NewCompilationDto; +import ru.practicum.compilation.dto.UpdateCompilationRequest; +import ru.practicum.compilation.model.Compilation; +import ru.practicum.event.dto.EventShortDto; +import ru.practicum.event.model.Event; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class CompilationsMapper { + + public Compilation mapToEntity(NewCompilationDto newCompilationDto, Set events) { + return new Compilation(null, newCompilationDto.title(), newCompilationDto.pinned(), events); + } + + public CompilationDto mapToDto(Compilation compilation, List events) { + return new CompilationDto( + events, + compilation.getId(), + Boolean.TRUE.equals(compilation.getPinned()), + compilation.getTitle()); + } + + public void updateEntity( + Compilation compilation, UpdateCompilationRequest updateRequest, Set events) { + + if (updateRequest.hasTitle()) { + compilation.setTitle(updateRequest.title()); + } + + if (updateRequest.hasPinned()) { + compilation.setPinned(updateRequest.pinned()); + } + + if (updateRequest.hasEvents()) { + compilation.setEvents(events); + } + } +} diff --git a/main-service/src/main/java/ru/practicum/compilation/repository/CompilationsRepository.java b/main-service/src/main/java/ru/practicum/compilation/repository/CompilationsRepository.java new file mode 100644 index 0000000..3f6d509 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/compilation/repository/CompilationsRepository.java @@ -0,0 +1,20 @@ +package ru.practicum.compilation.repository; + +import java.util.Optional; + +import ru.practicum.compilation.model.Compilation; + +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; + +public interface CompilationsRepository extends JpaRepository { + + Page findAllByPinned(boolean pinned, Pageable pageable); + + @EntityGraph(attributePaths = "events") + Optional findWithEventsById(Long id); + + boolean existsByTitle(String title); +} diff --git a/main-service/src/main/java/ru/practicum/compilation/service/CompilationsPublicGetRequest.java b/main-service/src/main/java/ru/practicum/compilation/service/CompilationsPublicGetRequest.java new file mode 100644 index 0000000..476e56a --- /dev/null +++ b/main-service/src/main/java/ru/practicum/compilation/service/CompilationsPublicGetRequest.java @@ -0,0 +1,16 @@ +package ru.practicum.compilation.service; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +public record CompilationsPublicGetRequest(Boolean pinned, int from, int size) { + + public CompilationsPublicGetRequest { + if (pinned == null) pinned = false; + } + + public Pageable getPageable() { + int page = from / size; + return PageRequest.of(page, size); + } +} diff --git a/main-service/src/main/java/ru/practicum/compilation/service/CompilationsService.java b/main-service/src/main/java/ru/practicum/compilation/service/CompilationsService.java new file mode 100644 index 0000000..1427a38 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/compilation/service/CompilationsService.java @@ -0,0 +1,21 @@ +package ru.practicum.compilation.service; + +import java.util.Collection; + +import jakarta.validation.Valid; + +import ru.practicum.compilation.dto.CompilationDto; +import ru.practicum.compilation.dto.NewCompilationDto; +import ru.practicum.compilation.dto.UpdateCompilationRequest; + +public interface CompilationsService { + Collection findAll(CompilationsPublicGetRequest getRequest); + + CompilationDto findById(long compId); + + CompilationDto save(@Valid NewCompilationDto newCompilationDto); + + void deleteById(long compId); + + CompilationDto update(long compId, UpdateCompilationRequest updateRequest); +} diff --git a/main-service/src/main/java/ru/practicum/compilation/service/CompilationsServiceImpl.java b/main-service/src/main/java/ru/practicum/compilation/service/CompilationsServiceImpl.java new file mode 100644 index 0000000..32ffbe8 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/compilation/service/CompilationsServiceImpl.java @@ -0,0 +1,202 @@ +package ru.practicum.compilation.service; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import ru.practicum.client.StatsClient; +import ru.practicum.compilation.dto.CompilationDto; +import ru.practicum.compilation.dto.NewCompilationDto; +import ru.practicum.compilation.dto.UpdateCompilationRequest; +import ru.practicum.compilation.mapper.CompilationsMapper; +import ru.practicum.compilation.model.Compilation; +import ru.practicum.compilation.repository.CompilationsRepository; +import ru.practicum.dto.ViewStatsDto; +import ru.practicum.event.dto.EventShortDto; +import ru.practicum.event.mapper.EventMapper; +import ru.practicum.event.model.Event; +import ru.practicum.event.repository.EventRepository; +import ru.practicum.exception.ConflictException; +import ru.practicum.exception.NotFoundException; +import ru.practicum.request.repository.ParticipationRequestRepository; + +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class CompilationsServiceImpl implements CompilationsService { + private static final LocalDateTime MINIMAL_LOCAL_DATE_TIME = + LocalDateTime.of(1000, 1, 1, 0, 0, 0); + private final CompilationsRepository compRepository; + private final EventRepository eventRepository; + private final ParticipationRequestRepository requestRepository; + private final StatsClient statsClient; + + @Override + public Collection findAll(CompilationsPublicGetRequest getRequest) { + + Page page = + compRepository.findAllByPinned(getRequest.pinned(), getRequest.getPageable()); + + Set events = + page.stream().flatMap(c -> c.getEvents().stream()).collect(Collectors.toSet()); + + Map confirmedRequests = getConfirmedRequests(events); + Map views = getViews(events); + + return page.stream().map(c -> toDto(c, confirmedRequests, views)).toList(); + } + + @Override + public CompilationDto findById(long compId) { + + Compilation compilation = + compRepository + .findWithEventsById(compId) + .orElseThrow( + NotFoundException.supplier( + "Compilation with id=%d was not found", compId)); + + Set events = compilation.getEvents(); + + Map confirmedRequests = getConfirmedRequests(events); + Map views = getViews(events); + + return toDto(compilation, confirmedRequests, views); + } + + @Override + @Transactional + public CompilationDto save(NewCompilationDto newCompilationDto) { + + if (compRepository.existsByTitle(newCompilationDto.title())) { + throw new ConflictException( + "Compilation with title=" + newCompilationDto.title() + " already exists"); + } + + Set events = getEvents(newCompilationDto.events()); + + Compilation compilation = CompilationsMapper.mapToEntity(newCompilationDto, events); + + Compilation saved = compRepository.save(compilation); + + Map confirmedRequests = getConfirmedRequests(events); + Map views = getViews(events); + + return toDto(saved, confirmedRequests, views); + } + + @Override + @Transactional + public void deleteById(long compId) { + if (!compRepository.existsById(compId)) { + throw new NotFoundException("Compilation with id=" + compId + " was not found"); + } + + compRepository.deleteById(compId); + } + + @Override + @Transactional + public CompilationDto update(long compId, UpdateCompilationRequest updateRequest) { + + Compilation compilation = + compRepository + .findWithEventsById(compId) + .orElseThrow( + NotFoundException.supplier( + "Compilation with id=%d was not found", compId)); + + Set events = null; + if (updateRequest.hasEvents()) { + events = getEvents(updateRequest.events()); + } + + CompilationsMapper.updateEntity(compilation, updateRequest, events); + + Compilation updated = compRepository.save(compilation); + + Set actualEvents = updated.getEvents(); + + Map confirmedRequests = getConfirmedRequests(actualEvents); + Map views = getViews(actualEvents); + + return toDto(updated, confirmedRequests, views); + } + + private CompilationDto toDto( + Compilation compilation, Map confirmedRequests, Map views) { + List events = + compilation.getEvents().stream() + .map( + event -> + EventMapper.mapToShortDto( + event, + confirmedRequests.getOrDefault(event.getId(), 0L), + views.get(event.getId()))) + .toList(); + + return CompilationsMapper.mapToDto(compilation, events); + } + + private Set getEvents(Collection eventIds) { + if (eventIds == null || eventIds.isEmpty()) { + return Set.of(); + } + + Set events = eventRepository.findAllByIdIn(eventIds); + + if (events.size() != eventIds.size()) { + throw new NotFoundException("One or more events were not found"); + } + + return events; + } + + private Map getConfirmedRequests(Set events) { + if (events.isEmpty()) { + return Map.of(); + } + + List eventIds = events.stream().map(Event::getId).toList(); + + return requestRepository.countConfirmedByEventIds(eventIds); + } + + private Map getViews(Set events) { + if (events.isEmpty()) { + return Map.of(); + } + + List uris = events.stream().map(e -> "/events/%s".formatted(e.getId())).toList(); + + try { + List stats = + statsClient.getStats(MINIMAL_LOCAL_DATE_TIME, LocalDateTime.now(), uris, true); + + return stats.stream() + .collect( + Collectors.toMap( + s -> { + String uri = s.uri(); + return Long.parseLong( + uri.substring(uri.lastIndexOf("/") + 1)); + }, + ViewStatsDto::hits)); + + } catch (Exception e) { + log.error("Error during getting stats for events", e); + return Map.of(); + } + } +} diff --git a/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java b/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java index c9917e8..9c07c7a 100644 --- a/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java +++ b/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java @@ -1,7 +1,9 @@ package ru.practicum.event.repository; import java.time.LocalDateTime; +import java.util.Collection; import java.util.Optional; +import java.util.Set; import ru.practicum.event.model.Event; import ru.practicum.event.model.EventState; @@ -86,4 +88,6 @@ static Predicate createPredicate(EventsPublicGetRequest request) { Optional findByIdAndState(Long id, EventState state); Page findByInitiator_Id(Long initiatorId, Pageable pageable); + + Set findAllByIdIn(Collection ids); } diff --git a/main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java b/main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java index f5a456e..b19d9db 100644 --- a/main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java +++ b/main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java @@ -23,6 +23,7 @@ import ru.practicum.exception.IllegalEventUpdateException; import ru.practicum.exception.NotFoundException; import ru.practicum.exception.ValidationException; +import ru.practicum.request.repository.ParticipationRequestRepository; import ru.practicum.user.model.User; import ru.practicum.user.repository.UserRepository; @@ -41,13 +42,12 @@ public class EventServiceImpl implements EventService { private static final Duration MIN_TIME_BEFORE_EVENT = Duration.ofHours(2); private static final LocalDateTime MINIMAL_LOCAL_DATE_TIME = LocalDateTime.of(1000, 1, 1, 0, 0, 0); - private static final LocalDateTime MAXIMUM_LOCAL_DATE_TIME = - LocalDateTime.of(9999, 1, 1, 0, 0, 0); private static final String EVENTS_URI = "/events/%d"; private final EventRepository eventRepository; private final UserRepository userRepository; private final CategoryRepository categoryRepository; private final StatsClient statsClient; + private final ParticipationRequestRepository requestRepository; @Override public EventFullDto getById(Long eventId, HttpServletRequest request) { @@ -62,8 +62,10 @@ public EventFullDto getById(Long eventId, HttpServletRequest request) { ViewStatsDto statsDto = getStatsForEvent(event, uri); + Map confirmedRequests = getConfirmedRequests(Set.of(event)); + return EventMapper.mapToFullDto( - event, 0, statsDto.hits()); // change 0 to actual number of requests + event, confirmedRequests.getOrDefault(event.getId(), 0L), statsDto.hits()); } @Override @@ -74,12 +76,10 @@ public Collection getEvents(EventsPublicGetRequest getRequest) { statsClient.hit(getRequest.httpRequest()); - LocalDateTime statsFrom = - getRequest.hasRangeStart() ? getRequest.rangeStart() : LocalDateTime.now(); - LocalDateTime statsTo = - getRequest.hasRangeEnd() ? getRequest.rangeEnd() : MAXIMUM_LOCAL_DATE_TIME; + Map statsForEvents = getStatsMapForEvents(events); - Map statsForEvents = getStatsMapForEvents(events, statsFrom, statsTo); + Map confirmedRequests = + getConfirmedRequests(events.stream().collect(Collectors.toSet())); List eventsList = events.stream() @@ -87,10 +87,8 @@ public Collection getEvents(EventsPublicGetRequest getRequest) { event -> EventMapper.mapToShortDto( event, - 0, - statsForEvents.get( - event.getId()))) // change 0 to actual - // number of requests + confirmedRequests.getOrDefault(event.getId(), 0L), + statsForEvents.get(event.getId()))) .toList(); if (EventSortBy.VIEWS.equals(getRequest.sort())) { @@ -106,18 +104,18 @@ public Collection getEvents(EventsAdminGetRequest getRequest) { eventRepository.findAll( EventRepository.createPredicate(getRequest), getRequest.getPageable()); - Map statsForEvents = - getStatsMapForEvents(events, MINIMAL_LOCAL_DATE_TIME, MAXIMUM_LOCAL_DATE_TIME); + Map statsForEvents = getStatsMapForEvents(events); + + Map confirmedRequests = + getConfirmedRequests(events.stream().collect(Collectors.toSet())); return events.stream() .map( event -> EventMapper.mapToFullDto( event, - 0, - statsForEvents.get( - event.getId()))) // change 0 to actual number of - // requests + confirmedRequests.getOrDefault(event.getId(), 0L), + statsForEvents.get(event.getId()))) .toList(); } @@ -127,18 +125,18 @@ public Collection getEvents(EventsPrivateGetRequest getRequest) { Page events = eventRepository.findByInitiator_Id(getRequest.userId(), getRequest.getPageable()); - Map statsForEvents = - getStatsMapForEvents(events, MINIMAL_LOCAL_DATE_TIME, MAXIMUM_LOCAL_DATE_TIME); + Map statsForEvents = getStatsMapForEvents(events); + + Map confirmedRequests = + getConfirmedRequests(events.stream().collect(Collectors.toSet())); return events.stream() .map( event -> EventMapper.mapToShortDto( event, - 0, - statsForEvents.get( - event.getId()))) // change 0 to actual number of - // requests + confirmedRequests.getOrDefault(event.getId(), 0L), + statsForEvents.get(event.getId()))) .toList(); } @@ -173,8 +171,10 @@ public EventFullDto getByUserById(Long userId, Long eventId) { ViewStatsDto statsDto = getStatsForEvent(event, EVENTS_URI.formatted(eventId)); + Map confirmedRequests = getConfirmedRequests(Set.of(event)); + return EventMapper.mapToFullDto( - event, 0, statsDto.hits()); // change 0 to actual number of requests + event, confirmedRequests.getOrDefault(event.getId(), 0L), statsDto.hits()); } @Override @@ -198,7 +198,10 @@ public EventFullDto updateEvent(Long eventId, UpdateEventAdminRequest updateRequ Event saved = eventRepository.save(event); - return EventMapper.mapToFullDto(saved, 0, null); // change 0 to actual number of requests + Map confirmedRequests = getConfirmedRequests(Set.of(event)); + + return EventMapper.mapToFullDto( + saved, confirmedRequests.getOrDefault(event.getId(), 0L), null); } @Override @@ -228,14 +231,16 @@ public EventFullDto updateEventByUser( Event saved = eventRepository.save(event); - return EventMapper.mapToFullDto(saved, 0, null); // change 0 to actual number of requests + Map confirmedRequests = getConfirmedRequests(Set.of(event)); + + return EventMapper.mapToFullDto( + saved, confirmedRequests.getOrDefault(event.getId(), 0L), null); } - private Map getStatsMapForEvents( - Page events, LocalDateTime from, LocalDateTime to) { + private Map getStatsMapForEvents(Page events) { List listOfUris = events.stream().map(event -> EVENTS_URI.formatted(event.getId())).toList(); - return getStatsForEvents(listOfUris, from, to).stream() + return getStatsForEvents(listOfUris).stream() .collect( Collectors.toMap( statsDto -> @@ -247,10 +252,19 @@ private Map getStatsMapForEvents( ViewStatsDto::hits)); } - private List getStatsForEvents( - List uris, LocalDateTime from, LocalDateTime to) { + private Map getConfirmedRequests(Set events) { + if (events.isEmpty()) { + return Map.of(); + } + + List eventIds = events.stream().map(Event::getId).toList(); + + return requestRepository.countConfirmedByEventIds(eventIds); + } + + private List getStatsForEvents(List uris) { try { - return statsClient.getStats(from, to, uris, true); + return statsClient.getStats(MINIMAL_LOCAL_DATE_TIME, LocalDateTime.now(), uris, true); } catch (Exception e) { log.error("Error during getting stats for events", e); } @@ -264,7 +278,7 @@ private ViewStatsDto getStatsForEvent(Event event, String uri) { statsClient .getStats( MINIMAL_LOCAL_DATE_TIME, - MAXIMUM_LOCAL_DATE_TIME, + LocalDateTime.now(), List.of(uri), true) .getFirst(); 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 index 37f49f3..da28856 100644 --- a/main-service/src/main/java/ru/practicum/request/controller/ParticipationRequestPrivateController.java +++ b/main-service/src/main/java/ru/practicum/request/controller/ParticipationRequestPrivateController.java @@ -5,13 +5,8 @@ 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 org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -25,6 +20,7 @@ public class ParticipationRequestPrivateController { private final ParticipationRequestService requestService; @PostMapping + @ResponseStatus(HttpStatus.CREATED) public ParticipationRequestDto createRequest( @PathVariable Long userId, @RequestParam Long eventId) { log.info("Create participation request userId={}, eventId={}", userId, eventId); diff --git a/main-service/src/main/java/ru/practicum/request/repository/ConfirmedRequestsCount.java b/main-service/src/main/java/ru/practicum/request/repository/ConfirmedRequestsCount.java new file mode 100644 index 0000000..64b26ad --- /dev/null +++ b/main-service/src/main/java/ru/practicum/request/repository/ConfirmedRequestsCount.java @@ -0,0 +1,8 @@ +package ru.practicum.request.repository; + +public interface ConfirmedRequestsCount { + + Long getEventId(); + + Long getCnt(); +} 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 index 4678b92..ad85ac6 100644 --- a/main-service/src/main/java/ru/practicum/request/repository/ParticipationRequestRepository.java +++ b/main-service/src/main/java/ru/practicum/request/repository/ParticipationRequestRepository.java @@ -2,11 +2,15 @@ import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import ru.practicum.request.model.EventRequestStatus; import ru.practicum.request.model.ParticipationRequest; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface ParticipationRequestRepository extends JpaRepository { boolean existsByEvent_IdAndRequester_Id(Long eventId, Long requesterId); @@ -20,4 +24,28 @@ public interface ParticipationRequestRepository extends JpaRepository findAllByIdInAndEvent_Id(Collection ids, Long eventId); List findAllByEvent_IdAndStatus(Long eventId, EventRequestStatus status); + + @Query( + """ + select pr.event.id as eventId, count(pr.id) as cnt + from ParticipationRequest pr + where pr.status = :status + and pr.event.id in :eventIds + group by pr.event.id + """) + List countByEventIdsAndStatusRaw( + @Param("eventIds") Collection eventIds, + @Param("status") EventRequestStatus status); + + default Map countConfirmedByEventIds(Collection eventIds) { + if (eventIds == null || eventIds.isEmpty()) { + return Map.of(); + } + + return countByEventIdsAndStatusRaw(eventIds, EventRequestStatus.CONFIRMED).stream() + .collect( + Collectors.toMap( + ConfirmedRequestsCount::getEventId, + ConfirmedRequestsCount::getCnt)); + } } diff --git a/postman/ewm-main-service.json b/postman/ewm-main-service.json index 9c84a28..62b600e 100644 --- a/postman/ewm-main-service.json +++ b/postman/ewm-main-service.json @@ -1,10 +1,10 @@ { "info": { - "_postman_id": "788f4375-147e-4e45-ab6d-7baa8e542674", - "name": "Test Explore With Me - Main service Copy", + "_postman_id": "4f622f31-328a-4506-95bd-66359cfbe749", + "name": "Test Explore With Me - Main service", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "27311667", - "_collection_link": "https://api555-4802.postman.co/workspace/explore-with-me~26332bb9-fe8e-403d-8af2-e0d94c187194/collection/27311667-788f4375-147e-4e45-ab6d-7baa8e542674?action=share&source=collection_link&creator=27311667" + "_exporter_id": "23073145", + "_collection_link": "https://universal-shadow-295426.postman.co/workspace/My-Workspace~4200f6aa-0504-44b1-8a1d-707d0dcbd5ce/collection/13708500-4f622f31-328a-4506-95bd-66359cfbe749?action=share&source=collection_link&creator=23073145" }, "item": [ { @@ -13,6 +13,103 @@ { "name": "Event", "item": [ + { + "name": "Required query params", + "item": [ + { + "name": "Добавление запроса от текущего пользователя на участие в событии без обязательного query params", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " pm.request.removeQueryParams(['eventId']);\r", + " pm.collectionVariables.set('uid', submittedUser.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/requests", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "requests" + ], + "query": [ + { + "key": "eventId", + "value": "0", + "description": "(Required) id события", + "disabled": true + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- нельзя добавить повторный запрос\n- инициатор события не может добавить запрос на участие в своём событии\n- нельзя участвовать в неопубликованном событии\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного" + }, + "response": [] + } + ] + }, { "name": "Unrequired query params", "item": [ @@ -1337,6 +1434,169 @@ }, "response": [] }, + { + "name": "Поиск событий с проверкой параметров", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const user2 = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = rnd.getEvent(category.id)\r", + " event.requestModeration = true;\r", + " event.participantLimit = 2;\r", + " event = await api.addEvent(user.id, event);\r", + " event = await api.publishEvent(event.id);\r", + " pm.request.removeQueryParams(['users', 'categories']);\r", + " pm.request.addQueryParams([`users=` + user.id, 'categories=' + category.id]);\r", + " pm.collectionVariables.set('response', event);\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/admin/events?users=\" + user.id +\"&states=PUBLISHED&categories=\" + category.id + \"&rangeStart=2022-01-06%2013%3A30%3A38&rangeEnd=2097-09-06%2013%3A30%3A38&from=0&size=1000\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " pm.collectionVariables.set('confirmedRequests', response.json()[0].confirmedRequests)\r", + " });\r", + " const requestToJoin = await api.publishParticipationRequest(event.id, user2.id);\r", + " const confirmedRequest = await api.acceptParticipationRequest(event.id, user.id, requestToJoin.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json()[0];\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('views');\r", + "pm.expect(target).to.have.property('confirmedRequests');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('publishedOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать искомому событию');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Идентификатор категории должен соответствовать искомой категории');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость посещения события должна соответствовать искомому событию');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать дате искомого события');\r", + " pm.expect(source.description).equal(target.description, 'Описание события должно соответствовать искомому событию');\r", + " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать искомому событию');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Число участников события должно соответствовать искомому событию');\r", + " pm.expect(pm.collectionVariables.get('confirmedRequests')).equal(0);\r", + " pm.expect(target.confirmedRequests).equal(1);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/events?users=0&states=PUBLISHED&categories=0&rangeStart=2022-01-06%2013%3A30%3A38&rangeEnd=2097-09-06%2013%3A30%3A38&from=0&size=1000", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events" + ], + "query": [ + { + "key": "users", + "value": "0", + "description": "список id пользователей, чьи события нужно найти" + }, + { + "key": "states", + "value": "PUBLISHED", + "description": "список состояний в которых находятся искомые события" + }, + { + "key": "categories", + "value": "0", + "description": "список id категорий в которых будет вестись поиск" + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие" + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие" + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе" + } + ] + }, + "description": "Эндпоинт возвращает полную информацию обо всех событиях подходящих под переданные условия" + }, + "response": [] + }, { "name": "Получение событий с возможностью фильтрации и проверкой на валидацию", "event": [ @@ -1570,7 +1830,7 @@ "response": [] }, { - "name": "Изменение даты события на уже наступившую", + "name": "Добавление запроса на участие при participantLimit == 0", "event": [ { "listen": "prerequest", @@ -1583,15 +1843,15 @@ " try {\r", " const user = await api.addUser(rnd.getUser());\r", " const category = await api.addCategory(rnd.getCategory());\r", - " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", - " pm.collectionVariables.set(\"eid\", event.id)\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify({\r", - " eventDate : \"2020-10-11 23:10:05\"\r", - " }),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true\r", + " eventBody['participantLimit'] = 0\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " pm.request.removeQueryParams(['eventId']);\r", + " pm.request.addQueryParams([`eventId=` + event.id]);\r", + " pm.collectionVariables.set('uid', submittedUser.id);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -1620,30 +1880,148 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", - " pm.response.to.have.status(400);\r", + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201); \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", "\r", - "" + "const target = pm.response.json();\r", + "var query = {};\r", + "pm.request.url.query.all().forEach((param) => { query[param.key] = param.value});\r", + "\r", + "pm.test(\"Запрос на участие должен содержать поля: id, requester, event, status, created\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('requester');\r", + "pm.expect(target).to.have.property('event');\r", + "pm.expect(target).to.have.property('status');\r", + "pm.expect(target).to.have.property('created');\r", + "});\r", + "\r", + "pm.test(\"При создании у запроса на участие должен быть статус CONFIRMED\", function () {\r", + " pm.expect(target.status).equal(\"CONFIRMED\");\r", + "});\r", + "\r", + "pm.test(\"Id ивента в запросе и в ответе должны совпадать\", function () {\r", + " pm.expect(target.event.toString()).equal(query['eventId'].toString());\r", + "});" ], "type": "text/javascript" } } ], "request": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{{request_body}}" - }, + "url": { + "raw": "{{baseUrl}}/users/:userId/requests?eventId=0", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "requests" + ], + "query": [ + { + "key": "eventId", + "value": "0", + "description": "(Required) id события" + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- нельзя добавить повторный запрос\n- инициатор события не может добавить запрос на участие в своём событии\n- нельзя участвовать в неопубликованном событии\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного" + }, + "response": [] + }, + { + "name": "Изменение даты события на уже наступившую", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " eventDate : \"2020-10-11 23:10:05\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.have.status(400);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, "url": { "raw": "{{baseUrl}}/admin/events/:eventId", "host": [ @@ -7980,165 +8358,3099 @@ ] } ] - } - ] - }, - { - "name": "409 Conflict", - "item": [ - { - "name": "Попытка изменения имени категории на уже существующее", - "event": [ - { - "listen": "prerequest", - "script": { - "exec": [ - "const main = async () => {\r", - " const api = new API(pm);\r", - " const rnd = new RandomUtils();\r", - " let category1, category2\r", - " try {\r", - " category1 = await api.addCategory(rnd.getCategory());\r", - " category2 = await api.addCategory(rnd.getCategory());\r", - " } catch(err) {\r", - " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", - " }\r", - " pm.collectionVariables.set(\"catid\", category2.id)\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify({\r", - " name : category1.name\r", - " }),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", - "};\r", - "\r", - "const interval = setInterval(() => {}, 1000);\r", - "\r", - "setTimeout(async () => \r", - " {\r", - " try {\r", - " await main();\r", - " } catch (e) {\r", - " console.error(e);\r", - " } finally {\r", - " clearInterval(interval);\r", - " }\r", - " }, \r", - " 100 \r", - ");" - ], - "type": "text/javascript" - } - }, - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", - " pm.response.to.have.status(409);\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/admin/categories/:catId", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "admin", - "categories", - ":catId" - ], - "variable": [ - { - "key": "catId", - "value": "{{catid}}" - } - ] - }, - "description": "Обратите внимание: имя категории должно быть уникальным" - }, - "response": [] }, { - "name": "Добавление новой категории с занятым именем", - "event": [ + "name": "Compilation", + "item": [ { - "listen": "prerequest", - "script": { - "exec": [ - "const main = async () => {\r", - " const api = new API(pm);\r", - " const rnd = new RandomUtils();\r", - "\r", - " let category;\r", - " try {\r", - " category = rnd.getCategory();\r", - " await api.addCategory(category);\r", - " } catch(err) {\r", - " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", - " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", - "};\r", - "\r", - "const interval = setInterval(() => {}, 1000);\r", - "\r", - "setTimeout(async () => \r", - " {\r", - " try {\r", - " await main();\r", - " } catch (e) {\r", - " console.error(e);\r", - " } finally {\r", - " clearInterval(interval);\r", - " }\r", - " }, \r", - " 100 \r", - ");" - ], - "type": "text/javascript" - } + "name": "Required query params", + "item": [] }, { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", - " pm.response.to.have.status(409);\r", + "name": "Unreqired params in body", + "item": [ + { + "name": "Получение подборок событий без нескольких Query params", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const compilation = await api.addCompilation(rnd.getCompilation());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "compilations" + ], + "query": [ + { + "key": "pinned", + "value": "true", + "description": "искать только закрепленные/не закрепленные подборки", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество элементов в наборе", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Добавление новой подборки без параметра pinned", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " compilation = rnd.getCompilation(event.id);\r", + " delete compilation['pinned'];\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Подборка должны содержать поля: id, title, pinned, events\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('pinned');\r", + "pm.expect(target).to.have.property('events');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(target.title).to.be.a(\"string\");\r", + " pm.expect(target.events).to.be.an(\"array\");\r", + " pm.expect(target.pinned).equal(false);\r", + "\r", + " pm.expect(source.events[0]).equal(target.events[0].id, 'Идентификаторы событий в подборке должен быть идентичен идентификаторам, указанным при создании подборки ');\r", + " pm.expect(source.title).equal(target.title, 'Название подборки должно соответствовать указанному при создании');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Required params in body", + "item": [ + { + "name": "Добавление подборки без поля title", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " compilation = {\r", + " \"pinned\":\"true\"};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + }, + { + "name": "Добавление подборки с пустым полем title", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " compilation = {\r", + " \"pinned\":\"true\",\r", + " \"title\": \"\"};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + }, + { + "name": "Добавление подборки с пустой строкой в качестве названия", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " compilation = {\r", + " \"pinned\":\"true\",\r", + " \"title\": \" \"};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Misc tests", + "item": [ + { + "name": "Добавление подборки с проверкой связей многие-ко-многим", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " let compilation2;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " const event2 = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " compilation = rnd.getCompilation(event.id, event2.id);\r", + " compilation2 = rnd.getCompilation(event.id, event2.id);\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/admin/compilations\",\r", + " method : \"POST\",\r", + " header: { \"Content-Type\": \"application/json\" },\r", + " body: JSON.stringify({\r", + " events: compilation2.events,\r", + " title: compilation2.title\r", + " })\r", + " }, (error, response) => {\r", + "\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Подборка должны содержать поля: id, title, pinned, events\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('pinned');\r", + "pm.expect(target).to.have.property('events');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(target.title).to.be.a(\"string\");\r", + " pm.expect(target.events).to.be.an(\"array\");\r", + " if (target.events[0].id < target.events[1].id){\r", + " pm.expect(source.events[0]).equal(target.events[0].id, 'Идентификаторы событий в подборке должен быть идентичен идентификаторам, указанным при создании подборки ');\r", + " } else {\r", + " pm.expect(source.events[0]).equal(target.events[1].id, 'Идентификаторы событий в подборке должен быть идентичен идентификаторам, указанным при создании подборки ');\r", + " }\r", + " pm.expect(source.title).equal(target.title, 'Название подборки должно соответствовать указанному при создании');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + }, + { + "name": "Добавление подборки без событий", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " compilation = rnd.getCompilation();\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Подборка должны содержать поля: id, title, pinned, events\", function () {\r", + " pm.expect(target).to.have.property('id');\r", + " pm.expect(target).to.have.property('title');\r", + " pm.expect(target).to.have.property('pinned');\r", + " pm.expect(target).to.have.property('events');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(target.title).to.be.a(\"string\");\r", + " pm.expect(target.events).to.be.an(\"array\");\r", + "\r", + " pm.expect(source.title).equal(target.title, 'Название подборки должно соответствовать указанному при создании');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "String length restrictions", + "item": [ + { + "name": "Добавление подборки с title.length > 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({title: rnd.getWord(51)}),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + }, + { + "name": "Добавление подборки с title.length == 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({title: rnd.getWord(50)}),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + }, + { + "name": "Обновить названия подборки с title.length > 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const compilation = await api.addCompilation(rnd.getCompilation());\r", + " pm.collectionVariables.set('compid', compilation.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " title: rnd.getWord(51)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations/:compId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations", + ":compId" + ], + "variable": [ + { + "key": "compId", + "value": "{{compid}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Обновить названия подборки с title.length == 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const compilation = await api.addCompilation(rnd.getCompilation());\r", + " pm.collectionVariables.set('compid', compilation.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " title: rnd.getWord(50)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.have.status(200);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations/:compId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations", + ":compId" + ], + "variable": [ + { + "key": "compId", + "value": "{{compid}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Default values check", + "item": [ + { + "name": "Добавление подборки без pinned", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let cArr = Array.from({length: 10}, () => rnd.getCompilation());\r", + " let responseArr = [];\r", + " try {\r", + " cArr.forEach(function(x){ delete x.pinned });\r", + " for (const c of cArr){\r", + " responseArr.push(await api.addCompilation(c));\r", + " }\r", + " pm.collectionVariables.set('responseArr', responseArr);\r", + " compilation = rnd.getCompilation();\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('responseArr');\r", + "\r", + "\r", + "pm.test(\"У каждой из созданных подборок pinned должно принять значение по умолчанию(false)\", function () {\r", + " source.forEach(function(x){pm.expect(x.pinned).to.be.equal(false)});\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + }, + { + "name": "Проверка на значения по-умолчанию from и size(compilation)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " for (let i = 0; i < 11; i++){\r", + " await api.addCompilation(rnd.getCompilation());\r", + " }\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/compilations?from=0\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {pm.collectionVariables.set('source', response.json())});\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('source');\r", + "\r", + "pm.test(\"Значение from по-умолчанию должно быть равным 0\", function () {\r", + " pm.expect(target[0].id).to.be.equal(source[0].id, 'Запросы с from=0 и без него должны начинаться с одного и того же события');\r", + "});\r", + "\r", + "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", + " pm.expect(target.length).to.be.equal(10);\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "compilations" + ], + "query": [ + { + "key": "pinned", + "value": "true", + "description": "искать только закрепленные/не закрепленные подборки", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество элементов в наборе", + "disabled": true + } + ] + } + }, + "response": [] + } + ] + } + ] + } + ] + }, + { + "name": "409 Conflict", + "item": [ + { + "name": "Попытка изменения имени категории на уже существующее", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category1, category2\r", + " try {\r", + " category1 = await api.addCategory(rnd.getCategory());\r", + " category2 = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", category2.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : category1.name\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Добавление новой категории с занятым именем", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = rnd.getCategory();\r", + " await api.addCategory(category);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Добавление пользователя с занятым именем почты", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(10);\r", + " await api.addUser(user);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Удаление категории с привязанными событиями", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const user = await api.addUser(rnd.getUser());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set('catid', category.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + }, + "description": "Обратите внимание: с категорий не должно быть связано ни одного события." + }, + "response": [] + }, + { + "name": "Изменение имени категории на уже занятое", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category1, category2\r", + " try {\r", + " category1 = await api.addCategory(rnd.getCategory());\r", + " category2 = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(category1.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : category2.name\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Публикация уже опубликованного события", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " stateAction : \"PUBLISH_EVENT\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Публикация отмененного события", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.rejectEvent(event.id);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " stateAction : \"PUBLISH_EVENT\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Отмена опубликованного события", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " stateAction : \"REJECT_EVENT\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Добавление повторного запроса от пользователя на участие в событии", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " await api.publishParticipationRequest(event.id, submittedUser.id);\r", + " pm.request.removeQueryParams(['eventId']);\r", + " pm.request.addQueryParams([`eventId=` + event.id]);\r", + " pm.collectionVariables.set('uid', submittedUser.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/requests?eventId=0", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "requests" + ], + "query": [ + { + "key": "eventId", + "value": "0", + "description": "(Required) id события" + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- нельзя добавить повторный запрос\n- инициатор события не может добавить запрос на участие в своём событии\n- нельзя участвовать в неопубликованном событии\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного" + }, + "response": [] + }, + { + "name": "Добавление запроса от инициатора события на участие в нём", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.request.removeQueryParams(['eventId']);\r", + " pm.request.addQueryParams([`eventId=` + event.id]);\r", + " pm.collectionVariables.set('uid', user.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/requests?eventId=0", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "requests" + ], + "query": [ + { + "key": "eventId", + "value": "0", + "description": "(Required) id события" + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- нельзя добавить повторный запрос\n- инициатор события не может добавить запрос на участие в своём событии\n- нельзя участвовать в неопубликованном событии\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного" + }, + "response": [] + }, + { + "name": "Добавление запроса на участие в неопубликованном событии", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " pm.request.removeQueryParams(['eventId']);\r", + " pm.request.addQueryParams([`eventId=` + event.id]);\r", + " pm.collectionVariables.set('uid', submittedUser.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/requests?eventId=0", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "requests" + ], + "query": [ + { + "key": "eventId", + "value": "0", + "description": "(Required) id события" + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- нельзя добавить повторный запрос\n- инициатор события не может добавить запрос на участие в своём событии\n- нельзя участвовать в неопубликованном событии\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного" + }, + "response": [] + }, + { + "name": "Добавление запроса на участие в событии, у которого заполнен лимит участников", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody.participantLimit = 1;\r", + " eventBody.requestModeration = false;\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser1 = await api.addUser(rnd.getUser());\r", + " const submittedUser2 = await api.addUser(rnd.getUser());\r", + " await api.publishParticipationRequest(event.id, submittedUser1.id);\r", + " pm.request.removeQueryParams(['eventId']);\r", + " pm.request.addQueryParams([`eventId=` + event.id]);\r", + " pm.collectionVariables.set('uid', submittedUser2.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/requests?eventId=0", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "requests" + ], + "query": [ + { + "key": "eventId", + "value": "0", + "description": "(Required) id события" + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- нельзя добавить повторный запрос\n- инициатор события не может добавить запрос на участие в своём событии\n- нельзя участвовать в неопубликованном событии\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного" + }, + "response": [] + }, + { + "name": "Изменение опубликованного события от имени пользователя", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.collectionVariables.set(\"response\", event);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " eventDate : rnd.getFutureDateTime(6)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Попытка принять заявку на участие в событии, когда лимит уже достигнут", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true;\r", + " eventBody['participantLimit'] = 1;\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser1 = await api.addUser(rnd.getUser());\r", + " const submittedUser2 = await api.addUser(rnd.getUser());\r", + " const requestToParticipate = await api.publishParticipationRequest(event.id, submittedUser1.id);\r", + " const requestToParticipate2 = await api.publishParticipationRequest(event.id, submittedUser2.id);\r", + " await api.acceptParticipationRequest(event.id, user.id, requestToParticipate.id);\r", + " pm.collectionVariables.set('uid', user.id);\r", + " pm.collectionVariables.set('eid', event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({requestIds: [requestToParticipate2.id],\r", + " status:\"CONFIRMED\"}),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId/requests", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId", + "requests" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- если для события лимит заявок равен 0 или отключена пре-модерация заявок, то подтверждение заявок не требуется\n- нельзя подтвердить заявку, если уже достигнут лимит по заявкам на данное событие\n- статус можно изменить только у заявок, находящихся в состоянии ожидания\n- если при подтверждении данной заявки, лимит заявок для события исчерпан, то все неподтверждённые заявки необходимо отклонить" + }, + "response": [] + }, + { + "name": "Попытка отменить уже принятую заявку на участие в событии", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true;\r", + " eventBody['participantLimit'] = 1;\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " const requestToParticipate = await api.publishParticipationRequest(event.id, submittedUser.id);\r", + " await api.acceptParticipationRequest(event.id, user.id, requestToParticipate.id);\r", + " pm.collectionVariables.set('uid', user.id);\r", + " pm.collectionVariables.set('eid', event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({requestIds: [requestToParticipate.id],\r", + " status:\"REJECTED\"}),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId/requests", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId", + "requests" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- если для события лимит заявок равен 0 или отключена пре-модерация заявок, то подтверждение заявок не требуется\n- нельзя подтвердить заявку, если уже достигнут лимит по заявкам на данное событие\n- статус можно изменить только у заявок, находящихся в состоянии ожидания\n- если при подтверждении данной заявки, лимит заявок для события исчерпан, то все неподтверждённые заявки необходимо отклонить" + }, + "response": [] + } + ] + }, + { + "name": "Category", + "item": [ + { + "name": "Добавление новой категории", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = rnd.getCategory();\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Получение категорий", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " pm.collectionVariables.set(\"response\", category)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json();\r", + "let founded;\r", + "target.forEach(function(element){if (element.id == source.id) founded = element});\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target[0]).to.have.property('id');\r", + "pm.expect(target[0]).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.id).equal(founded.id, 'Идентификатор категории должен соответствовать идентификатору категории добавленной ранее');\r", + " pm.expect(source.name).equal(founded.name, 'Название категории должно соответствовать названию категории добавленной ранее');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/categories?from=0&size=1000", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "categories" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество категорий, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество категорий в наборе" + } + ] + } + }, + "response": [] + }, + { + "name": "Получение информации о категории по её идентификатору", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " pm.collectionVariables.set(\"response\", category)\r", + " pm.collectionVariables.set(\"catid\", category.id)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор категории должен соответствовать идентификатору в запросе');\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно соответствовать названию категории с указанным идентификатором');\r", "});" ], "type": "text/javascript" @@ -8146,42 +11458,131 @@ } ], "request": { - "method": "POST", + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}", + "description": "(Required) id категории" + } + ] + } + }, + "response": [] + }, + { + "name": "Удаление категории", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const findedCategory = await api.findCategory(category.id);\r", + " pm.collectionVariables.set(\"catid\", category.id)\r", + " pm.collectionVariables.set(\"response\", findedCategory)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 204\", function () {\r", + " pm.response.to.have.status(204);\r", + "});\r", + "\r", + "source = pm.collectionVariables.get('response');\r", + "catId = pm.collectionVariables.get('catid');\r", + "\r", + "pm.test(\"Категория должна быть найдена до удаления\", function () {\r", + " pm.expect(source.id).equal(catId, 'Идентификтор категории должен совпадать с удаляемым');\r", + "});\r", + "\r", + "pm.sendRequest({\r", + " url: pm.collectionVariables.get(\"baseUrl\") + \"/categories/\" + catId,\r", + " method: 'GET',\r", + " }, (error, response) => {\r", + " pm.test(\"Категория не должна быть найдена после удаления\", function () {\r", + " pm.expect(response.code).to.eql(404);\r", + " });\r", + " });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "{{baseUrl}}/admin/categories", + "raw": "{{baseUrl}}/admin/categories/:catId", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "categories" + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } ] }, - "description": "Обратите внимание: имя категории должно быть уникальным" + "description": "Обратите внимание: с категорий не должно быть связано ни одного события." }, "response": [] }, { - "name": "Добавление пользователя с занятым именем почты", + "name": "Изменение категории", "event": [ { "listen": "prerequest", @@ -8190,19 +11591,18 @@ "const main = async () => {\r", " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", - "\r", - " let user;\r", + " let category\r", " try {\r", - " user = rnd.getUser();\r", - " user.name = rnd.getWord(10);\r", - " await api.addUser(user);\r", + " category = await api.addCategory(rnd.getCategory());\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", + " pm.collectionVariables.set(\"catid\", Number(category.id))\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", + " raw: JSON.stringify({\r", + " name : rnd.getCategory().name\r", + " }),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -8229,10 +11629,23 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", - " pm.response.to.have.status(409);\r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", "});" ], "type": "text/javascript" @@ -8240,41 +11653,39 @@ } ], "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], + "method": "PATCH", + "header": [], "body": { "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{{request_body}}" }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/admin/categories/:catId", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "users" + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } ] } }, "response": [] - }, + } + ] + }, + { + "name": "Users", + "item": [ { - "name": "Удаление категории с привязанными событиями", + "name": "Поиск пользователей", "event": [ { "listen": "prerequest", @@ -8284,11 +11695,10 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", + " let compilation;\r", " try {\r", - " const category = await api.addCategory(rnd.getCategory());\r", " const user = await api.addUser(rnd.getUser());\r", - " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", - " pm.collectionVariables.set('catid', category.id);\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -8316,10 +11726,26 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", - " pm.response.to.have.status(409);\r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Пользователи должны содержать поля: id, name, email\", function () {\r", + " pm.expect(target[0]).to.have.property('id');\r", + " pm.expect(target[0]).to.have.property('name');\r", + " pm.expect(target[0]).to.have.property('email');\r", + "});\r", + "\r", + "pm.test(\"Должен быть найден только один пользователь по заданному фильтру\", function () {\r", + " pm.expect(target.length).to.eql(1);\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target[0].id).equal(pm.collectionVariables.get(\"uid\"));\r", "});" ], "type": "text/javascript" @@ -8327,7 +11753,7 @@ } ], "request": { - "method": "DELETE", + "method": "GET", "header": [ { "key": "Accept", @@ -8335,28 +11761,45 @@ } ], "url": { - "raw": "{{baseUrl}}/admin/categories/:catId", + "raw": "{{baseUrl}}/admin/users?ids={{uid}}", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "categories", - ":catId" + "users" ], - "variable": [ + "query": [ { - "key": "catId", - "value": "{{catid}}" + "key": "ids", + "value": "{{uid}}", + "description": "id пользователей" + }, + { + "key": "ids", + "value": "-10833646", + "description": "id пользователей", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "10", + "description": "количество элементов в наборе", + "disabled": true } ] - }, - "description": "Обратите внимание: с категорий не должно быть связано ни одного события." + } }, "response": [] }, { - "name": "Изменение имени категории на уже занятое", + "name": "Добавление нового пользователя", "event": [ { "listen": "prerequest", @@ -8365,19 +11808,17 @@ "const main = async () => {\r", " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", - " let category1, category2\r", + "\r", + " let user;\r", " try {\r", - " category1 = await api.addCategory(rnd.getCategory());\r", - " category2 = await api.addCategory(rnd.getCategory());\r", + " user = rnd.getUser();\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - " pm.collectionVariables.set(\"catid\", Number(category1.id))\r", + "\r", " pm.request.body.update({\r", " mode: 'raw',\r", - " raw: JSON.stringify({\r", - " name : category2.name\r", - " }),\r", + " raw: JSON.stringify(user),\r", " options: { raw: { language: 'json' } }\r", " });\r", "};\r", @@ -8404,10 +11845,25 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", - " pm.response.to.have.status(409);\r", + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Пользователь должен содержать поля: id, name, email\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "pm.expect(target).to.have.property('email');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Имя пользователя должно соответствовать отправленному в запросе');\r", + " pm.expect(source.email).equal(target.email, 'Почта пользователя должна соответствовать отправленной в запросе');\r", "});" ], "type": "text/javascript" @@ -8415,35 +11871,72 @@ } ], "request": { - "method": "PATCH", - "header": [], + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], "body": { "mode": "raw", - "raw": "{{request_body}}" + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } }, "url": { - "raw": "{{baseUrl}}/admin/categories/:catId", + "raw": "{{baseUrl}}/admin/users", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "categories", - ":catId" - ], - "variable": [ - { - "key": "catId", - "value": "{{catid}}" - } + "users" ] } }, "response": [] }, { - "name": "Публикация уже опубликованного события", + "name": "Удаление пользователя", "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 204\", function () {\r", + " pm.response.to.have.status(204);\r", + "});\r", + "const source = pm.collectionVariables.get('response');\r", + "const userId = pm.collectionVariables.get('uid');\r", + "\r", + "pm.test(\"Пользователь должен быть найден до выполнения запроса\", function(){\r", + " pm.expect(source.length).to.eql(1);\r", + " pm.expect(source[0].id).to.eql(userId);\r", + "});\r", + "let body\r", + "const req = {\r", + " url: \"http://localhost:8080/admin/users?ids=\" + pm.collectionVariables.get(\"uid\"),\r", + " method: \"GET\",\r", + " body: body == null ? \"\" : JSON.stringify(body),\r", + " header: { \"Content-Type\": \"application/json\" },\r", + " };\r", + "pm.sendRequest(req, (error, response) => {\r", + " pm.test(\"Пользователь должен быть удалён после выполнения запроса\", function(){\r", + " pm.expect(response.json().length).to.eql(0);\r", + " });\r", + "})" + ], + "type": "text/javascript" + } + }, { "listen": "prerequest", "script": { @@ -8452,19 +11945,12 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", + " let compilation;\r", " try {\r", " const user = await api.addUser(rnd.getUser());\r", - " const category = await api.addCategory(rnd.getCategory());\r", - " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", - " event = await api.publishEvent(event.id);\r", - " pm.collectionVariables.set(\"eid\", event.id)\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify({\r", - " stateAction : \"PUBLISH_EVENT\"\r", - " }),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", + " const foundedUser = await api.findUser(user.id);\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"response\", foundedUser)\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -8475,7 +11961,6 @@ "setTimeout(async () => \r", " {\r", " try {\r", - " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -8488,57 +11973,44 @@ ], "type": "text/javascript" } - }, - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", - " pm.response.to.have.status(409);\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});" - ], - "type": "text/javascript" - } } ], "request": { - "method": "PATCH", + "method": "DELETE", "header": [ { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{{request_body}}" - }, "url": { - "raw": "{{baseUrl}}/admin/events/:eventId", + "raw": "{{baseUrl}}/admin/users/:userId", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "events", - ":eventId" + "users", + ":userId" ], "variable": [ { - "key": "eventId", - "value": "{{eid}}", - "description": "(Required) id события" + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id пользователя" } ] - }, - "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + } }, "response": [] - }, + } + ] + }, + { + "name": "Event", + "item": [ { - "name": "Публикация отмененного события", + "name": "Добавление нового события", "event": [ { "listen": "prerequest", @@ -8548,22 +12020,21 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", + " let event;\r", " try {\r", " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", " const category = await api.addCategory(rnd.getCategory());\r", - " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", - " event = await api.rejectEvent(event.id);\r", - " pm.collectionVariables.set(\"eid\", event.id)\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify({\r", - " stateAction : \"PUBLISH_EVENT\"\r", - " }),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", + " event = rnd.getEvent(category.id);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -8571,7 +12042,6 @@ "setTimeout(async () => \r", " {\r", " try {\r", - " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -8589,10 +12059,42 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", - " pm.response.to.have.status(409);\r", + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201); \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(target.title).equal(source.title, 'Название события должно соответствовать названию события в запросе');\r", + " pm.expect(target.annotation).equal(source.annotation, 'Аннотация события должна соответствовать аннотации события в запросе');\r", + " pm.expect(target.paid.toString()).equal(source.paid.toString(), 'Стоимость события должна соответствовать стоимости события в запросе');\r", + " pm.expect(target.eventDate).equal(source.eventDate, 'Дата проведения события должна соответствовать дате проведения события в запросе');\r", + " pm.expect(target.description).equal(source.description, 'Описание события должно соответствовать описание события в запросе');\r", + " pm.expect(target.participantLimit.toString()).equal(source.participantLimit.toString(), 'Лимит участников события должно соответствовать лимиту участников события в запросе');\r", + " pm.expect(target.location.lat.toString()).equal(source.location.lat.toString(), 'Широта локации проведения события должна соответствовать широте локации проведения события в запросе');\r", + " pm.expect(target.location.lon.toString()).equal(source.location.lon.toString(), 'Долгота локации проведения события должна соответствовать долготе локации проведения события в запросе');\r", + " pm.expect(target.requestModeration.toString()).equal(source.requestModeration.toString(), 'Необходимость модерации события должна соответствовать необходимости модерации события в запросе');\r", "});" ], "type": "text/javascript" @@ -8600,8 +12102,12 @@ } ], "request": { - "method": "PATCH", + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" @@ -8609,32 +12115,37 @@ ], "body": { "mode": "raw", - "raw": "{{request_body}}" + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } }, "url": { - "raw": "{{baseUrl}}/admin/events/:eventId", + "raw": "{{baseUrl}}/users/:userId/events", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "events", - ":eventId" + "users", + ":userId", + "events" ], "variable": [ { - "key": "eventId", - "value": "{{eid}}", - "description": "(Required) id события" + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" } ] }, - "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" }, "response": [] }, { - "name": "Отмена опубликованного события", + "name": "Добавление запроса от текущего пользователя на участие в событии", "event": [ { "listen": "prerequest", @@ -8647,16 +12158,14 @@ " try {\r", " const user = await api.addUser(rnd.getUser());\r", " const category = await api.addCategory(rnd.getCategory());\r", - " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true\r", + " let event = await api.addEvent(user.id, eventBody);\r", " event = await api.publishEvent(event.id);\r", - " pm.collectionVariables.set(\"eid\", event.id)\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify({\r", - " stateAction : \"REJECT_EVENT\"\r", - " }),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " pm.request.removeQueryParams(['eventId']);\r", + " pm.request.addQueryParams([`eventId=` + event.id]);\r", + " pm.collectionVariables.set('uid', submittedUser.id);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -8685,10 +12194,30 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", - " pm.response.to.have.status(409);\r", + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201); \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "var query = {};\r", + "pm.request.url.query.all().forEach((param) => { query[param.key] = param.value});\r", + "\r", + "pm.test(\"Запрос на участие должен содержать поля: id, requester, event, status, created\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('requester');\r", + "pm.expect(target).to.have.property('event');\r", + "pm.expect(target).to.have.property('status');\r", + "pm.expect(target).to.have.property('created');\r", + "});\r", + "\r", + "pm.test(\"При создании у запроса на участие должен быть статус PENDING\", function () {\r", + " pm.expect(target.status).equal(\"PENDING\");\r", + "});\r", + "\r", + "pm.test(\"Id ивента в запросе и в ответе должны совпадать\", function () {\r", + " pm.expect(target.event.toString()).equal(query['eventId'].toString());\r", "});" ], "type": "text/javascript" @@ -8696,41 +12225,44 @@ } ], "request": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{{request_body}}" - }, "url": { - "raw": "{{baseUrl}}/admin/events/:eventId", + "raw": "{{baseUrl}}/users/:userId/requests?eventId=0", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "events", - ":eventId" + "users", + ":userId", + "requests" ], - "variable": [ + "query": [ { "key": "eventId", - "value": "{{eid}}", + "value": "0", "description": "(Required) id события" } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] }, - "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + "description": "Обратите внимание:\n- нельзя добавить повторный запрос\n- инициатор события не может добавить запрос на участие в своём событии\n- нельзя участвовать в неопубликованном событии\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного" }, "response": [] }, { - "name": "Изменение опубликованного события от имени пользователя", + "name": "Поиск событий", "event": [ { "listen": "prerequest", @@ -8745,16 +12277,9 @@ " const category = await api.addCategory(rnd.getCategory());\r", " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", " event = await api.publishEvent(event.id);\r", - " pm.collectionVariables.set(\"uid\", user.id);\r", - " pm.collectionVariables.set(\"eid\", event.id);\r", - " pm.collectionVariables.set(\"response\", event);\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify({\r", - " eventDate : rnd.getFutureDateTime(6)\r", - " }),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", + " pm.request.removeQueryParams(['users', 'categories']);\r", + " pm.request.addQueryParams([`users=` + user.id, 'categories=' + category.id]);\r", + " pm.collectionVariables.set('response', event);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -8765,6 +12290,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -8781,11 +12307,43 @@ { "listen": "test", "script": { - "exec": [ - "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", - " pm.response.to.have.status(409);\r", + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json()[0];\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('views');\r", + "pm.expect(target).to.have.property('confirmedRequests');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('publishedOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать искомому событию');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Идентификатор категории должен соответствовать искомой категории');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость посещения события должна соответствовать искомому событию');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать дате искомого события');\r", + " pm.expect(source.description).equal(target.description, 'Описание события должно соответствовать искомому событию');\r", + " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать искомому событию');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Число участников события должно соответствовать искомому событию');\r", "});" ], "type": "text/javascript" @@ -8793,61 +12351,66 @@ } ], "request": { - "method": "PATCH", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "raw": "{{baseUrl}}/admin/events?users=0&states=PUBLISHED&categories=0&rangeStart=2022-01-06%2013%3A30%3A38&rangeEnd=2097-09-06%2013%3A30%3A38&from=0&size=1000", "host": [ "{{baseUrl}}" ], "path": [ - "users", - ":userId", - "events", - ":eventId" + "admin", + "events" ], - "variable": [ + "query": [ { - "key": "userId", - "value": "{{uid}}", - "description": "(Required) id текущего пользователя" + "key": "users", + "value": "0", + "description": "список id пользователей, чьи события нужно найти" }, { - "key": "eventId", - "value": "{{eid}}", - "description": "(Required) id отменяемого события" + "key": "states", + "value": "PUBLISHED", + "description": "список состояний в которых находятся искомые события" + }, + { + "key": "categories", + "value": "0", + "description": "список id категорий в которых будет вестись поиск" + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие" + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие" + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе" } ] }, - "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + "description": "Эндпоинт возвращает полную информацию обо всех событиях подходящих под переданные условия" }, "response": [] - } - ] - }, - { - "name": "Category", - "item": [ + }, { - "name": "Добавление новой категории", + "name": "Получение событий, добавленных текущим пользователем", "event": [ { "listen": "prerequest", @@ -8857,18 +12420,14 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let category;\r", " try {\r", - " category = rnd.getCategory();\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(category),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -8893,23 +12452,20 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", - " pm.response.to.have.status(201);\r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", "\r", - "const source = JSON.parse(pm.request.body.raw);\r", - "const target = pm.response.json();\r", + "const target = pm.response.json()[0];\r", "\r", - "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", - "pm.expect(target).to.have.property('id');\r", - "pm.expect(target).to.have.property('name');\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate\", function () {\r", + " pm.expect(target).to.contain.keys('id', 'title', 'annotation', 'category', 'paid', 'eventDate');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", " pm.expect(target.id).to.not.be.null;\r", - " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", "});" ], "type": "text/javascript" @@ -8917,42 +12473,48 @@ } ], "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "{{baseUrl}}/admin/categories", + "raw": "{{baseUrl}}/users/:userId/events?from=0&size=1000", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "categories" + "users", + ":userId", + "events" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество элементов в наборе" + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } ] - }, - "description": "Обратите внимание: имя категории должно быть уникальным" + } }, "response": [] }, { - "name": "Получение категорий", + "name": "Получение событий с возможностью фильтрации", "event": [ { "listen": "prerequest", @@ -8963,8 +12525,13 @@ " const rnd = new RandomUtils();\r", "\r", " try {\r", + " const user = await api.addUser(rnd.getUser());\r", " const category = await api.addCategory(rnd.getCategory());\r", - " pm.collectionVariables.set(\"response\", category)\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.request.removeQueryParams(['text', 'categories', 'paid']);\r", + " pm.request.addQueryParams([`text=` + event.annotation, 'categories=' + category.id, 'paid=' + event.paid]);\r", + " pm.collectionVariables.set('response', event);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -8975,6 +12542,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -8999,18 +12567,26 @@ "});\r", "\r", "const source = pm.collectionVariables.get('response');\r", - "const target = pm.response.json();\r", - "let founded;\r", - "target.forEach(function(element){if (element.id == source.id) founded = element});\r", + "const target = pm.response.json()[0];\r", "\r", - "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", - "pm.expect(target[0]).to.have.property('id');\r", - "pm.expect(target[0]).to.have.property('name');\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('views');\r", + "pm.expect(target).to.have.property('confirmedRequests');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(source.id).equal(founded.id, 'Идентификатор категории должен соответствовать идентификатору категории добавленной ранее');\r", - " pm.expect(source.name).equal(founded.name, 'Название категории должно соответствовать названию категории добавленной ранее');\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать аннотации события с указанным идентификатором');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Категория события должна соответствовать категории события с указанным идентификатором');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость события должна соответствовать стоимости события с указанным идентификатором');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать дате проведения события с указанным идентификатором');\r", + " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать названию события с указанным идентификатором');\r", "});" ], "type": "text/javascript" @@ -9026,31 +12602,67 @@ } ], "url": { - "raw": "{{baseUrl}}/categories?from=0&size=1000", + "raw": "{{baseUrl}}/events?text=0&categories=0&paid=true&rangeStart=2022-01-06%2013%3A30%3A38&rangeEnd=2097-09-06%2013%3A30%3A38&onlyAvailable=false&sort=EVENT_DATE&from=0&size=1000", "host": [ "{{baseUrl}}" ], "path": [ - "categories" + "events" ], "query": [ + { + "key": "text", + "value": "0", + "description": "текст для поиска в содержимом аннотации и подробном описании события" + }, + { + "key": "categories", + "value": "0", + "description": "список идентификаторов категорий в которых будет вестись поиск" + }, + { + "key": "paid", + "value": "true", + "description": "поиск только платных/бесплатных событий" + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие" + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие" + }, + { + "key": "onlyAvailable", + "value": "false", + "description": "только события у которых не исчерпан лимит запросов на участие" + }, + { + "key": "sort", + "value": "EVENT_DATE", + "description": "Вариант сортировки: по дате события или по количеству просмотров" + }, { "key": "from", "value": "0", - "description": "количество категорий, которые нужно пропустить для формирования текущего набора" + "description": "количество событий, которые нужно пропустить для формирования текущего набора" }, { "key": "size", "value": "1000", - "description": "количество категорий в наборе" + "description": "количество событий в наборе" } ] - } + }, + "description": "Обратите внимание: \n- это публичный эндпоинт, соответственно в выдаче должны быть только опубликованные события\n- текстовый поиск (по аннотации и подробному описанию) должен быть без учета регистра букв\n- если в запросе не указан диапазон дат [rangeStart-rangeEnd], то нужно выгружать события, которые произойдут позже текущей даты и времени\n- информация о каждом событии должна включать в себя количество просмотров и количество уже одобренных заявок на участие\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" }, "response": [] }, { - "name": "Получение информации о категории по её идентификатору", + "name": "Получение подробной информации об опубликованном событии по его идентификатору", "event": [ { "listen": "prerequest", @@ -9061,9 +12673,12 @@ " const rnd = new RandomUtils();\r", "\r", " try {\r", + " const user = await api.addUser(rnd.getUser());\r", " const category = await api.addCategory(rnd.getCategory());\r", - " pm.collectionVariables.set(\"response\", category)\r", - " pm.collectionVariables.set(\"catid\", category.id)\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.collectionVariables.set('response', event);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -9074,6 +12689,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -9100,14 +12716,33 @@ "const source = pm.collectionVariables.get('response');\r", "const target = pm.response.json();\r", "\r", - "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", "pm.expect(target).to.have.property('id');\r", - "pm.expect(target).to.have.property('name');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('views');\r", + "pm.expect(target).to.have.property('confirmedRequests');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('publishedOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(source.id).equal(target.id, 'Идентификатор категории должен соответствовать идентификатору в запросе');\r", - " pm.expect(source.name).equal(target.name, 'Название категории должно соответствовать названию категории с указанным идентификатором');\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать аннотации события с указанным идентификатором');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Категория события должна соответствовать категории события с указанным идентификатором');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость события должна соответствовать стоимости события с указанным идентификатором');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать дате проведения события с указанным идентификатором');\r", + " pm.expect(source.description).equal(target.description, 'Описание события должно соответствовать описанию события с указанным идентификатором');\r", + " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать названию события с указанным идентификатором');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Лимит участников события должен соответствовать лимиту участников события с указанным идентификатором');\r", "});" ], "type": "text/javascript" @@ -9123,27 +12758,28 @@ } ], "url": { - "raw": "{{baseUrl}}/categories/:catId", + "raw": "{{baseUrl}}/events/:id", "host": [ "{{baseUrl}}" ], "path": [ - "categories", - ":catId" + "events", + ":id" ], "variable": [ { - "key": "catId", - "value": "{{catid}}", - "description": "(Required) id категории" + "key": "id", + "value": "{{eid}}", + "description": "(Required) id события" } ] - } + }, + "description": "Обратите внимание:\n- событие должно быть опубликовано\n- информация о событии должна включать в себя количество просмотров и количество подтвержденных запросов\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" }, "response": [] }, { - "name": "Удаление категории", + "name": "Получение полной информации о событии добавленном текущим пользователем", "event": [ { "listen": "prerequest", @@ -9154,10 +12790,11 @@ " const rnd = new RandomUtils();\r", "\r", " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", " const category = await api.addCategory(rnd.getCategory());\r", - " const findedCategory = await api.findCategory(category.id);\r", - " pm.collectionVariables.set(\"catid\", category.id)\r", - " pm.collectionVariables.set(\"response\", findedCategory)\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -9185,32 +12822,40 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 204\", function () {\r", - " pm.response.to.have.status(204);\r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", "});\r", "\r", - "source = pm.collectionVariables.get('response');\r", - "catId = pm.collectionVariables.get('catid');\r", + "const target = pm.response.json();\r", "\r", - "pm.test(\"Категория должна быть найдена до удаления\", function () {\r", - " pm.expect(source.id).equal(catId, 'Идентификтор категории должен совпадать с удаляемым');\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", "});\r", "\r", - "pm.sendRequest({\r", - " url: pm.collectionVariables.get(\"baseUrl\") + \"/categories/\" + catId,\r", - " method: 'GET',\r", - " }, (error, response) => {\r", - " pm.test(\"Категория не должна быть найдена после удаления\", function () {\r", - " pm.expect(response.code).to.eql(404);\r", - " });\r", - " });" + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + "});" ], "type": "text/javascript" } } ], "request": { - "method": "DELETE", + "method": "GET", "header": [ { "key": "Accept", @@ -9218,28 +12863,34 @@ } ], "url": { - "raw": "{{baseUrl}}/admin/categories/:catId", + "raw": "{{baseUrl}}/users/:userId/events/:eventId", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "categories", - ":catId" + "users", + ":userId", + "events", + ":eventId" ], "variable": [ { - "key": "catId", - "value": "{{catid}}" + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" } ] - }, - "description": "Обратите внимание: с категорий не должно быть связано ни одного события." + } }, "response": [] }, { - "name": "Изменение категории", + "name": "Получение информации о заявках текущего пользователя на участие в чужих событиях", "event": [ { "listen": "prerequest", @@ -9248,20 +12899,22 @@ "const main = async () => {\r", " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", - " let category\r", + "\r", " try {\r", - " category = await api.addCategory(rnd.getCategory());\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " const requestToJoin = await api.publishParticipationRequest(event.id, submittedUser.id);\r", + " pm.collectionVariables.set('response', requestToJoin);\r", + " pm.collectionVariables.set('uid', submittedUser.id);\r", + "\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - " pm.collectionVariables.set(\"catid\", Number(category.id))\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify({\r", - " name : rnd.getCategory().name\r", - " }),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -9269,6 +12922,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -9292,17 +12946,22 @@ " pm.response.to.be.json;\r", "});\r", "\r", - "const source = JSON.parse(pm.request.body.raw);\r", - "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json()[0];\r", "\r", - "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.test(\"Запрос на участие должен содержать поля: id, requester, event, status, created\", function () {\r", "pm.expect(target).to.have.property('id');\r", - "pm.expect(target).to.have.property('name');\r", + "pm.expect(target).to.have.property('requester');\r", + "pm.expect(target).to.have.property('event');\r", + "pm.expect(target).to.have.property('status');\r", + "pm.expect(target).to.have.property('created');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(target.id).to.not.be.null;\r", - " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор запроса на участие в событии должен соответствовать идентификатору запроса, созданного ранее');\r", + " pm.expect(source.requester).equal(target.requester, 'Пользователя, запрашивающий доступ на участие в событии, должен соответствовать указанному пользователю');\r", + " pm.expect(source.event).equal(target.event, 'Событие, доступ к которому запрашивает пользователь, должно соответствовать событию, доступ к которому пользователь запрашивал доступ ранее');\r", + " pm.expect(source.created).equal(target.created, 'Время создания запроса на участие в событии должно соответствовать времени создания запроса, созданного ранее указанным пользователем');\r", "});" ], "type": "text/javascript" @@ -9310,39 +12969,36 @@ } ], "request": { - "method": "PATCH", - "header": [], - "body": { - "mode": "raw", - "raw": "{{request_body}}" - }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], "url": { - "raw": "{{baseUrl}}/admin/categories/:catId", + "raw": "{{baseUrl}}/users/:userId/requests", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "categories", - ":catId" + "users", + ":userId", + "requests" ], "variable": [ { - "key": "catId", - "value": "{{catid}}" + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" } ] } }, "response": [] - } - ] - }, - { - "name": "Users", - "item": [ + }, { - "name": "Поиск пользователей", + "name": "Получение информации о запросах на участие в событии текущего пользователя", "event": [ { "listen": "prerequest", @@ -9352,10 +13008,19 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let compilation;\r", " try {\r", " const user = await api.addUser(rnd.getUser());\r", - " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " const requestToJoin = await api.publishParticipationRequest(event.id, submittedUser.id);\r", + " pm.collectionVariables.set('response', requestToJoin);\r", + " pm.collectionVariables.set('uid', user.id);\r", + " pm.collectionVariables.set('eid', event.id);\r", + "\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -9366,6 +13031,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -9389,20 +13055,22 @@ " pm.response.to.be.json;\r", "});\r", "\r", - "const target = pm.response.json();\r", - "\r", - "pm.test(\"Пользователи должны содержать поля: id, name, email\", function () {\r", - " pm.expect(target[0]).to.have.property('id');\r", - " pm.expect(target[0]).to.have.property('name');\r", - " pm.expect(target[0]).to.have.property('email');\r", - "});\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json()[0];\r", "\r", - "pm.test(\"Должен быть найден только один пользователь по заданному фильтру\", function () {\r", - " pm.expect(target.length).to.eql(1);\r", + "pm.test(\"Запрос на участие должен содержать поля: id, requester, event, status, created\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('requester');\r", + "pm.expect(target).to.have.property('event');\r", + "pm.expect(target).to.have.property('status');\r", + "pm.expect(target).to.have.property('created');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(target[0].id).equal(pm.collectionVariables.get(\"uid\"));\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор запроса на участие в событии должен соответствовать идентификатору запроса на участие в событии указанного пользователя');\r", + " pm.expect(source.requester).equal(target.requester, 'Автор запроса на участие в событии должен соответствовать указанному пользователю');\r", + " pm.expect(source.event).equal(target.event, 'Событие в ответе должно соответствовать событию с запросом на участие от указанного пользователя');\r", + " pm.expect(source.created).equal(target.created, 'Время создания запроса на участие в событии должно соответствовать времени создания запроса на участие в событии указанного пользователя');\r", "});" ], "type": "text/javascript" @@ -9418,37 +13086,27 @@ } ], "url": { - "raw": "{{baseUrl}}/admin/users?ids={{uid}}", + "raw": "{{baseUrl}}/users/:userId/events/:eventId/requests", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "users" + "users", + ":userId", + "events", + ":eventId", + "requests" ], - "query": [ + "variable": [ { - "key": "ids", + "key": "userId", "value": "{{uid}}", - "description": "id пользователей" - }, - { - "key": "ids", - "value": "-10833646", - "description": "id пользователей", - "disabled": true - }, - { - "key": "from", - "value": "0", - "description": "количество элементов, которые нужно пропустить для формирования текущего набора", - "disabled": true + "description": "(Required) id текущего пользователя" }, { - "key": "size", - "value": "10", - "description": "количество элементов в наборе", - "disabled": true + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" } ] } @@ -9456,7 +13114,7 @@ "response": [] }, { - "name": "Добавление нового пользователя", + "name": "Редактирование данных события и его статуса (отклонение/публикация).", "event": [ { "listen": "prerequest", @@ -9466,18 +13124,22 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let user;\r", " try {\r", - " user = rnd.getUser();\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.stateAction = \"PUBLISH_EVENT\"\r", + " pm.collectionVariables.set('response', event2);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(user),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -9485,6 +13147,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -9502,25 +13165,39 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", - " pm.response.to.have.status(201);\r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", "\r", - "const source = JSON.parse(pm.request.body.raw);\r", + "const source = pm.collectionVariables.get('response');\r", "const target = pm.response.json();\r", "\r", - "pm.test(\"Пользователь должен содержать поля: id, name, email\", function () {\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", "pm.expect(target).to.have.property('id');\r", - "pm.expect(target).to.have.property('name');\r", - "pm.expect(target).to.have.property('email');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('publishedOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(target.id).to.not.be.null;\r", - " pm.expect(source.name).equal(target.name, 'Имя пользователя должно соответствовать отправленному в запросе');\r", - " pm.expect(source.email).equal(target.email, 'Почта пользователя должна соответствовать отправленной в запросе');\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать искомому событию');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость события должна соответствовать искомому событию');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать искомому событию');\r", + " pm.expect(source.description).equal(target.description, 'Описание события должно соответствовать искомому событию');\r", + " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать искомому событию');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Лимит участников события должен соответствовать искомому событию');\r", "});" ], "type": "text/javascript" @@ -9528,12 +13205,8 @@ } ], "request": { - "method": "POST", + "method": "PATCH", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" @@ -9541,59 +13214,33 @@ ], "body": { "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{{request_body}}" }, "url": { - "raw": "{{baseUrl}}/admin/users", + "raw": "{{baseUrl}}/admin/events/:eventId", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "users" + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } ] - } + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" }, "response": [] }, { - "name": "Удаление пользователя", + "name": "Изменение события добавленного текущим пользователем", "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Ответ должен содержать код статуса 204\", function () {\r", - " pm.response.to.have.status(204);\r", - "});\r", - "const source = pm.collectionVariables.get('response');\r", - "const userId = pm.collectionVariables.get('uid');\r", - "\r", - "pm.test(\"Пользователь должен быть найден до выполнения запроса\", function(){\r", - " pm.expect(source.length).to.eql(1);\r", - " pm.expect(source[0].id).to.eql(userId);\r", - "});\r", - "let body\r", - "const req = {\r", - " url: \"http://localhost:8080/admin/users?ids=\" + pm.collectionVariables.get(\"uid\"),\r", - " method: \"GET\",\r", - " body: body == null ? \"\" : JSON.stringify(body),\r", - " header: { \"Content-Type\": \"application/json\" },\r", - " };\r", - "pm.sendRequest(req, (error, response) => {\r", - " pm.test(\"Пользователь должен быть удалён после выполнения запроса\", function(){\r", - " pm.expect(response.json().length).to.eql(0);\r", - " });\r", - "})" - ], - "type": "text/javascript" - } - }, { "listen": "prerequest", "script": { @@ -9602,12 +13249,20 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let compilation;\r", " try {\r", " const user = await api.addUser(rnd.getUser());\r", - " const foundedUser = await api.findUser(user.id);\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", " pm.collectionVariables.set(\"uid\", user.id);\r", - " pm.collectionVariables.set(\"response\", foundedUser)\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.collectionVariables.set(\"response\", event);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " stateAction: \"CANCEL_REVIEW\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -9630,44 +13285,108 @@ ], "type": "text/javascript" } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get(\"response\");\r", + "const target = pm.response.json();\r", + "\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация отменённого события должна соответствовать аннотации события до отмены');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Категория отменённого события должна соответствовать категории события до отмены');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость отменённого события должна соответствовать стоимости события до отмены');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения отменённого события должна соответствовать дате проведения события до отмены');\r", + " pm.expect(source.description).equal(target.description, 'Описание отменённого события должно соответствовать описанию события до отмены');\r", + " pm.expect(source.title).equal(target.title, 'Название отменённого события должно соответствовать названию события до отмены');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Лимит участников отменённого события должен соответствовать лимиту участников события до отмены');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость отменённого события должна соответствовать стоимости события до отмены');\r", + "});\r", + "\r", + "pm.test(\"Событие должно иметь статус PENDING при создании и статус CANCELED после выполнения запроса\", function () {\r", + " pm.expect(source.state).equal(\"PENDING\");\r", + " pm.expect(target.state).equal(\"CANCELED\");\r", + "});" + ], + "type": "text/javascript" + } } ], "request": { - "method": "DELETE", + "method": "PATCH", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { - "raw": "{{baseUrl}}/admin/users/:userId", + "raw": "{{baseUrl}}/users/:userId/events/:eventId", "host": [ "{{baseUrl}}" ], "path": [ - "admin", "users", - ":userId" + ":userId", + "events", + ":eventId" ], "variable": [ { "key": "userId", "value": "{{uid}}", - "description": "(Required) id пользователя" + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" } ] - } + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." }, "response": [] - } - ] - }, - { - "name": "Event", - "item": [ + }, { - "name": "Добавление нового события", + "name": "Отмена своего запроса на участие в событии", "event": [ { "listen": "prerequest", @@ -9677,21 +13396,22 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", - " let event;\r", " try {\r", " const user = await api.addUser(rnd.getUser());\r", - " pm.collectionVariables.set(\"uid\", user.id)\r", " const category = await api.addCategory(rnd.getCategory());\r", - " event = rnd.getEvent(category.id);\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " const requestToJoin = await api.publishParticipationRequest(event.id, submittedUser.id);\r", + " pm.collectionVariables.set('response', requestToJoin);\r", + " pm.collectionVariables.set('uid', submittedUser.id);\r", + " pm.collectionVariables.set('reqid', requestToJoin.id);\r", + "\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", - "\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: JSON.stringify(event),\r", - " options: { raw: { language: 'json' } }\r", - " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -9699,6 +13419,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -9716,42 +13437,33 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", - " pm.response.to.have.status(201); \r", + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", "\r", - "const source = JSON.parse(pm.request.body.raw);\r", "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('response');\r", "\r", - "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, location, requestModeration\", function () {\r", + "pm.test(\"Запрос на участие должен содержать поля: id, requester, event, status, created\", function () {\r", "pm.expect(target).to.have.property('id');\r", - "pm.expect(target).to.have.property('title');\r", - "pm.expect(target).to.have.property('annotation');\r", - "pm.expect(target).to.have.property('category');\r", - "pm.expect(target).to.have.property('paid');\r", - "pm.expect(target).to.have.property('eventDate');\r", - "pm.expect(target).to.have.property('initiator');\r", - "pm.expect(target).to.have.property('description');\r", - "pm.expect(target).to.have.property('participantLimit');\r", - "pm.expect(target).to.have.property('state');\r", - "pm.expect(target).to.have.property('createdOn');\r", - "pm.expect(target).to.have.property('location');\r", - "pm.expect(target).to.have.property('requestModeration');\r", + "pm.expect(target).to.have.property('requester');\r", + "pm.expect(target).to.have.property('event');\r", + "pm.expect(target).to.have.property('status');\r", + "pm.expect(target).to.have.property('created');\r", + "});\r", + "\r", + "pm.test(\"При создании у запроса на участие должен быть статус PENDING, а при удалении - CANCELED\", function () {\r", + " pm.expect(source.status).equal(\"PENDING\");\r", + " pm.expect(target.status).equal(\"CANCELED\");\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(target.id).to.not.be.null;\r", - " pm.expect(target.title).equal(source.title, 'Название события должно соответствовать названию события в запросе');\r", - " pm.expect(target.annotation).equal(source.annotation, 'Аннотация события должна соответствовать аннотации события в запросе');\r", - " pm.expect(target.paid.toString()).equal(source.paid.toString(), 'Стоимость события должна соответствовать стоимости события в запросе');\r", - " pm.expect(target.eventDate).equal(source.eventDate, 'Дата проведения события должна соответствовать дате проведения события в запросе');\r", - " pm.expect(target.description).equal(source.description, 'Описание события должно соответствовать описание события в запросе');\r", - " pm.expect(target.participantLimit.toString()).equal(source.participantLimit.toString(), 'Лимит участников события должно соответствовать лимиту участников события в запросе');\r", - " pm.expect(target.location.lat.toString()).equal(source.location.lat.toString(), 'Широта локации проведения события должна соответствовать широте локации проведения события в запросе');\r", - " pm.expect(target.location.lon.toString()).equal(source.location.lon.toString(), 'Долгота локации проведения события должна соответствовать долготе локации проведения события в запросе');\r", - " pm.expect(target.requestModeration.toString()).equal(source.requestModeration.toString(), 'Необходимость модерации события должна соответствовать необходимости модерации события в запросе');\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор отменённого запроса на участие в событии должен соответствовать идентификатору запроса до отмены');\r", + " pm.expect(source.requester).equal(target.requester, 'Пользователь, отменяющий запрос на участие в событии, должен соответствовать текущему пользователю');\r", + " pm.expect(source.event).equal(target.event, 'Событие отменённого запроса на участие должно соответствовать запросу на участие в событии до отмены');\r", + " pm.expect(source.created).equal(target.created, 'Дата создания отменённого запроса на участие в событии должна соответствовать дате создания запроса до отмены');\r", "});" ], "type": "text/javascript" @@ -9759,50 +13471,43 @@ } ], "request": { - "method": "POST", + "method": "PATCH", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "{{baseUrl}}/users/:userId/events", + "raw": "{{baseUrl}}/users/:userId/requests/:requestId/cancel", "host": [ "{{baseUrl}}" ], "path": [ "users", ":userId", - "events" + "requests", + ":requestId", + "cancel" ], "variable": [ { "key": "userId", "value": "{{uid}}", "description": "(Required) id текущего пользователя" + }, + { + "key": "requestId", + "value": "{{reqid}}", + "description": "(Required) id запроса на участие" } ] - }, - "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + } }, "response": [] }, { - "name": "Поиск событий", + "name": "Отклонение запроса на участие в событии", "event": [ { "listen": "prerequest", @@ -9815,11 +13520,21 @@ " try {\r", " const user = await api.addUser(rnd.getUser());\r", " const category = await api.addCategory(rnd.getCategory());\r", - " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true\r", + " let event = await api.addEvent(user.id, eventBody);\r", " event = await api.publishEvent(event.id);\r", - " pm.request.removeQueryParams(['users', 'categories']);\r", - " pm.request.addQueryParams([`users=` + user.id, 'categories=' + category.id]);\r", - " pm.collectionVariables.set('response', event);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " const requestToJoin = await api.publishParticipationRequest(event.id, submittedUser.id);\r", + " pm.collectionVariables.set('response', requestToJoin);\r", + " pm.collectionVariables.set('uid', user.id);\r", + " pm.collectionVariables.set('eid', event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({requestIds: [requestToJoin.id],\r", + " status:\"REJECTED\"}),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -9855,35 +13570,26 @@ "});\r", "\r", "const source = pm.collectionVariables.get('response');\r", - "const target = pm.response.json()[0];\r", + "const target = pm.response.json()[\"rejectedRequests\"][0];\r", "\r", - "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", + "pm.test(\"Запрос на участие должен содержать поля: id, requester, event, status, created\", function () {\r", "pm.expect(target).to.have.property('id');\r", - "pm.expect(target).to.have.property('title');\r", - "pm.expect(target).to.have.property('annotation');\r", - "pm.expect(target).to.have.property('category');\r", - "pm.expect(target).to.have.property('paid');\r", - "pm.expect(target).to.have.property('eventDate');\r", - "pm.expect(target).to.have.property('initiator');\r", - "pm.expect(target).to.have.property('views');\r", - "pm.expect(target).to.have.property('confirmedRequests');\r", - "pm.expect(target).to.have.property('description');\r", - "pm.expect(target).to.have.property('participantLimit');\r", - "pm.expect(target).to.have.property('state');\r", - "pm.expect(target).to.have.property('createdOn');\r", - "pm.expect(target).to.have.property('publishedOn');\r", - "pm.expect(target).to.have.property('location');\r", - "pm.expect(target).to.have.property('requestModeration');\r", + "pm.expect(target).to.have.property('requester');\r", + "pm.expect(target).to.have.property('event');\r", + "pm.expect(target).to.have.property('status');\r", + "pm.expect(target).to.have.property('created');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать искомому событию');\r", - " pm.expect(source.category.id).equal(target.category.id, 'Идентификатор категории должен соответствовать искомой категории');\r", - " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость посещения события должна соответствовать искомому событию');\r", - " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать дате искомого события');\r", - " pm.expect(source.description).equal(target.description, 'Описание события должно соответствовать искомому событию');\r", - " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать искомому событию');\r", - " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Число участников события должно соответствовать искомому событию');\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор запроса на участие в событии должен соответствовать идентификатору запроса на участие в событии указанного пользователя');\r", + " pm.expect(source.requester).equal(target.requester, 'Пользователь, запрашивающий доступ на участие в событии должен пользователю, отправившему запрос на участие в событии указанного пользователя ранее');\r", + " pm.expect(source.event).equal(target.event, 'Событие, запрос на участие в котором надо подтвердить, должно соответствовать событию указанного пользователя');\r", + " pm.expect(source.created).equal(target.created, 'Время создания запроса на участие в событии после подтверждения должно соответствовать времени создания запроса на участие в событии указанного пользователя до подтверждения');\r", + "});\r", + "\r", + "pm.test(\"Запрос на участие должен иметь статус PENDING при создании и статус REJECTED после выполнения запроса\", function () {\r", + " pm.expect(source.status).equal(\"PENDING\");\r", + " pm.expect(target.status).equal(\"REJECTED\");\r", "});" ], "type": "text/javascript" @@ -9891,66 +13597,57 @@ } ], "request": { - "method": "GET", + "method": "PATCH", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{\n \"requestIds\": [\n 1,\n 2,\n 3\n ],\n \"status\": \"CONFIRMED\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { - "raw": "{{baseUrl}}/admin/events?users=0&states=PUBLISHED&categories=0&rangeStart=2022-01-06%2013%3A30%3A38&rangeEnd=2097-09-06%2013%3A30%3A38&from=0&size=1000", + "raw": "{{baseUrl}}/users/:userId/events/:eventId/requests", "host": [ "{{baseUrl}}" ], "path": [ - "admin", - "events" + "users", + ":userId", + "events", + ":eventId", + "requests" ], - "query": [ - { - "key": "users", - "value": "0", - "description": "список id пользователей, чьи события нужно найти" - }, - { - "key": "states", - "value": "PUBLISHED", - "description": "список состояний в которых находятся искомые события" - }, - { - "key": "categories", - "value": "0", - "description": "список id категорий в которых будет вестись поиск" - }, - { - "key": "rangeStart", - "value": "2022-01-06%2013%3A30%3A38", - "description": "дата и время не раньше которых должно произойти событие" - }, - { - "key": "rangeEnd", - "value": "2097-09-06%2013%3A30%3A38", - "description": "дата и время не позже которых должно произойти событие" - }, + "variable": [ { - "key": "from", - "value": "0", - "description": "количество событий, которые нужно пропустить для формирования текущего набора" + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" }, { - "key": "size", - "value": "1000", - "description": "количество событий в наборе" + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события текущего пользователя" } ] }, - "description": "Эндпоинт возвращает полную информацию обо всех событиях подходящих под переданные условия" + "description": "Обратите внимание:\n- если для события лимит заявок равен 0 или отключена пре-модерация заявок, то подтверждение заявок не требуется\n- нельзя подтвердить заявку, если уже достигнут лимит по заявкам на данное событие\n- статус можно изменить только у заявок, находящихся в состоянии ожидания\n- если при подтверждении данной заявки, лимит заявок для события исчерпан, то все неподтверждённые заявки необходимо отклонить" }, "response": [] }, { - "name": "Получение событий, добавленных текущим пользователем", + "name": "Изменение статуса (подтверждена, отменена) заявок на участие в событии текущего пользователя", "event": [ { "listen": "prerequest", @@ -9962,9 +13659,22 @@ "\r", " try {\r", " const user = await api.addUser(rnd.getUser());\r", - " pm.collectionVariables.set(\"uid\", user.id)\r", " const category = await api.addCategory(rnd.getCategory());\r", - " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " const requestToJoin = await api.publishParticipationRequest(event.id, submittedUser.id);\r", + " pm.collectionVariables.set('response', requestToJoin);\r", + " pm.collectionVariables.set('uid', user.id);\r", + " pm.collectionVariables.set('eid', event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({requestIds: [requestToJoin.id],\r", + " status:\"CONFIRMED\"}),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -9975,6 +13685,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -9998,14 +13709,27 @@ " pm.response.to.be.json;\r", "});\r", "\r", - "const target = pm.response.json()[0];\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json()[\"confirmedRequests\"][0];\r", "\r", - "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate\", function () {\r", - " pm.expect(target).to.contain.keys('id', 'title', 'annotation', 'category', 'paid', 'eventDate');\r", + "pm.test(\"Запрос на участие должен содержать поля: id, requester, event, status, created\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('requester');\r", + "pm.expect(target).to.have.property('event');\r", + "pm.expect(target).to.have.property('status');\r", + "pm.expect(target).to.have.property('created');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор запроса на участие в событии должен соответствовать идентификатору запроса на участие в событии указанного пользователя');\r", + " pm.expect(source.requester).equal(target.requester, 'Пользователь, запрашивающий доступ на участие в событии должен пользователю, отправившему запрос на участие в событии указанного пользователя ранее');\r", + " pm.expect(source.event).equal(target.event, 'Событие, запрос на участие в котором надо подтвердить, должно соответствовать событию указанного пользователя');\r", + " pm.expect(source.created).equal(target.created, 'Время создания запроса на участие в событии после подтверждения должно соответствовать времени создания запроса на участие в событии указанного пользователя до подтверждения');\r", + "});\r", + "\r", + "pm.test(\"Запрос на участие должен иметь статус PENDING при создании и статус CONFIRMED после выполнения запроса\", function () {\r", + " pm.expect(source.status).equal(\"PENDING\");\r", + " pm.expect(target.status).equal(\"CONFIRMED\");\r", "});" ], "type": "text/javascript" @@ -10013,48 +13737,62 @@ } ], "request": { - "method": "GET", + "method": "PATCH", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { - "raw": "{{baseUrl}}/users/:userId/events?from=0&size=1000", + "raw": "{{baseUrl}}/users/:userId/events/:eventId/requests", "host": [ "{{baseUrl}}" ], "path": [ "users", ":userId", - "events" - ], - "query": [ - { - "key": "from", - "value": "0", - "description": "количество элементов, которые нужно пропустить для формирования текущего набора" - }, - { - "key": "size", - "value": "1000", - "description": "количество элементов в наборе" - } + "events", + ":eventId", + "requests" ], "variable": [ { "key": "userId", "value": "{{uid}}", "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события текущего пользователя" } ] - } + }, + "description": "Обратите внимание:\n- если для события лимит заявок равен 0 или отключена пре-модерация заявок, то подтверждение заявок не требуется\n- нельзя подтвердить заявку, если уже достигнут лимит по заявкам на данное событие\n- статус можно изменить только у заявок, находящихся в состоянии ожидания\n- если при подтверждении данной заявки, лимит заявок для события исчерпан, то все неподтверждённые заявки необходимо отклонить" }, "response": [] - }, + } + ] + }, + { + "name": "Compilation", + "item": [ { - "name": "Получение событий с возможностью фильтрации", + "name": "Добавление новой подборки", "event": [ { "listen": "prerequest", @@ -10064,17 +13802,21 @@ " const api = new API(pm);\r", " const rnd = new RandomUtils();\r", "\r", + " let compilation;\r", " try {\r", " const user = await api.addUser(rnd.getUser());\r", " const category = await api.addCategory(rnd.getCategory());\r", - " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", - " event = await api.publishEvent(event.id);\r", - " pm.request.removeQueryParams(['text', 'categories', 'paid']);\r", - " pm.request.addQueryParams([`text=` + event.annotation, 'categories=' + category.id, 'paid=' + event.paid]);\r", - " pm.collectionVariables.set('response', event);\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " compilation = rnd.getCompilation(event.id);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", "};\r", "\r", "const interval = setInterval(() => {}, 1000);\r", @@ -10082,7 +13824,6 @@ "setTimeout(async () => \r", " {\r", " try {\r", - " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -10100,33 +13841,29 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", - " pm.response.to.be.ok; \r", + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", " pm.response.to.be.withBody;\r", " pm.response.to.be.json;\r", "});\r", "\r", - "const source = pm.collectionVariables.get('response');\r", - "const target = pm.response.json()[0];\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", "\r", - "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests\", function () {\r", + "pm.test(\"Подборка должны содержать поля: id, title, pinned, events\", function () {\r", "pm.expect(target).to.have.property('id');\r", "pm.expect(target).to.have.property('title');\r", - "pm.expect(target).to.have.property('annotation');\r", - "pm.expect(target).to.have.property('category');\r", - "pm.expect(target).to.have.property('paid');\r", - "pm.expect(target).to.have.property('eventDate');\r", - "pm.expect(target).to.have.property('initiator');\r", - "pm.expect(target).to.have.property('views');\r", - "pm.expect(target).to.have.property('confirmedRequests');\r", + "pm.expect(target).to.have.property('pinned');\r", + "pm.expect(target).to.have.property('events');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать аннотации события с указанным идентификатором');\r", - " pm.expect(source.category.id).equal(target.category.id, 'Категория события должна соответствовать категории события с указанным идентификатором');\r", - " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость события должна соответствовать стоимости события с указанным идентификатором');\r", - " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать дате проведения события с указанным идентификатором');\r", - " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать названию события с указанным идентификатором');\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(target.title).to.be.a(\"string\");\r", + " pm.expect(target.events).to.be.an(\"array\");\r", + "\r", + " pm.expect(source.events[0]).equal(target.events[0].id, 'Идентификаторы событий в подборке должен быть идентичен идентификаторам, указанным при создании подборки ');\r", + " pm.expect(source.title).equal(target.title, 'Название подборки должно соответствовать указанному при создании');\r", "});" ], "type": "text/javascript" @@ -10134,75 +13871,41 @@ } ], "request": { - "method": "GET", + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { - "raw": "{{baseUrl}}/events?text=0&categories=0&paid=true&rangeStart=2022-01-06%2013%3A30%3A38&rangeEnd=2097-09-06%2013%3A30%3A38&onlyAvailable=false&sort=EVENT_DATE&from=0&size=1000", + "raw": "{{baseUrl}}/admin/compilations", "host": [ "{{baseUrl}}" ], "path": [ - "events" - ], - "query": [ - { - "key": "text", - "value": "0", - "description": "текст для поиска в содержимом аннотации и подробном описании события" - }, - { - "key": "categories", - "value": "0", - "description": "список идентификаторов категорий в которых будет вестись поиск" - }, - { - "key": "paid", - "value": "true", - "description": "поиск только платных/бесплатных событий" - }, - { - "key": "rangeStart", - "value": "2022-01-06%2013%3A30%3A38", - "description": "дата и время не раньше которых должно произойти событие" - }, - { - "key": "rangeEnd", - "value": "2097-09-06%2013%3A30%3A38", - "description": "дата и время не позже которых должно произойти событие" - }, - { - "key": "onlyAvailable", - "value": "false", - "description": "только события у которых не исчерпан лимит запросов на участие" - }, - { - "key": "sort", - "value": "EVENT_DATE", - "description": "Вариант сортировки: по дате события или по количеству просмотров" - }, - { - "key": "from", - "value": "0", - "description": "количество событий, которые нужно пропустить для формирования текущего набора" - }, - { - "key": "size", - "value": "1000", - "description": "количество событий в наборе" - } - ] - }, - "description": "Обратите внимание: \n- это публичный эндпоинт, соответственно в выдаче должны быть только опубликованные события\n- текстовый поиск (по аннотации и подробному описанию) должен быть без учета регистра букв\n- если в запросе не указан диапазон дат [rangeStart-rangeEnd], то нужно выгружать события, которые произойдут позже текущей даты и времени\n- информация о каждом событии должна включать в себя количество просмотров и количество уже одобренных заявок на участие\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" + "admin", + "compilations" + ] + } }, "response": [] }, { - "name": "Получение подробной информации об опубликованном событии по его идентификатору", + "name": "Получение подборки событий по её id", "event": [ { "listen": "prerequest", @@ -10215,10 +13918,9 @@ " try {\r", " const user = await api.addUser(rnd.getUser());\r", " const category = await api.addCategory(rnd.getCategory());\r", - " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", - " event = await api.publishEvent(event.id);\r", - " pm.collectionVariables.set(\"eid\", event.id)\r", - " pm.collectionVariables.set('response', event);\r", + " const compilation = await api.addCompilation(rnd.getCompilation());\r", + " pm.collectionVariables.set('response', compilation);\r", + " pm.collectionVariables.set('compid', compilation.id);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -10256,33 +13958,18 @@ "const source = pm.collectionVariables.get('response');\r", "const target = pm.response.json();\r", "\r", - "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", + "pm.test(\"Подборка должны содержать поля: id, title, pinned, events\", function () {\r", "pm.expect(target).to.have.property('id');\r", "pm.expect(target).to.have.property('title');\r", - "pm.expect(target).to.have.property('annotation');\r", - "pm.expect(target).to.have.property('category');\r", - "pm.expect(target).to.have.property('paid');\r", - "pm.expect(target).to.have.property('eventDate');\r", - "pm.expect(target).to.have.property('initiator');\r", - "pm.expect(target).to.have.property('views');\r", - "pm.expect(target).to.have.property('confirmedRequests');\r", - "pm.expect(target).to.have.property('description');\r", - "pm.expect(target).to.have.property('participantLimit');\r", - "pm.expect(target).to.have.property('state');\r", - "pm.expect(target).to.have.property('createdOn');\r", - "pm.expect(target).to.have.property('publishedOn');\r", - "pm.expect(target).to.have.property('location');\r", - "pm.expect(target).to.have.property('requestModeration');\r", + "pm.expect(target).to.have.property('pinned');\r", + "pm.expect(target).to.have.property('events');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать аннотации события с указанным идентификатором');\r", - " pm.expect(source.category.id).equal(target.category.id, 'Категория события должна соответствовать категории события с указанным идентификатором');\r", - " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость события должна соответствовать стоимости события с указанным идентификатором');\r", - " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать дате проведения события с указанным идентификатором');\r", - " pm.expect(source.description).equal(target.description, 'Описание события должно соответствовать описанию события с указанным идентификатором');\r", - " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать названию события с указанным идентификатором');\r", - " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Лимит участников события должен соответствовать лимиту участников события с указанным идентификатором');\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор подборки должен соответствовать идентификатору подборки добавленной ранее');\r", + " pm.expect(source.title).equal(target.title, 'Название подборки должно соответствовать названию подборки добавленной ранее');\r", + " pm.expect(source.pinned).equal(target.pinned, 'Закреплённость подборки должна соответствовать закреплённости подборки добавленной ранее');\r", + " pm.expect(source.events.join()).equal(target.events.join(), 'События подборки должны соответствовать событиям подборки добавленной ранее');\r", "});" ], "type": "text/javascript" @@ -10298,28 +13985,27 @@ } ], "url": { - "raw": "{{baseUrl}}/events/:id", + "raw": "{{baseUrl}}/compilations/:compId", "host": [ "{{baseUrl}}" ], "path": [ - "events", - ":id" + "compilations", + ":compId" ], "variable": [ { - "key": "id", - "value": "{{eid}}", - "description": "(Required) id события" + "key": "compId", + "value": "{{compid}}", + "description": "(Required) id подборки" } ] - }, - "description": "Обратите внимание:\n- событие должно быть опубликовано\n- информация о событии должна включать в себя количество просмотров и количество подтвержденных запросов\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" + } }, "response": [] }, { - "name": "Получение полной информации о событии добавленном текущим пользователем", + "name": "Получение подборок событий", "event": [ { "listen": "prerequest", @@ -10331,10 +14017,11 @@ "\r", " try {\r", " const user = await api.addUser(rnd.getUser());\r", - " pm.collectionVariables.set(\"uid\", user.id)\r", " const category = await api.addCategory(rnd.getCategory());\r", - " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", - " pm.collectionVariables.set(\"eid\", event.id)\r", + " let newComp = rnd.getCompilation();\r", + " newComp['pinned'] = true;\r", + " const compilation = await api.addCompilation(newComp);\r", + " pm.collectionVariables.set('response', compilation);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -10345,6 +14032,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -10368,26 +14056,23 @@ " pm.response.to.be.json;\r", "});\r", "\r", + "const source = pm.collectionVariables.get('response');\r", "const target = pm.response.json();\r", + "let founded;\r", + "target.forEach(function(element){if (element.id == source.id) founded = element});\r", "\r", - "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, location, requestModeration\", function () {\r", - "pm.expect(target).to.have.property('id');\r", - "pm.expect(target).to.have.property('title');\r", - "pm.expect(target).to.have.property('annotation');\r", - "pm.expect(target).to.have.property('category');\r", - "pm.expect(target).to.have.property('paid');\r", - "pm.expect(target).to.have.property('eventDate');\r", - "pm.expect(target).to.have.property('initiator');\r", - "pm.expect(target).to.have.property('description');\r", - "pm.expect(target).to.have.property('participantLimit');\r", - "pm.expect(target).to.have.property('state');\r", - "pm.expect(target).to.have.property('createdOn');\r", - "pm.expect(target).to.have.property('location');\r", - "pm.expect(target).to.have.property('requestModeration');\r", + "pm.test(\"Подборка должны содержать поля: id, title, pinned, events\", function () {\r", + "pm.expect(target[0]).to.have.property('id');\r", + "pm.expect(target[0]).to.have.property('title');\r", + "pm.expect(target[0]).to.have.property('pinned');\r", + "pm.expect(target[0]).to.have.property('events');\r", "});\r", "\r", "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.id).equal(founded.id, 'Идентификатор подборки должен соответствовать идентификатору подборки добавленной ранее');\r", + " pm.expect(source.title).equal(founded.title, 'Название подборки должно соответствовать названию подборки добавленной ранее');\r", + " pm.expect(source.pinned).equal(founded.pinned, 'Закрепленность подборки должна соответствовать закрепленности подборки добавленной ранее');\r", + " pm.expect(source.events.join()).equal(founded.events.join(), 'События подборки должны соответствовать событиям подборки добавленной ранее');\r", "});" ], "type": "text/javascript" @@ -10403,26 +14088,28 @@ } ], "url": { - "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "raw": "{{baseUrl}}/compilations?pinned=true&from=0&size=1000", "host": [ "{{baseUrl}}" ], "path": [ - "users", - ":userId", - "events", - ":eventId" + "compilations" ], - "variable": [ + "query": [ { - "key": "userId", - "value": "{{uid}}", - "description": "(Required) id текущего пользователя" + "key": "pinned", + "value": "true", + "description": "искать только закрепленные/не закрепленные подборки" }, { - "key": "eventId", - "value": "{{eid}}", - "description": "(Required) id события" + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество элементов в наборе" } ] } @@ -10430,8 +14117,43 @@ "response": [] }, { - "name": "Редактирование данных события и его статуса (отклонение/публикация).", + "name": "Удаление подборки", "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 204\", function () {\r", + " pm.response.to.have.status(204);\r", + "});\r", + "\r", + "let source = pm.collectionVariables.get('response');\r", + "\r", + "pm.test(\"Подборка должна быть найдена до удаления\", function () {\r", + " pm.expect(source).not.to.be.null;\r", + "});\r", + "\r", + "let body\r", + "const req = {\r", + " url: \"http://localhost:8080/compilations?from=0&size=1000\" + pm.collectionVariables.get(\"uid\"),\r", + " method: \"GET\",\r", + " body: body == null ? \"\" : JSON.stringify(body),\r", + " header: { \"Content-Type\": \"application/json\" },\r", + " };\r", + "pm.sendRequest(req, (error, response) => {\r", + " pm.test(\"Подборка должна быть удалена после выполнения запроса\", function(){\r", + " response.json().forEach(element => {\r", + " if(element.id == pm.collectionVariables.get('compid')){\r", + " throw new Error(\"Подборка все еще находится в списке существующих\");\r", + " }\r", + " })\r", + " });\r", + "})\r", + "" + ], + "type": "text/javascript" + } + }, { "listen": "prerequest", "script": { @@ -10443,16 +14165,10 @@ " try {\r", " const user = await api.addUser(rnd.getUser());\r", " const category = await api.addCategory(rnd.getCategory());\r", - " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", - " let event2 = rnd.getEvent(category.id)\r", - " event2.stateAction = \"PUBLISH_EVENT\"\r", - " pm.collectionVariables.set('response', event2);\r", - " pm.collectionVariables.set(\"eid\", event.id)\r", - " pm.request.body.update({\r", - " mode: 'raw',\r", - " raw: event2,\r", - " options: { raw: { language: 'json' } }\r", - " });\r", + " const compilation = await api.addCompilation(rnd.getCompilation());\r", + " const foundedCompilation = await api.findCompilation(compilation.id);\r", + " pm.collectionVariables.set('compid', compilation.id);\r", + " pm.collectionVariables.set('response', foundedCompilation);\r", " } catch(err) {\r", " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", " }\r", @@ -10476,86 +14192,39 @@ ], "type": "text/javascript" } - }, - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", - " pm.response.to.be.ok; \r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "\r", - "const source = pm.collectionVariables.get('response');\r", - "const target = pm.response.json();\r", - "\r", - "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", - "pm.expect(target).to.have.property('id');\r", - "pm.expect(target).to.have.property('title');\r", - "pm.expect(target).to.have.property('annotation');\r", - "pm.expect(target).to.have.property('category');\r", - "pm.expect(target).to.have.property('paid');\r", - "pm.expect(target).to.have.property('eventDate');\r", - "pm.expect(target).to.have.property('initiator');\r", - "pm.expect(target).to.have.property('description');\r", - "pm.expect(target).to.have.property('participantLimit');\r", - "pm.expect(target).to.have.property('state');\r", - "pm.expect(target).to.have.property('createdOn');\r", - "pm.expect(target).to.have.property('publishedOn');\r", - "pm.expect(target).to.have.property('location');\r", - "pm.expect(target).to.have.property('requestModeration');\r", - "});\r", - "\r", - "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать искомому событию');\r", - " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость события должна соответствовать искомому событию');\r", - " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать искомому событию');\r", - " pm.expect(source.description).equal(target.description, 'Описание события должно соответствовать искомому событию');\r", - " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать искомому событию');\r", - " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Лимит участников события должен соответствовать искомому событию');\r", - "});" - ], - "type": "text/javascript" - } } ], "request": { - "method": "PATCH", + "method": "DELETE", "header": [ { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{{request_body}}" - }, "url": { - "raw": "{{baseUrl}}/admin/events/:eventId", + "raw": "{{baseUrl}}/admin/compilations/:compId", "host": [ "{{baseUrl}}" ], "path": [ "admin", - "events", - ":eventId" + "compilations", + ":compId" ], "variable": [ { - "key": "eventId", - "value": "{{eid}}", - "description": "(Required) id события" + "key": "compId", + "value": "{{compid}}", + "description": "(Required) id подборки" } ] - }, - "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + } }, "response": [] }, { - "name": "Изменение события добавленного текущим пользователем", + "name": "Обновить информацию о подборке", "event": [ { "listen": "prerequest", @@ -10568,14 +14237,19 @@ " try {\r", " const user = await api.addUser(rnd.getUser());\r", " const category = await api.addCategory(rnd.getCategory());\r", - " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", - " pm.collectionVariables.set(\"uid\", user.id);\r", - " pm.collectionVariables.set(\"eid\", event.id);\r", - " pm.collectionVariables.set(\"response\", event);\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " const compilation = await api.addCompilation(rnd.getCompilation());\r", + " const foundedCompilation = await api.findCompilation(compilation.id);\r", + " pm.collectionVariables.set('compid', compilation.id);\r", + " pm.collectionVariables.set('response', foundedCompilation);\r", + " pm.collectionVariables.set('eid', event.id);\r", " pm.request.body.update({\r", " mode: 'raw',\r", " raw: JSON.stringify({\r", - " stateAction: \"CANCEL_REVIEW\"\r", + " events : [event.id],\r", + " pinned: true,\r", + " title: rnd.getCompilation().name\r", " }),\r", " options: { raw: { language: 'json' } }\r", " });\r", @@ -10589,6 +14263,7 @@ "setTimeout(async () => \r", " {\r", " try {\r", + " // выполняем наш скрипт\r", " await main();\r", " } catch (e) {\r", " console.error(e);\r", @@ -10606,47 +14281,27 @@ "listen": "test", "script": { "exec": [ - "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + "pm.test(\"Ответ должен содержать код статуса 200\", function () {\r", " pm.response.to.be.ok; \r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", "});\r", "\r", - "const source = pm.collectionVariables.get(\"response\");\r", - "const target = pm.response.json();\r", - "\r", - "\r", - "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, location, requestModeration\", function () {\r", - "pm.expect(target).to.have.property('id');\r", - "pm.expect(target).to.have.property('title');\r", - "pm.expect(target).to.have.property('annotation');\r", - "pm.expect(target).to.have.property('category');\r", - "pm.expect(target).to.have.property('paid');\r", - "pm.expect(target).to.have.property('eventDate');\r", - "pm.expect(target).to.have.property('initiator');\r", - "pm.expect(target).to.have.property('description');\r", - "pm.expect(target).to.have.property('participantLimit');\r", - "pm.expect(target).to.have.property('state');\r", - "pm.expect(target).to.have.property('createdOn');\r", - "pm.expect(target).to.have.property('location');\r", - "pm.expect(target).to.have.property('requestModeration');\r", - "});\r", + "source = pm.collectionVariables.get('response');\r", + "compId = pm.collectionVariables.get('compid');\r", + "eventId = pm.collectionVariables.get('eid');\r", "\r", - "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", - " pm.expect(source.annotation).equal(target.annotation, 'Аннотация отменённого события должна соответствовать аннотации события до отмены');\r", - " pm.expect(source.category.id).equal(target.category.id, 'Категория отменённого события должна соответствовать категории события до отмены');\r", - " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость отменённого события должна соответствовать стоимости события до отмены');\r", - " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения отменённого события должна соответствовать дате проведения события до отмены');\r", - " pm.expect(source.description).equal(target.description, 'Описание отменённого события должно соответствовать описанию события до отмены');\r", - " pm.expect(source.title).equal(target.title, 'Название отменённого события должно соответствовать названию события до отмены');\r", - " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Лимит участников отменённого события должен соответствовать лимиту участников события до отмены');\r", - " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость отменённого события должна соответствовать стоимости события до отмены');\r", + "pm.test(\"Событие не должно быть найдено в подборке до добавления\", function () {\r", + " pm.expect(source.events.length).equal(0);\r", "});\r", "\r", - "pm.test(\"Событие должно иметь статус PENDING при создании и статус CANCELED после выполнения запроса\", function () {\r", - " pm.expect(source.state).equal(\"PENDING\");\r", - " pm.expect(target.state).equal(\"CANCELED\");\r", - "});" + "pm.sendRequest({\r", + " url: pm.collectionVariables.get(\"baseUrl\") + \"/compilations/\" + compId,\r", + " method: 'GET',\r", + " }, (error, response) => {\r", + " \r", + " pm.test(\"Событие должно быть найдено в подборке после добавления\", function () {\r", + " pm.expect(response.json().events[0].id).equal(eventId);\r", + " });\r", + " });" ], "type": "text/javascript" } @@ -10654,50 +14309,28 @@ ], "request": { "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], + "header": [], "body": { "mode": "raw", - "raw": "{{request_body}}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{{request_body}}" }, "url": { - "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "raw": "{{baseUrl}}/admin/compilations/:compId", "host": [ "{{baseUrl}}" ], "path": [ - "users", - ":userId", - "events", - ":eventId" + "admin", + "compilations", + ":compId" ], "variable": [ { - "key": "userId", - "value": "{{uid}}", - "description": "(Required) id текущего пользователя" - }, - { - "key": "eventId", - "value": "{{eid}}", - "description": "(Required) id отменяемого события" + "key": "compId", + "value": "{{compid}}" } ] - }, - "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + } }, "response": [] } @@ -10994,4 +14627,4 @@ "value": "" } ] -} \ No newline at end of file +} From 6535d616c8772c4b6fd671711b690c59870a6cb6 Mon Sep 17 00:00:00 2001 From: LightInTheFire <109972737+LightInTheFire@users.noreply.github.com> Date: Sun, 18 Jan 2026 15:16:30 +0300 Subject: [PATCH 10/10] Main svc fix (#13) * feat: remove update category dto * feat: add constraints to page params * fix: remove views from compilations * fix: change start date stats in events * fix: change catch exception in statclient connection * fix: npe fixes --- .../controller/CategoryAdminController.java | 3 +- .../controller/CategoryPublicController.java | 9 +++- .../category/dto/UpdateCategoryDto.java | 6 --- .../category/service/CategoryService.java | 3 +- .../category/service/CategoryServiceImpl.java | 3 +- .../CompilationsPublicController.java | 9 +++- .../service/CompilationsServiceImpl.java | 50 +++---------------- .../controller/EventAdminController.java | 8 ++- .../controller/EventPrivateController.java | 8 ++- .../controller/EventPublicController.java | 8 ++- .../event/service/EventServiceImpl.java | 39 +++++++++------ .../user/controller/UserAdminController.java | 8 ++- 12 files changed, 71 insertions(+), 83 deletions(-) delete mode 100644 main-service/src/main/java/ru/practicum/category/dto/UpdateCategoryDto.java diff --git a/main-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java b/main-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java index 4dda309..5495dcd 100644 --- a/main-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java +++ b/main-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java @@ -4,7 +4,6 @@ import ru.practicum.category.dto.CategoryDto; import ru.practicum.category.dto.NewCategoryDto; -import ru.practicum.category.dto.UpdateCategoryDto; import ru.practicum.category.service.CategoryService; import org.springframework.http.HttpStatus; @@ -29,7 +28,7 @@ public CategoryDto createCategory(@Valid @RequestBody NewCategoryDto newCategory @PatchMapping("/{catId}") public CategoryDto updateCategory( - @PathVariable Long catId, @Valid @RequestBody UpdateCategoryDto updateCategoryDto) { + @PathVariable Long catId, @Valid @RequestBody NewCategoryDto updateCategoryDto) { log.info( "Update category with id={} requested, updated category {}", catId, diff --git a/main-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java b/main-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java index 0831614..1ffa2f0 100644 --- a/main-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java +++ b/main-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java @@ -2,15 +2,20 @@ import java.util.Collection; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; + import ru.practicum.category.dto.CategoryDto; import ru.practicum.category.service.CategoryService; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j +@Validated @RestController @RequiredArgsConstructor @RequestMapping("/categories") @@ -19,8 +24,8 @@ public class CategoryPublicController { @GetMapping public Collection getAllCategoriesPaged( - @RequestParam(defaultValue = "0") int from, - @RequestParam(defaultValue = "10") int size) { + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size) { log.info("View categories page by page requested from={}, size={}", from, size); return categoryService.getAllCategoriesPaged(from, size); } diff --git a/main-service/src/main/java/ru/practicum/category/dto/UpdateCategoryDto.java b/main-service/src/main/java/ru/practicum/category/dto/UpdateCategoryDto.java deleted file mode 100644 index 58611f4..0000000 --- a/main-service/src/main/java/ru/practicum/category/dto/UpdateCategoryDto.java +++ /dev/null @@ -1,6 +0,0 @@ -package ru.practicum.category.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -public record UpdateCategoryDto(@Size(min = 1, max = 50) @NotBlank String name) {} diff --git a/main-service/src/main/java/ru/practicum/category/service/CategoryService.java b/main-service/src/main/java/ru/practicum/category/service/CategoryService.java index 9058f1e..69d5377 100644 --- a/main-service/src/main/java/ru/practicum/category/service/CategoryService.java +++ b/main-service/src/main/java/ru/practicum/category/service/CategoryService.java @@ -4,7 +4,6 @@ import ru.practicum.category.dto.CategoryDto; import ru.practicum.category.dto.NewCategoryDto; -import ru.practicum.category.dto.UpdateCategoryDto; public interface CategoryService { CategoryDto createCategory(NewCategoryDto newCategoryDto); @@ -15,5 +14,5 @@ public interface CategoryService { void deleteCategoryById(Long id); - CategoryDto updateCategory(Long catId, UpdateCategoryDto updateCategoryDto); + CategoryDto updateCategory(Long catId, NewCategoryDto updateCategoryDto); } diff --git a/main-service/src/main/java/ru/practicum/category/service/CategoryServiceImpl.java b/main-service/src/main/java/ru/practicum/category/service/CategoryServiceImpl.java index e91d26b..2697af3 100644 --- a/main-service/src/main/java/ru/practicum/category/service/CategoryServiceImpl.java +++ b/main-service/src/main/java/ru/practicum/category/service/CategoryServiceImpl.java @@ -4,7 +4,6 @@ import ru.practicum.category.dto.CategoryDto; import ru.practicum.category.dto.NewCategoryDto; -import ru.practicum.category.dto.UpdateCategoryDto; import ru.practicum.category.mapper.CategoryMapper; import ru.practicum.category.model.Category; import ru.practicum.category.repository.CategoryRepository; @@ -30,7 +29,7 @@ public CategoryDto createCategory(NewCategoryDto newCategoryDto) { } @Override - public CategoryDto updateCategory(Long catId, UpdateCategoryDto updateCategoryDto) { + public CategoryDto updateCategory(Long catId, NewCategoryDto updateCategoryDto) { Category category = categoryRepository .findById(catId) diff --git a/main-service/src/main/java/ru/practicum/compilation/controller/CompilationsPublicController.java b/main-service/src/main/java/ru/practicum/compilation/controller/CompilationsPublicController.java index 0f6be83..fbf0812 100644 --- a/main-service/src/main/java/ru/practicum/compilation/controller/CompilationsPublicController.java +++ b/main-service/src/main/java/ru/practicum/compilation/controller/CompilationsPublicController.java @@ -2,16 +2,21 @@ import java.util.Collection; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; + import ru.practicum.compilation.dto.CompilationDto; import ru.practicum.compilation.service.CompilationsPublicGetRequest; import ru.practicum.compilation.service.CompilationsService; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j +@Validated @RestController @RequiredArgsConstructor @RequestMapping("/compilations") @@ -21,8 +26,8 @@ public class CompilationsPublicController { @GetMapping public Collection getCompilations( @RequestParam(required = false) Boolean pinned, - @RequestParam(defaultValue = "0") int from, - @RequestParam(defaultValue = "10") int size) { + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size) { CompilationsPublicGetRequest getRequest = new CompilationsPublicGetRequest(pinned, from, size); log.info("Public get compilations requested with params= {}", getRequest); diff --git a/main-service/src/main/java/ru/practicum/compilation/service/CompilationsServiceImpl.java b/main-service/src/main/java/ru/practicum/compilation/service/CompilationsServiceImpl.java index 32ffbe8..e4e3d1c 100644 --- a/main-service/src/main/java/ru/practicum/compilation/service/CompilationsServiceImpl.java +++ b/main-service/src/main/java/ru/practicum/compilation/service/CompilationsServiceImpl.java @@ -1,20 +1,17 @@ package ru.practicum.compilation.service; -import java.time.LocalDateTime; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import ru.practicum.client.StatsClient; import ru.practicum.compilation.dto.CompilationDto; import ru.practicum.compilation.dto.NewCompilationDto; import ru.practicum.compilation.dto.UpdateCompilationRequest; import ru.practicum.compilation.mapper.CompilationsMapper; import ru.practicum.compilation.model.Compilation; import ru.practicum.compilation.repository.CompilationsRepository; -import ru.practicum.dto.ViewStatsDto; import ru.practicum.event.dto.EventShortDto; import ru.practicum.event.mapper.EventMapper; import ru.practicum.event.model.Event; @@ -35,12 +32,9 @@ @RequiredArgsConstructor @Transactional(readOnly = true) public class CompilationsServiceImpl implements CompilationsService { - private static final LocalDateTime MINIMAL_LOCAL_DATE_TIME = - LocalDateTime.of(1000, 1, 1, 0, 0, 0); private final CompilationsRepository compRepository; private final EventRepository eventRepository; private final ParticipationRequestRepository requestRepository; - private final StatsClient statsClient; @Override public Collection findAll(CompilationsPublicGetRequest getRequest) { @@ -52,9 +46,8 @@ public Collection findAll(CompilationsPublicGetRequest getReques page.stream().flatMap(c -> c.getEvents().stream()).collect(Collectors.toSet()); Map confirmedRequests = getConfirmedRequests(events); - Map views = getViews(events); - return page.stream().map(c -> toDto(c, confirmedRequests, views)).toList(); + return page.stream().map(c -> toDto(c, confirmedRequests)).toList(); } @Override @@ -70,9 +63,8 @@ public CompilationDto findById(long compId) { Set events = compilation.getEvents(); Map confirmedRequests = getConfirmedRequests(events); - Map views = getViews(events); - return toDto(compilation, confirmedRequests, views); + return toDto(compilation, confirmedRequests); } @Override @@ -91,9 +83,8 @@ public CompilationDto save(NewCompilationDto newCompilationDto) { Compilation saved = compRepository.save(compilation); Map confirmedRequests = getConfirmedRequests(events); - Map views = getViews(events); - return toDto(saved, confirmedRequests, views); + return toDto(saved, confirmedRequests); } @Override @@ -129,13 +120,11 @@ public CompilationDto update(long compId, UpdateCompilationRequest updateRequest Set actualEvents = updated.getEvents(); Map confirmedRequests = getConfirmedRequests(actualEvents); - Map views = getViews(actualEvents); - return toDto(updated, confirmedRequests, views); + return toDto(updated, confirmedRequests); } - private CompilationDto toDto( - Compilation compilation, Map confirmedRequests, Map views) { + private CompilationDto toDto(Compilation compilation, Map confirmedRequests) { List events = compilation.getEvents().stream() .map( @@ -143,7 +132,7 @@ private CompilationDto toDto( EventMapper.mapToShortDto( event, confirmedRequests.getOrDefault(event.getId(), 0L), - views.get(event.getId()))) + null)) .toList(); return CompilationsMapper.mapToDto(compilation, events); @@ -172,31 +161,4 @@ private Map getConfirmedRequests(Set events) { return requestRepository.countConfirmedByEventIds(eventIds); } - - private Map getViews(Set events) { - if (events.isEmpty()) { - return Map.of(); - } - - List uris = events.stream().map(e -> "/events/%s".formatted(e.getId())).toList(); - - try { - List stats = - statsClient.getStats(MINIMAL_LOCAL_DATE_TIME, LocalDateTime.now(), uris, true); - - return stats.stream() - .collect( - Collectors.toMap( - s -> { - String uri = s.uri(); - return Long.parseLong( - uri.substring(uri.lastIndexOf("/") + 1)); - }, - ViewStatsDto::hits)); - - } catch (Exception e) { - log.error("Error during getting stats for events", e); - return Map.of(); - } - } } diff --git a/main-service/src/main/java/ru/practicum/event/controller/EventAdminController.java b/main-service/src/main/java/ru/practicum/event/controller/EventAdminController.java index 31fd7e7..c0760d6 100644 --- a/main-service/src/main/java/ru/practicum/event/controller/EventAdminController.java +++ b/main-service/src/main/java/ru/practicum/event/controller/EventAdminController.java @@ -5,6 +5,8 @@ import java.util.List; import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import ru.practicum.event.dto.EventFullDto; import ru.practicum.event.dto.UpdateEventAdminRequest; @@ -12,12 +14,14 @@ import ru.practicum.event.service.EventService; import ru.practicum.event.service.EventsAdminGetRequest; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j +@Validated @RestController @RequiredArgsConstructor @RequestMapping("/admin/events") @@ -31,8 +35,8 @@ public Collection getEventsFiltered( @RequestParam(required = false) List categories, @RequestParam(required = false) LocalDateTime rangeStart, @RequestParam(required = false) LocalDateTime rangeEnd, - @RequestParam(defaultValue = "0") int from, - @RequestParam(defaultValue = "10") int size) { + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size) { EventsAdminGetRequest getRequest = new EventsAdminGetRequest( users, states, categories, rangeStart, rangeEnd, from, size); diff --git a/main-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java b/main-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java index d0c0976..aee1200 100644 --- a/main-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java +++ b/main-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java @@ -3,6 +3,8 @@ import java.util.Collection; import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import ru.practicum.event.dto.EventFullDto; import ru.practicum.event.dto.EventShortDto; @@ -12,12 +14,14 @@ import ru.practicum.event.service.EventsPrivateGetRequest; import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j +@Validated @RestController @RequiredArgsConstructor @RequestMapping("/users/{userId}/events") @@ -27,8 +31,8 @@ public class EventPrivateController { @GetMapping() public Collection getEventsOfUserPaged( @PathVariable Long userId, - @RequestParam(defaultValue = "0") int from, - @RequestParam(defaultValue = "10") int size) { + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size) { EventsPrivateGetRequest getRequest = new EventsPrivateGetRequest(userId, from, size); log.info("Private get events requested with params= {}", getRequest); return eventService.getEvents(getRequest); diff --git a/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java b/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java index 8501170..2f4d5ee 100644 --- a/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java +++ b/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java @@ -5,6 +5,8 @@ import java.util.List; import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import ru.practicum.event.dto.EventFullDto; import ru.practicum.event.dto.EventShortDto; @@ -12,12 +14,14 @@ import ru.practicum.event.service.EventsPublicGetRequest; import ru.practicum.exception.ValidationException; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j +@Validated @RestController @RequiredArgsConstructor @RequestMapping("/events") @@ -33,8 +37,8 @@ public Collection getEventsFiltered( @RequestParam(required = false) LocalDateTime rangeEnd, @RequestParam(defaultValue = "false") boolean onlyAvailable, @RequestParam(required = false) EventSortBy sort, - @RequestParam(defaultValue = "0") int from, - @RequestParam(defaultValue = "10") int size, + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size, HttpServletRequest request) { EventsPublicGetRequest getRequest = new EventsPublicGetRequest( diff --git a/main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java b/main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java index b19d9db..246a538 100644 --- a/main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java +++ b/main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java @@ -30,6 +30,7 @@ import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestClientException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -40,8 +41,6 @@ @Transactional(readOnly = true) public class EventServiceImpl implements EventService { private static final Duration MIN_TIME_BEFORE_EVENT = Duration.ofHours(2); - private static final LocalDateTime MINIMAL_LOCAL_DATE_TIME = - LocalDateTime.of(1000, 1, 1, 0, 0, 0); private static final String EVENTS_URI = "/events/%d"; private final EventRepository eventRepository; private final UserRepository userRepository; @@ -238,9 +237,19 @@ public EventFullDto updateEventByUser( } private Map getStatsMapForEvents(Page events) { + if (events.isEmpty()) { + return Collections.emptyMap(); + } List listOfUris = events.stream().map(event -> EVENTS_URI.formatted(event.getId())).toList(); - return getStatsForEvents(listOfUris).stream() + LocalDateTime minimalPublishDate = + events.stream() + .map(Event::getPublishedOn) + .filter(Objects::nonNull) + .min(LocalDateTime::compareTo) + .orElse(LocalDateTime.of(1000, 1, 1, 1, 1)); + + return getStatsForEvents(listOfUris, minimalPublishDate).stream() .collect( Collectors.toMap( statsDto -> @@ -262,30 +271,30 @@ private Map getConfirmedRequests(Set events) { return requestRepository.countConfirmedByEventIds(eventIds); } - private List getStatsForEvents(List uris) { + private List getStatsForEvents(List uris, LocalDateTime startDate) { try { - return statsClient.getStats(MINIMAL_LOCAL_DATE_TIME, LocalDateTime.now(), uris, true); - } catch (Exception e) { + return statsClient.getStats(startDate, LocalDateTime.now(), uris, true); + } catch (RestClientException e) { log.error("Error during getting stats for events", e); } return List.of(); } private ViewStatsDto getStatsForEvent(Event event, String uri) { + if (event.getPublishedOn() == null) { + return new ViewStatsDto(null, null, null); + } + + LocalDateTime startDate = event.getPublishedOn().minusSeconds(10); + LocalDateTime endDate = LocalDateTime.now().plusSeconds(10); ViewStatsDto statsDto; + try { - statsDto = - statsClient - .getStats( - MINIMAL_LOCAL_DATE_TIME, - LocalDateTime.now(), - List.of(uri), - true) - .getFirst(); + statsDto = statsClient.getStats(startDate, endDate, List.of(uri), true).getFirst(); } catch (NoSuchElementException e) { log.trace("No stats for event with id={} found", event.getId()); statsDto = new ViewStatsDto(null, null, 0L); - } catch (Exception e) { + } catch (RestClientException e) { log.error("Error during getting stats for event with id={}", event.getId(), e); statsDto = new ViewStatsDto(null, null, null); } diff --git a/main-service/src/main/java/ru/practicum/user/controller/UserAdminController.java b/main-service/src/main/java/ru/practicum/user/controller/UserAdminController.java index f239a1e..b8c4699 100644 --- a/main-service/src/main/java/ru/practicum/user/controller/UserAdminController.java +++ b/main-service/src/main/java/ru/practicum/user/controller/UserAdminController.java @@ -4,6 +4,8 @@ import java.util.List; import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import ru.practicum.user.dto.NewUserRequest; import ru.practicum.user.dto.UserDto; @@ -11,12 +13,14 @@ import ru.practicum.user.service.UsersGetRequest; import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j +@Validated @RestController @RequiredArgsConstructor @RequestMapping("/admin/users") @@ -26,8 +30,8 @@ public class UserAdminController { @GetMapping() public Collection getUsersPaged( @RequestParam(required = false) List ids, - @RequestParam(defaultValue = "0") int from, - @RequestParam(defaultValue = "10") int size) { + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size) { log.info("Get users requested with ids={} from={} size={}", ids, from, size); UsersGetRequest request = new UsersGetRequest(from, size, ids); return userService.getUsersPaged(request);