diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..abe6570 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,39 @@ +services: + gateway: + build: gateway + image: shareit-gateway + container_name: shareit-gateway + ports: + - "8080:8080" + depends_on: + - server + environment: + - SHAREIT_SERVER_URL=http://server:9090 + + server: + build: server + image: shareit-server + container_name: shareit-server + ports: + - "9090:9090" + depends_on: + - db + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/shareit + - SPRING_DATASOURCE_USERNAME=shareit + - SPRING_DATASOURCE_PASSWORD=shareit + + db: + image: postgres:16.1 + container_name: postgres + ports: + - "6541:5432" + environment: + - POSTGRES_PASSWORD=shareit + - POSTGRES_USER=shareit + - POSTGRES_DB=shareit + healthcheck: + test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER + timeout: 5s + interval: 5s + retries: 10 \ No newline at end of file diff --git a/gateway/Dockerfile b/gateway/Dockerfile new file mode 100644 index 0000000..0ff1817 --- /dev/null +++ b/gateway/Dockerfile @@ -0,0 +1,5 @@ +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/gateway/pom.xml b/gateway/pom.xml new file mode 100644 index 0000000..f420171 --- /dev/null +++ b/gateway/pom.xml @@ -0,0 +1,124 @@ + + + 4.0.0 + + ru.practicum + shareit + 0.0.1-SNAPSHOT + + + shareit-gateway + 0.0.1-SNAPSHOT + + ShareIt Gateway + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.hibernate.validator + hibernate-validator + + + + org.apache.httpcomponents.client5 + httpclient5 + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.jacoco + jacoco-maven-plugin + + + ru/practicum/shareit/**/dto/* + ru/practicum/shareit/ShareItGateway.class + ru/practicum/shareit/rest/*.class + + + + + default-prepare-agent + + prepare-agent + + + ${project.build.directory}/coverage-reports/jacoco.exec + surefireArgLine + + + + default-report + test + + report + + + ${project.build.directory}/coverage-reports/jacoco.exec + ${project.reporting.outputDirectory}/jacoco + + + + default-check + + check + + + + + BUNDLE + + + COMPLEXITY + COVEREDRATIO + 0.70 + + + + + + + + + + + + \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java new file mode 100644 index 0000000..0aa75c3 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ShareItGateway { + public static void main(String[] args) { + SpringApplication.run(ShareItGateway.class, args); + } + +} diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java new file mode 100644 index 0000000..9f87656 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -0,0 +1,79 @@ +package ru.practicum.shareit.booking; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.rest.RestQueryBuilder; +import ru.practicum.shareit.booking.dto.BookingDto; + +@RestController +@RequestMapping(path = "/bookings") +@RequiredArgsConstructor +public class BookingController { + private final BookingService bookingService; + + @GetMapping("/{id}") + public ResponseEntity getBooking( + @PathVariable long id, + @RequestHeader("X-Sharer-User-Id") long userId) { + var request = RestQueryBuilder.builder() + .method(HttpMethod.GET) + .path("/" + id) + .requestHead("X-Sharer-User-Id", String.valueOf(userId)) + .build(); + + return bookingService.executeQuery(request); + } + + @GetMapping + public ResponseEntity getBookingForUserId( + @RequestHeader("X-Sharer-User-Id") long userId) { + var request = RestQueryBuilder.builder() + .method(HttpMethod.GET) + .requestHead("X-Sharer-User-Id", String.valueOf(userId)) + .build(); + + return bookingService.executeQuery(request); + } + + @GetMapping("/owner") + public ResponseEntity getBookingForItemOwnerId( + @RequestHeader("X-Sharer-User-Id") long ownerId) { + var request = RestQueryBuilder.builder() + .method(HttpMethod.GET) + .path("/owner") + .requestHead("X-Sharer-User-Id", String.valueOf(ownerId)) + .build(); + + return bookingService.executeQuery(request); + } + + @PostMapping + public ResponseEntity postBooking( + @RequestHeader("X-Sharer-User-Id") long userId, + @Valid @RequestBody BookingDto bookingDto) { + var request = RestQueryBuilder.builder() + .method(HttpMethod.POST) + .requestHead("X-Sharer-User-Id", String.valueOf(userId)) + .body(bookingDto) + .build(); + + return bookingService.executeQuery(request); + } + + @PatchMapping("/{id}") + public ResponseEntity patchBooking( + @PathVariable long id, + @RequestParam(name = "approved") boolean approved, + @RequestHeader("X-Sharer-User-Id") long userId) { + var request = RestQueryBuilder.builder() + .method(HttpMethod.PATCH) + .path("/" + id + "?approved=" + (approved ? "true" : "false")) + .requestHead("X-Sharer-User-Id", String.valueOf(userId)) + .build(); + + return bookingService.executeQuery(request); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/BookingService.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingService.java new file mode 100644 index 0000000..ccfde5a --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingService.java @@ -0,0 +1,26 @@ +package ru.practicum.shareit.booking; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.rest.RestService; + +@Service +public class BookingService extends RestService { + private static final String API_PREFIX = "/bookings"; + + @Autowired + public BookingService( + @Value("${shareit-server.url}") String serverUrl, + RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java new file mode 100644 index 0000000..93e4e47 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.booking.dto; + + +import lombok.Builder; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.user.dto.UserDto; + +@Builder(toBuilder = true) +public record BookingDto( + Long id, + Long itemId, + BookingStatus status, + String start, + String end, + UserDto booker, + ItemDto item +){ } diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingStatus.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingStatus.java new file mode 100644 index 0000000..3353e65 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingStatus.java @@ -0,0 +1,8 @@ +package ru.practicum.shareit.booking.dto; + +public enum BookingStatus { + WAITING, + APPROVED, + REJECTED, + CANCELED +} diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingStatusRequestDto.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingStatusRequestDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dto/BookingStatusRequestDto.java rename to gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingStatusRequestDto.java diff --git a/src/main/java/ru/practicum/shareit/booking/dto/ReviewDto.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/ReviewDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dto/ReviewDto.java rename to gateway/src/main/java/ru/practicum/shareit/booking/dto/ReviewDto.java diff --git a/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java new file mode 100644 index 0000000..946cddc --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -0,0 +1,118 @@ +package ru.practicum.shareit.item; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.ItemToUpdateDto; +import ru.practicum.shareit.rest.RestQueryBuilder; + + +@RestController +@RequestMapping("/items") +@RequiredArgsConstructor +public class ItemController { + private final ItemService itemService; + + @GetMapping + public ResponseEntity getItems(@RequestHeader("X-Sharer-User-Id") long userId) { + var restQuery = RestQueryBuilder.builder() + .method(HttpMethod.GET) + .requestHeadUserId(userId) + .build(); + + return itemService.executeQuery(restQuery); + } + + @GetMapping("/{id}") + public ResponseEntity getItem(@PathVariable long id) { + var restQuery = RestQueryBuilder.builder() + .method(HttpMethod.GET) + .path("/" + id) + .build(); + + return itemService.executeQuery(restQuery); + } + + @GetMapping("/search") + public ResponseEntity findItemsByText(@RequestParam String text) { + var restQuery = RestQueryBuilder.builder() + .method(HttpMethod.GET) + .path("/search?text=" + text) + .build(); + + return itemService.executeQuery(restQuery); + } + + @PostMapping + public ResponseEntity postItem( + @Valid @RequestBody ItemDto itemDto, + @RequestHeader("X-Sharer-User-Id") long userId) { + var restQuery = RestQueryBuilder.builder() + .method(HttpMethod.POST) + .requestHeadUserId(userId) + .body(itemDto) + .build(); + + return itemService.executeQuery(restQuery); + } + + @PostMapping("/{id}/comment") + public ResponseEntity postComment( + @PathVariable long id, + @RequestHeader("X-Sharer-User-Id") long userId, + @Valid @RequestBody CommentDto commentDto) { + var restQuery = RestQueryBuilder.builder() + .method(HttpMethod.POST) + .path("/" + id + "/comment") + .requestHeadUserId(userId) + .body(commentDto) + .build(); + + return itemService.executeQuery(restQuery); + } + + @PutMapping() + public ResponseEntity putItem( + @Valid @RequestBody ItemDto itemDto, + @RequestHeader("X-Sharer-User-Id") long userId) { + var restQuery = RestQueryBuilder.builder() + .method(HttpMethod.PUT) + .requestHeadUserId(userId) + .body(itemDto) + .build(); + + return itemService.executeQuery(restQuery); + } + + @PatchMapping("/{id}") + public ResponseEntity patchItem( + @PathVariable long id, + @Valid @RequestBody ItemToUpdateDto item, + @RequestHeader("X-Sharer-User-Id") long userId) { + var restQuery = RestQueryBuilder.builder() + .method(HttpMethod.PATCH) + .path("/" + id) + .requestHeadUserId(userId) + .body(item) + .build(); + + return itemService.executeQuery(restQuery); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteItem( + @PathVariable long id, + @RequestHeader("X-Sharer-User-Id") long userId) { + var restQuery = RestQueryBuilder.builder() + .method(HttpMethod.DELETE) + .path("/" + id) + .requestHeadUserId(userId) + .build(); + + return itemService.executeQuery(restQuery); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/item/ItemService.java b/gateway/src/main/java/ru/practicum/shareit/item/ItemService.java new file mode 100644 index 0000000..73eeea8 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemService.java @@ -0,0 +1,26 @@ +package ru.practicum.shareit.item; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.rest.RestService; + +@Service +public class ItemService extends RestService { + private static final String API_PREFIX = "/items"; + + @Autowired + public ItemService( + @Value("${shareit-server.url}") String serverUrl, + RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } +} diff --git a/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/dto/CommentDto.java rename to gateway/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java similarity index 89% rename from src/main/java/ru/practicum/shareit/item/dto/ItemDto.java rename to gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java index f208647..842bbd5 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -15,5 +15,6 @@ public record ItemDto( @NotNull Boolean available, Timestamp lastBooking, Timestamp nextBooking, - List comments + List comments, + Long requestId ) { } diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemToUpdateDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemToUpdateDto.java similarity index 91% rename from src/main/java/ru/practicum/shareit/item/dto/ItemToUpdateDto.java rename to gateway/src/main/java/ru/practicum/shareit/item/dto/ItemToUpdateDto.java index 8e023b4..af7523d 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemToUpdateDto.java +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/ItemToUpdateDto.java @@ -4,7 +4,6 @@ @Builder public record ItemToUpdateDto( - Long itemId, String name, String description, Boolean available diff --git a/gateway/src/main/java/ru/practicum/shareit/request/RequestController.java b/gateway/src/main/java/ru/practicum/shareit/request/RequestController.java new file mode 100644 index 0000000..4ba16fb --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/RequestController.java @@ -0,0 +1,61 @@ +package ru.practicum.shareit.request; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.request.dto.RequestDto; +import ru.practicum.shareit.rest.RestQueryBuilder; + +@RestController +@RequestMapping(path = "/requests") +@RequiredArgsConstructor +public class RequestController { + private final RequestService requestService; + + @GetMapping + public ResponseEntity getSelfRequests( + @RequestHeader("X-Sharer-User-Id") long userId) { + var query = RestQueryBuilder.builder() + .method(HttpMethod.GET) + .requestHead("X-Sharer-User-Id", String.valueOf(userId)) + .build(); + + return requestService.executeQuery(query); + } + + @GetMapping("/{id}") + public ResponseEntity getRequestById(@PathVariable long id) { + var query = RestQueryBuilder.builder() + .method(HttpMethod.GET) + .path("/" + id) + .build(); + + return requestService.executeQuery(query); + } + + @GetMapping("/all") + public ResponseEntity getOtherUserRequests(@RequestHeader("X-Sharer-User-Id") long userId) { + var query = RestQueryBuilder.builder() + .method(HttpMethod.GET) + .path("/all") + .requestHead("X-Sharer-User-Id", String.valueOf(userId)) + .build(); + + return requestService.executeQuery(query); + } + + @PostMapping + public ResponseEntity postRequestDto( + @Valid @RequestBody RequestDto requestDto, + @RequestHeader("X-Sharer-User-Id") long userId) { + var query = RestQueryBuilder.builder() + .method(HttpMethod.POST) + .requestHead("X-Sharer-User-Id", String.valueOf(userId)) + .body(requestDto) + .build(); + + return requestService.executeQuery(query); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/request/RequestService.java b/gateway/src/main/java/ru/practicum/shareit/request/RequestService.java new file mode 100644 index 0000000..72d8b4b --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/RequestService.java @@ -0,0 +1,26 @@ +package ru.practicum.shareit.request; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.rest.RestService; + +@Service +public class RequestService extends RestService { + private static final String API_PREFIX = "/requests"; + + @Autowired + public RequestService( + @Value("${shareit-server.url}") String serverUrl, + RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/request/dto/RequestDto.java b/gateway/src/main/java/ru/practicum/shareit/request/dto/RequestDto.java new file mode 100644 index 0000000..7b74515 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/dto/RequestDto.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.request.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import ru.practicum.shareit.item.dto.ItemDto; + +import java.sql.Timestamp; +import java.util.List; + +@Builder(toBuilder = true) +public record RequestDto( + Long id, + String text, + @NotBlank + String description, + Timestamp created, + List items +) { +} diff --git a/gateway/src/main/java/ru/practicum/shareit/rest/RestQueryBuilder.java b/gateway/src/main/java/ru/practicum/shareit/rest/RestQueryBuilder.java new file mode 100644 index 0000000..7db61e0 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/rest/RestQueryBuilder.java @@ -0,0 +1,57 @@ +package ru.practicum.shareit.rest; + +import org.springframework.http.HttpMethod; + +import java.util.Optional; + +public class RestQueryBuilder { + private final RestQueryDto.RestQueryDtoBuilder builder; + + private RestQueryBuilder(RestQueryDto.RestQueryDtoBuilder builder) { + this.builder = builder; + } + + public static RestQueryBuilder builder() { + return new RestQueryBuilder(RestQueryDto.builder()); + } + + public RestQueryDto build() { + return builder.build(); + } + + public RestQueryBuilder method(HttpMethod method) { + builder.httpMethod(method); + + return this; + } + + public RestQueryBuilder path(String path) { + builder.path(path); + + return this; + } + + public RestQueryBuilder requestHead(RestQueryHead restQueryHead) { + builder.requestHead(Optional.of(restQueryHead)); + + return this; + } + + public RestQueryBuilder requestHead(String name, String value) { + builder.requestHead(Optional.of(new RestQueryHead(name, value))); + + return this; + } + + public RestQueryBuilder requestHeadUserId(long userId) { + builder.requestHead(Optional.of(new RestQueryHead("X-Sharer-User-Id", String.valueOf(userId)))); + + return this; + } + + public RestQueryBuilder body(Object body) { + builder.body(Optional.of(body)); + + return this; + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/rest/RestQueryDto.java b/gateway/src/main/java/ru/practicum/shareit/rest/RestQueryDto.java new file mode 100644 index 0000000..3644988 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/rest/RestQueryDto.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.rest; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Value; +import org.springframework.http.HttpMethod; + +import java.util.Optional; + +@Builder(access = AccessLevel.PACKAGE) +@Value +public class RestQueryDto { + HttpMethod httpMethod; + @Builder.Default + String path = ""; + @Builder.Default + Optional requestHead = Optional.empty(); + @Builder.Default + Optional body = Optional.empty(); +} diff --git a/gateway/src/main/java/ru/practicum/shareit/rest/RestQueryHead.java b/gateway/src/main/java/ru/practicum/shareit/rest/RestQueryHead.java new file mode 100644 index 0000000..56076ba --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/rest/RestQueryHead.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.rest; + +public record RestQueryHead( + String name, + String value +) { +} diff --git a/gateway/src/main/java/ru/practicum/shareit/rest/RestService.java b/gateway/src/main/java/ru/practicum/shareit/rest/RestService.java new file mode 100644 index 0000000..ffecb63 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/rest/RestService.java @@ -0,0 +1,64 @@ +package ru.practicum.shareit.rest; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.*; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.Optional; + + +@Slf4j +@RequiredArgsConstructor +public abstract class RestService { + private final RestTemplate restTemplate; + + public ResponseEntity executeQuery(RestQueryDto restQueryDto) { + log.trace("try exchange " + restQueryDto); + + var headers = getHeaders(restQueryDto.getRequestHead()); + + HttpEntity restQueryEntity = restQueryDto.getBody() + .map(i -> new HttpEntity<>(i, headers)) + .orElse(new HttpEntity<>(headers)); + + try { + var response = restTemplate.exchange( + restQueryDto.getPath(), + restQueryDto.getHttpMethod(), + restQueryEntity, + Object.class); + + return prepareGatewayResponse(response); + } catch (HttpStatusCodeException e) { + return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray()); + } + } + + private HttpHeaders getHeaders(Optional head) { + HttpHeaders headers = new HttpHeaders(); + + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + + head.ifPresent(i -> headers.set(i.name(), i.value())); + + return headers; + } + + private ResponseEntity prepareGatewayResponse(ResponseEntity response) { + if (response.getStatusCode().is2xxSuccessful()) { + return response; + } + + ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.status(response.getStatusCode()); + + if (response.hasBody()) { + return responseBuilder.body(response.getBody()); + } + + return responseBuilder.build(); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/user/UserController.java b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java new file mode 100644 index 0000000..d9727f8 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java @@ -0,0 +1,71 @@ +package ru.practicum.shareit.user; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.rest.RestQueryBuilder; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.dto.UserToUpdateDto; + + +@RestController +@RequestMapping(path = "/users") +@RequiredArgsConstructor +public class UserController { + private final UserService userService; + + @GetMapping("/{id}") + public ResponseEntity getUser(@PathVariable long id) { + var query = RestQueryBuilder.builder() + .method(HttpMethod.GET) + .path("/" + id) + .build(); + + return userService.executeQuery(query); + } + + @PostMapping + public ResponseEntity postUser(@Valid @RequestBody UserDto user) { + var query = RestQueryBuilder.builder() + .method(HttpMethod.POST) + .body(user) + .build(); + + return userService.executeQuery(query); + } + + @PutMapping + public ResponseEntity putUser(@Valid @RequestBody UserDto user) { + var query = RestQueryBuilder.builder() + .method(HttpMethod.PUT) + .body(user) + .build(); + + return userService.executeQuery(query); + } + + @PatchMapping("/{id}") + public ResponseEntity patchUser( + @PathVariable long id, + @RequestBody @Valid UserToUpdateDto user) { + var query = RestQueryBuilder.builder() + .method(HttpMethod.PATCH) + .path("/" + id) + .body(user) + .build(); + + return userService.executeQuery(query); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteUser(@PathVariable long id) { + var query = RestQueryBuilder.builder() + .method(HttpMethod.DELETE) + .path("/" + id) + .build(); + + return userService.executeQuery(query); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/user/UserService.java b/gateway/src/main/java/ru/practicum/shareit/user/UserService.java new file mode 100644 index 0000000..5adf826 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserService.java @@ -0,0 +1,26 @@ +package ru.practicum.shareit.user; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.rest.RestService; + +@Service +public class UserService extends RestService { + private static final String API_PREFIX = "/users"; + + @Autowired + public UserService( + @Value("${shareit-server.url}") String serverUrl, + RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } +} diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/dto/UserDto.java rename to gateway/src/main/java/ru/practicum/shareit/user/dto/UserDto.java diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserToUpdateDto.java b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserToUpdateDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/dto/UserToUpdateDto.java rename to gateway/src/main/java/ru/practicum/shareit/user/dto/UserToUpdateDto.java diff --git a/gateway/src/main/resources/application.properties b/gateway/src/main/resources/application.properties new file mode 100644 index 0000000..2ee0851 --- /dev/null +++ b/gateway/src/main/resources/application.properties @@ -0,0 +1,7 @@ +logging.level.org.springframework.web.client.RestTemplate=DEBUG +#logging.level.org.apache.http=DEBUG +#logging.level.httpclient.wire=DEBUG + +server.port=8080 + +shareit-server.url=http://localhost:9090 \ No newline at end of file diff --git a/src/test/java/ru/practicum/shareit/ShareItTests.java b/gateway/src/test/java/ru/practicum/shareit/ShareItTests.java similarity index 100% rename from src/test/java/ru/practicum/shareit/ShareItTests.java rename to gateway/src/test/java/ru/practicum/shareit/ShareItTests.java diff --git a/gateway/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java new file mode 100644 index 0000000..eb4343f --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java @@ -0,0 +1,277 @@ +package ru.practicum.shareit.booking; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.dto.BookingStatus; +import ru.practicum.shareit.rest.RestQueryBuilder; +import ru.practicum.shareit.rest.RestQueryDto; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +public class BookingControllerTest { + @Mock + BookingService bookingService; + + @InjectMocks + BookingController bookingController; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void requestPathWithoutUserIdInHead() throws Exception { + List needIdUserFromHeadRequest = + List.of(get("/bookings/1"), + get("/bookings"), + get("/bookings/owner"), + post("/bookings"), + patch("/bookings/1?approved=true")); + + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + for (var requestBuilder: needIdUserFromHeadRequest) { + var result = mockMvc.perform(requestBuilder) + .andReturn(); + + var status = result + .getResponse() + .getStatus(); + + var path = result.getRequest().getMethod() + " '" + result.getRequest().getRequestURI() + "'"; + + Assertions.assertEquals(400, status, path + " was needed userid as RequestHeader \"X-Sharer-User-Id\""); + } + } + + @Test + public void getBookingTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + var dto = BookingDto.builder() + .id(10L) + .build(); + + var response = new ResponseEntity<>( + (Object) dto, + HttpStatus.OK); + + Mockito.when(bookingService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(get("/bookings/1") + .header("X-Sharer-User-Id", 1L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(10L), Long.class)); + + Mockito.verify(bookingService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.GET) + .path("/1") + .requestHeadUserId(1L) + .build()); + } + + @Test + void getItemsForUserIdTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + var expectedResult = List.of( + BookingDto.builder().id(1L).build(), + BookingDto.builder().id(2L).build(), + BookingDto.builder().id(3L).build()); + + var response = new ResponseEntity<>( + (Object) expectedResult, + HttpStatus.OK); + + Mockito.when(bookingService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(get("/bookings") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.[0].id",is(1L), Long.class)) + .andExpect(jsonPath("$.[1].id",is(2L), Long.class)) + .andExpect(jsonPath("$.[2].id",is(3L), Long.class)); + + Mockito.verify(bookingService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.GET) + .requestHeadUserId(10L) + .build()); + } + + @Test + void getItemsForItemOwnerIdTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + var expectedResult = List.of( + BookingDto.builder().id(1L).build(), + BookingDto.builder().id(2L).build(), + BookingDto.builder().id(3L).build()); + + var response = new ResponseEntity<>( + (Object) expectedResult, + HttpStatus.OK); + + Mockito.when(bookingService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(get("/bookings/owner") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.[0].id",is(1L), Long.class)) + .andExpect(jsonPath("$.[1].id",is(2L), Long.class)) + .andExpect(jsonPath("$.[2].id",is(3L), Long.class)); + + Mockito.verify(bookingService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.GET) + .path("/owner") + .requestHeadUserId(10L) + .build()); + } + + @Test + void postItemTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + var dto = BookingDto.builder() + .id(50L) + .build(); + + var response = new ResponseEntity<>( + (Object) BookingDto.builder().id(17L).build(), + HttpStatus.OK); + + Mockito.when(bookingService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(post("/bookings") + .header("X-Sharer-User-Id", 10L) + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(17L), Long.class)); + + Mockito.verify(bookingService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.POST) + .requestHeadUserId(10L) + .body(dto) + .build()); + } + + @Test + void postItemWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + mockMvc.perform(post("/bookings") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().is(400)); + } + + @Test + void patchItemApprovedTrueTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + var dto = BookingDto.builder() + .id(15L) + .status(BookingStatus.APPROVED) + .build(); + + var response = new ResponseEntity<>( + (Object) dto, + HttpStatus.OK); + + Mockito.when(bookingService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(patch("/bookings/10?approved=true") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status",is(BookingStatus.APPROVED.name()), String.class)); + + Mockito.verify(bookingService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.PATCH) + .path("/10?approved=true") + .requestHeadUserId(10L) + .build()); + } + + @Test + void patchItemApprovedFalseTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + var dto = BookingDto.builder() + .id(10L) + .status(BookingStatus.REJECTED) + .build(); + + var response = new ResponseEntity<>( + (Object) dto, + HttpStatus.OK); + + Mockito.when(bookingService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(patch("/bookings/10?approved=false") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status",is(BookingStatus.REJECTED.name()), String.class)); + + Mockito.verify(bookingService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.PATCH) + .path("/10?approved=false") + .requestHeadUserId(10L) + .build()); + } + + @Test + void patchItemWithoutParamsTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + mockMvc.perform(patch("/bookings/100") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().is(400)); + } +} diff --git a/gateway/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java new file mode 100644 index 0000000..c5901e7 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java @@ -0,0 +1,371 @@ +package ru.practicum.shareit.item; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.ItemToUpdateDto; +import ru.practicum.shareit.rest.RestQueryBuilder; +import ru.practicum.shareit.rest.RestQueryDto; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +public class ItemControllerTest { + @Mock + ItemService itemService; + + @InjectMocks + ItemController itemController; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void requestPathWithoutUserIdInHead() throws Exception { + List needIdUserFromHeadRequest = + List.of(post("/items"), + post("/items/1/comment"), + put("/items"), + patch("/items/1"), + delete("/items/1")); + + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + for (var requestBuilder: needIdUserFromHeadRequest) { + var result = mockMvc.perform(requestBuilder) + .andReturn(); + + var status = result + .getResponse() + .getStatus(); + + var path = result.getRequest().getMethod() + " '" + result.getRequest().getRequestURI() + "'"; + + Assertions.assertEquals(400, status, path + " was needed userid as RequestHeader \"X-Sharer-User-Id\""); + } + } + + @Test + public void getItemTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + var dto = ItemDto.builder() + .id(10L) + .build(); + + var response = new ResponseEntity<>( + (Object) dto, + HttpStatus.OK); + + Mockito.when(itemService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(get("/items/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(10L), Long.class)); + + Mockito.verify(itemService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.GET) + .path("/1") + .build()); + } + + @Test + void getItemsForUserIdTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + var expectedResult = List.of( + ItemDto.builder().id(11L).build(), + ItemDto.builder().id(42L).build(), + ItemDto.builder().id(37L).build()); + + var response = new ResponseEntity<>( + (Object) expectedResult, + HttpStatus.OK); + + Mockito.when(itemService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(get("/items") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.[0].id",is(11L), Long.class)) + .andExpect(jsonPath("$.[1].id",is(42L), Long.class)) + .andExpect(jsonPath("$.[2].id",is(37L), Long.class)); + + Mockito.verify(itemService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.GET) + .requestHeadUserId(10L) + .build()); + } + + @Test + void findItemsByTextTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + var expectedResult = List.of( + ItemDto.builder().id(1L).build(), + ItemDto.builder().id(42L).build(), + ItemDto.builder().id(37L).build()); + + var response = new ResponseEntity<>( + (Object) expectedResult, + HttpStatus.OK); + + Mockito.when(itemService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(get("/items/search?text=abc")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.[0].id",is(1L), Long.class)) + .andExpect(jsonPath("$.[1].id",is(42L), Long.class)) + .andExpect(jsonPath("$.[2].id",is(37L), Long.class)); + + Mockito.verify(itemService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.GET) + .path("/search?text=abc") + .build()); + } + + @Test + void postItemTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + var response = new ResponseEntity<>( + (Object) ItemDto.builder().id(50L).build(), + HttpStatus.OK); + + Mockito.when(itemService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + var dto = ItemDto.builder() + .id(13L) + .name("la") + .available(true) + .description("ma") + .build(); + + mockMvc.perform(post("/items") + .header("X-Sharer-User-Id", 10L) + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(50L), Long.class)); + + Mockito.verify(itemService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.POST) + .requestHeadUserId(10L) + .body(dto) + .build()); + } + + @Test + void postItemWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + mockMvc.perform(post("/items") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().is(400)); + } + + @Test + void postCommentTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + var commentDto = CommentDto.builder() + .id(47L) + .text("la") + .build(); + + var response = new ResponseEntity<>( + (Object) commentDto, + HttpStatus.OK); + + Mockito.when(itemService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(post("/items/13/comment") + .header("X-Sharer-User-Id", 10L) + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(commentDto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(47L), Long.class)); + + Mockito.verify(itemService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.POST) + .path("/13/comment") + .requestHeadUserId(10L) + .body(commentDto) + .build()); + } + + @Test + void postCommentWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + mockMvc.perform(post("/items/13/comment") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().is(400)); + } + + @Test + void putItemTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + var response = new ResponseEntity<>( + (Object) ItemDto.builder().id(50L).build(), + HttpStatus.OK); + + Mockito.when(itemService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + var dto = ItemDto.builder() + .id(13L) + .name("la") + .description("ma") + .available(true) + .build(); + + mockMvc.perform(put("/items") + .header("X-Sharer-User-Id", 10L) + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(50L), Long.class)); + + Mockito.verify(itemService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.PUT) + .requestHeadUserId(10L) + .body(dto) + .build()); + } + + @Test + void putItemWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + mockMvc.perform(put("/items") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().is(400)); + } + + @Test + void patchItemTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + var dto = ItemToUpdateDto.builder() + .build(); + + var response = new ResponseEntity<>( + (Object) ItemDto.builder().id(50L).build(), + HttpStatus.OK); + + Mockito.when(itemService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(patch("/items/17") + .header("X-Sharer-User-Id", 10L) + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(50L), Long.class)); + + Mockito.verify(itemService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.PATCH) + .path("/17") + .requestHeadUserId(10L) + .body(dto) + .build()); + } + + @Test + void patchItemWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + mockMvc.perform(put("/items") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().is(400)); + } + + @Test + void deleteItemTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + var dto = ItemDto.builder() + .id(50L) + .build(); + + var response = new ResponseEntity<>( + (Object) dto, + HttpStatus.OK); + + Mockito.when(itemService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(delete("/items/17") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(50L), Long.class)); + + Mockito.verify(itemService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.DELETE) + .path("/17") + .requestHeadUserId(10L) + .build()); + } +} diff --git a/gateway/src/test/java/ru/practicum/shareit/request/RequestControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/request/RequestControllerTest.java new file mode 100644 index 0000000..9a99a7b --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/request/RequestControllerTest.java @@ -0,0 +1,200 @@ +package ru.practicum.shareit.request; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import ru.practicum.shareit.request.dto.RequestDto; +import ru.practicum.shareit.rest.RestQueryBuilder; +import ru.practicum.shareit.rest.RestQueryDto; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +public class RequestControllerTest { + @Mock + RequestService requestService; + + @InjectMocks + RequestController requestController; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void requestPathWithoutUserIdInHead() throws Exception { + List needIdUserFromHeadRequest = + List.of(get("/requests"), + get("/requests/all"), + post("/requests")); + + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(requestController) + .build(); + + for (var requestBuilder: needIdUserFromHeadRequest) { + var result = mockMvc.perform(requestBuilder) + .andReturn(); + + var status = result + .getResponse() + .getStatus(); + + var path = result.getRequest().getMethod() + " '" + result.getRequest().getRequestURI() + "'"; + + Assertions.assertEquals(400, status, path + " was needed userid as RequestHeader \"X-Sharer-User-Id\""); + } + } + + @Test + public void getSelfRequestsTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(requestController) + .build(); + + List expectedRequests = List.of( + RequestDto.builder().id(13L).build(), + RequestDto.builder().id(24L).build(), + RequestDto.builder().id(37L).build() + ); + + var response = new ResponseEntity<>( + (Object)expectedRequests, + HttpStatus.OK); + + Mockito.when(requestService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(get("/requests") + .header("X-Sharer-User-Id", 7L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id", is(13L), Long.class)) + .andExpect(jsonPath("$[1].id", is(24L), Long.class)) + .andExpect(jsonPath("$[2].id", is(37L), Long.class)); + + Mockito.verify(requestService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.GET) + .requestHeadUserId(7L) + .build()); + } + + @Test + void getRequestByIdTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(requestController) + .build(); + + var expectedResult = RequestDto.builder().id(13L).build(); + + var response = new ResponseEntity<>( + (Object) expectedResult, + HttpStatus.OK); + + Mockito.when(requestService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(get("/requests/23")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(13L), Long.class)); + + Mockito.verify(requestService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.GET) + .path("/23") + .build()); + } + + @Test + void getOtherUserRequestsTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(requestController) + .build(); + + List expectedRequests = List.of( + RequestDto.builder().id(13L).build(), + RequestDto.builder().id(24L).build(), + RequestDto.builder().id(37L).build() + ); + + var response = new ResponseEntity<>( + (Object) expectedRequests, + HttpStatus.OK); + + Mockito.when(requestService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(get("/requests/all") + .header("X-Sharer-User-Id", 7L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id", is(13L), Long.class)) + .andExpect(jsonPath("$[1].id", is(24L), Long.class)) + .andExpect(jsonPath("$[2].id", is(37L), Long.class)); + + Mockito.verify(requestService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.GET) + .path("/all") + .requestHeadUserId(7L) + .build()); + } + + @Test + void postRequestDtoTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(requestController) + .build(); + + var response = new ResponseEntity<>( + (Object) RequestDto.builder().id(50L).build(), + HttpStatus.OK); + + Mockito.when(requestService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + var dto = RequestDto.builder() + .id(13L) + .description("la") + .build(); + + mockMvc.perform(post("/requests") + .header("X-Sharer-User-Id", 10L) + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(50L), Long.class)); + + Mockito.verify(requestService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.POST) + .requestHeadUserId(10L) + .body(dto) + .build()); + } + + @Test + void postRequestWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(requestController) + .build(); + + mockMvc.perform(post("/requests") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().is(400)); + } +} diff --git a/gateway/src/test/java/ru/practicum/shareit/user/UserControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/user/UserControllerTest.java new file mode 100644 index 0000000..2d581b8 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/user/UserControllerTest.java @@ -0,0 +1,215 @@ +package ru.practicum.shareit.user; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.rest.RestQueryBuilder; +import ru.practicum.shareit.rest.RestQueryDto; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.dto.UserToUpdateDto; + +import java.nio.charset.StandardCharsets; + +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +public class UserControllerTest { + @Mock + UserService userService; + + @InjectMocks + UserController userController; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void getUserTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + var dto = UserDto.builder() + .id(47L) + .name("la") + .email("email@mail.ru") + .build(); + + var response = new ResponseEntity<>( + (Object) dto, + HttpStatus.OK); + + Mockito.when(userService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(get("/users/17")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(47L), Long.class)); + + Mockito.verify(userService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.GET) + .path("/17") + .build()); + } + + @Test + void postUserTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + var response = new ResponseEntity<>( + (Object) ItemDto.builder().id(50L).build(), + HttpStatus.OK); + + Mockito.when(userService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + var dto = UserDto.builder() + .name("la") + .email("email@mail.ru") + .build(); + + mockMvc.perform(post("/users") + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(50L), Long.class)); + + Mockito.verify(userService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.POST) + .body(dto) + .build()); + } + + @Test + void postUserWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + mockMvc.perform(post("/users")) + .andExpect(status().is(400)); + } + + @Test + void putUserTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + var response = new ResponseEntity<>( + (Object) ItemDto.builder().id(52L).build(), + HttpStatus.OK); + + Mockito.when(userService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + var dto = UserDto.builder() + .id(10L) + .name("la") + .email("email@mail.ru") + .build(); + + mockMvc.perform(put("/users") + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(52L), Long.class)); + + Mockito.verify(userService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.PUT) + .body(dto) + .build()); + } + + @Test + void putUserWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + mockMvc.perform(put("/users")) + .andExpect(status().is(400)); + } + + @Test + void patchUserTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + var response = new ResponseEntity<>( + (Object) ItemDto.builder().id(542L).build(), + HttpStatus.OK); + + Mockito.when(userService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + var dto = UserToUpdateDto.builder().name("la").build(); + + mockMvc.perform(patch("/users/13") + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(542L), Long.class)); + + Mockito.verify(userService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.PATCH) + .path("/13") + .body(dto) + .build()); + } + + @Test + void patchUserWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + mockMvc.perform(patch("/users/13")) + .andExpect(status().is(400)); + } + + @Test + void deleteUserTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + var response = new ResponseEntity<>( + (Object) ItemDto.builder().id(54L).build(), + HttpStatus.OK); + + Mockito.when(userService.executeQuery(Mockito.any(RestQueryDto.class))) + .thenReturn(response); + + mockMvc.perform(delete("/users/13")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(54L), Long.class)); + + Mockito.verify(userService, Mockito.times(1)) + .executeQuery(RestQueryBuilder.builder() + .method(HttpMethod.DELETE) + .path("/13") + .build()); + } +} diff --git a/pom.xml b/pom.xml index 3b4c514..8c95aa8 100644 --- a/pom.xml +++ b/pom.xml @@ -12,10 +12,15 @@ ru.practicum shareit 0.0.1-SNAPSHOT + pom - ShareIt + ShareIt + + gateway + server + - + 21 diff --git a/postman/sprint.json b/postman/sprint.json new file mode 100644 index 0000000..6e87b6b --- /dev/null +++ b/postman/sprint.json @@ -0,0 +1,4052 @@ +{ + "info": { + "_postman_id": "7fc26681-0775-4307-885f-fd53560f0317", + "name": "Sprint 16 ShareIt (add-item-requests-and-gateway)", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "23073145", + "_collection_link": "https://universal-shadow-295426.postman.co/workspace/My-Workspace~4200f6aa-0504-44b1-8a1d-707d0dcbd5ce/collection/13708500-7fc26681-0775-4307-885f-fd53560f0317?action=share&source=collection_link&creator=23073145" + }, + "item": [ + { + "name": "users", + "item": [ + { + "name": "Create user", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " pm.collectionVariables.set(\"userName\", user.name);\r", + " pm.collectionVariables.set(\"userEmail\", user.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200 or 201\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([200,201]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{userName}}\",\n \"email\": \"{{userEmail}}\"\n}" + }, + "url": { + "raw": "localhost:8080/users", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "users" + ] + } + }, + "response": [] + }, + { + "name": "Create user without email", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user2 = rnd.getUser();\r", + " pm.collectionVariables.set(\"userName\", user2.name);\r", + " pm.collectionVariables.set(\"userEmail\", user2.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{userName}}\"\n}" + }, + "url": { + "raw": "localhost:8080/users", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "users" + ] + } + }, + "response": [] + }, + { + "name": "Create 2 users with same email", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user1 = rnd.getUser();\r", + " us = await api.addUser(user1);\r", + " user2 = rnd.getUser();\r", + " user2.email = user1.email;\r", + " pm.collectionVariables.set(\"userName\", user2.name);\r", + " pm.collectionVariables.set(\"userEmail\", user2.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 409\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([409, 500]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{userName}}\",\n \"email\": \"{{userEmail}}\"\n}" + }, + "url": { + "raw": "localhost:8080/users", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "users" + ] + } + }, + "response": [] + }, + { + "name": "Create user with invalid email", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " pm.collectionVariables.set(\"userName\", user.name);\r", + " pm.collectionVariables.set(\"userEmail\", user.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{userName}}\",\n \"email\": \"user.com\"\n}" + }, + "url": { + "raw": "localhost:8080/users", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "users" + ] + } + }, + "response": [] + }, + { + "name": "User update", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " us = await api.addUser(user);\r", + " pm.collectionVariables.set(\"userId\", us.id);\r", + " us = rnd.getUser()\r", + " pm.collectionVariables.set(\"userName\", us.name);\r", + " pm.collectionVariables.set(\"userEmail\", us.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.be.ok;\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "pm.test(\"Test user 'id' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('id');\r", + " var id = pm.collectionVariables.get(\"userId\");\r", + " pm.expect(jsonData.id, '\"id\" must be ' + id).to.eql(Number(id));\r", + "});\r", + "pm.test(\"Test user 'email' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('email');\r", + " var email = pm.collectionVariables.get(\"userEmail\");\r", + " pm.expect(jsonData.email, '\"email\" must be ' + email).to.eql(email);\r", + "});\r", + "pm.test(\"Test user 'name' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('name');\r", + " var name = pm.collectionVariables.get(\"userName\");\r", + " pm.expect(jsonData.name, '\"name\" must be ' + name).to.eql(name);\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{userName}}\",\n \"email\": \"{{userEmail}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [] + }, + { + "name": "User update name", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " us = await api.addUser(user);\r", + " pm.collectionVariables.set(\"userId\", us.id);\r", + " us = rnd.getUser()\r", + " pm.collectionVariables.set(\"userName\", us.name);\r", + " pm.collectionVariables.set(\"userEmail\", us.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.be.ok;\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "pm.test(\"Test user 'id' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('id');\r", + " var id = pm.collectionVariables.get(\"userId\");\r", + " pm.expect(jsonData.id, '\"id\" must be ' + id).to.eql(Number(id));\r", + "});\r", + "pm.test(\"Test user 'name' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('name');\r", + " var name = pm.collectionVariables.get(\"userName\");\r", + " pm.expect(jsonData.name, '\"name\" must be ' + name).to.eql(name);\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{userName}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [] + }, + { + "name": "User update email", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " us = await api.addUser(user);\r", + " pm.collectionVariables.set(\"userId\", us.id);\r", + " us = rnd.getUser()\r", + " pm.collectionVariables.set(\"userName\", us.name);\r", + " pm.collectionVariables.set(\"userEmail\", us.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.be.ok;\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "pm.test(\"Test user 'id' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('id');\r", + " var id = pm.collectionVariables.get(\"userId\");\r", + " pm.expect(jsonData.id, '\"id\" must be ' + id).to.eql(Number(id));\r", + "});\r", + "pm.test(\"Test user 'email' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('email');\r", + " var email = pm.collectionVariables.get(\"userEmail\");\r", + " pm.expect(jsonData.email, '\"email\" must be ' + email).to.eql(email);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{userEmail}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [] + }, + { + "name": "User update with existing email", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " us = await api.addUser(user);\r", + " user2 = rnd.getUser();\r", + " us2 = await api.addUser(user2)\r", + " pm.collectionVariables.set(\"userId\", us2.id);\r", + " usa = rnd.getUser()\r", + " pm.collectionVariables.set(\"userName\", usa.name);\r", + " pm.collectionVariables.set(\"userEmail\", user.email);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 409\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([409, 500]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{userName}}\",\n \"email\": \"{{userEmail}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [] + }, + { + "name": "Get user", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " us = await api.addUser(user);\r", + " pm.collectionVariables.set(\"userId\", us.id);\r", + " pm.collectionVariables.set(\"userName\", us.name);\r", + " pm.collectionVariables.set(\"userEmail\", us.email);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.be.ok;\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "pm.test(\"Test user 'id' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('id');\r", + " var id = pm.collectionVariables.get(\"userId\");\r", + " pm.expect(jsonData.id, '\"id\" must be ' + id).to.eql(Number(id));\r", + "});\r", + "pm.test(\"Test user 'email' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('email');\r", + " var email = pm.collectionVariables.get(\"userEmail\");\r", + " pm.expect(jsonData.email, '\"email\" must be ' + email).to.eql(email);\r", + "});\r", + "pm.test(\"Test user 'name' field\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('name');\r", + " var name = pm.collectionVariables.get(\"userName\");\r", + " pm.expect(jsonData.name, '\"name\" must be ' + name).to.eql(name);\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [] + }, + { + "name": "User delete", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([200,204]);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = rnd.getUser();\r", + " us = await api.addUser(user);\r", + " pm.collectionVariables.set(\"userId\", us.id);\r", + " pm.collectionVariables.set(\"userName\", us.name);\r", + " pm.collectionVariables.set(\"userEmail\", us.email);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/users/{{userId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Item", + "item": [ + { + "name": "Create Item", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " item = rnd.getItem();\r", + " pm.collectionVariables.set(\"item\", item);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200 or 201\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([200,201]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "var item = pm.collectionVariables.get(\"item\");\r", + "\r", + "pm.test(\"Response data equal to request\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('id');\r", + " pm.expect(jsonData).to.have.property('name');\r", + " pm.expect(jsonData).to.have.property('description');\r", + " pm.expect(jsonData).to.have.property('available');\r", + " pm.expect(jsonData.name, `\"name\" must be ${item.name}`).to.eql(item.name);\r", + " pm.expect(jsonData.description, `\"description\" must be ${item.description}`).to.eql(item.description);\r", + " pm.expect(jsonData.available.toString(), `\"available\" must be ${item.available}`).to.eql(item.available.toString());\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{itemName}}\",\n \"description\": \"{{itemDescription}}\",\n \"available\": {{itemAvailable}}\n}" + }, + "url": { + "raw": "localhost:8080/items", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "items" + ] + } + }, + "response": [] + }, + { + "name": "Create Item on request", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user1 = await api.addUser(rnd.getUser());\r", + " var request = await api.addRequest(rnd.getRequest(), user1.id);\r", + " pm.collectionVariables.set(\"requestId\", request.id);\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + "\r", + " item = rnd.getItem();\r", + " pm.collectionVariables.set(\"item\", item);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + "\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200 or 201\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([200,201]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "var item = pm.collectionVariables.get(\"item\");\r", + "\r", + "pm.test(\"Response data equal to request\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('id');\r", + " pm.expect(jsonData).to.have.property('name');\r", + " pm.expect(jsonData).to.have.property('description');\r", + " pm.expect(jsonData).to.have.property('available');\r", + " pm.expect(jsonData.name, `\"name\" must be ${item.name}`).to.eql(item.name);\r", + " pm.expect(jsonData.description, `\"description\" must be ${item.description}`).to.eql(item.description);\r", + " pm.expect(jsonData.available.toString(), `\"available\" must be ${item.available}`).to.eql(item.available.toString());\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{itemName}}\",\n \"description\": \"{{itemDescription}}\",\n \"available\": {{itemAvailable}},\n \"requestId\": {{requestId}}\n}" + }, + "url": { + "raw": "localhost:8080/items", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "items" + ] + } + }, + "response": [] + }, + { + "name": "Create Item without name on request", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user1 = await api.addUser(rnd.getUser());\r", + " var request = await api.addRequest(rnd.getRequest(), user1.id);\r", + " pm.collectionVariables.set(\"requestId\", request.id);\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + "\r", + " item = rnd.getItem();\r", + " pm.collectionVariables.set(\"item\", item);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + "\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "pm.test(\"Response data have error\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('error');\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"description\": \"{{itemDescription}}\",\n \"available\": {{itemAvailable}},\n \"requestId\": {{requestId}}\n}" + }, + "url": { + "raw": "localhost:8080/items", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "items" + ] + } + }, + "response": [] + }, + { + "name": "Create Item without description on request", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user1 = await api.addUser(rnd.getUser());\r", + " var request = await api.addRequest(rnd.getRequest(), user1.id);\r", + " pm.collectionVariables.set(\"requestId\", request.id);\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + "\r", + " item = rnd.getItem();\r", + " pm.collectionVariables.set(\"item\", item);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + "\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "pm.test(\"Response data have error\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('error');\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{itemName}}\",\n \"available\": {{itemAvailable}},\n \"requestId\": {{requestId}}\n}" + }, + "url": { + "raw": "localhost:8080/items", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "items" + ] + } + }, + "response": [] + }, + { + "name": "Create Item without available on request", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user1 = await api.addUser(rnd.getUser());\r", + " var request = await api.addRequest(rnd.getRequest(), user1.id);\r", + " pm.collectionVariables.set(\"requestId\", request.id);\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + "\r", + " item = rnd.getItem();\r", + " pm.collectionVariables.set(\"item\", item);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " pm.collectionVariables.set(\"itemAvailable\", item.available);\r", + " pm.collectionVariables.set(\"itemDescription\", item.description);\r", + "\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "pm.test(\"Response data have error\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('error');\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"{{itemName}}\",\n \"description\": \"{{itemDescription}}\",\n \"requestId\": {{requestId}}\n}" + }, + "url": { + "raw": "localhost:8080/items", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "items" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "requests", + "item": [ + { + "name": "Create request", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " request1 = rnd.getRequest();\r", + " pm.collectionVariables.set(\"requestDescription\", request1.description);\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200 or 201\", function () {\r", + " pm.expect(pm.response.code).to.be.oneOf([200,201]);\r", + "});\r", + "pm.test(\"Response have body\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "pm.test(\"Response data equal to request\", function () {\r", + " var description = pm.collectionVariables.get(\"requestDescription\");\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('id');\r", + " pm.expect(jsonData).to.have.property('description');\r", + " pm.expect(jsonData).to.have.property('created');\r", + " pm.expect(jsonData.description, `\"description\" must be ${description}`).to.eql(description);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}" + } + ], + "body": { + "mode": "raw", + "raw": "{ \n \"description\": \"{{requestDescription}}\"\n}" + }, + "url": { + "raw": "localhost:8080/requests", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "requests" + ] + } + }, + "response": [] + }, + { + "name": "Get user requests", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user.id);\r", + " request1 = await api.addRequest(rnd.getRequest(), user.id);\r", + " request2 = await api.addRequest(rnd.getRequest(), user.id);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.be.ok;\r", + "});\r", + "pm.test(\"User requests amount\", function () {\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData.length, 'List length must be 2').to.eql(2);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}" + } + ], + "url": { + "raw": "localhost:8080/requests", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "requests" + ] + } + }, + "response": [] + }, + { + "name": "Get user request by id", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user1 = await api.addUser(rnd.getUser());\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + " req = await api.addRequest(rnd.getRequest(), user1.id);\r", + " pm.collectionVariables.set(\"requestId\", req.id);\r", + " item = await api.addItem(rnd.getItemForRequest(req.id), user2.id);\r", + " pm.collectionVariables.set(\"itemName\", item.name);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {\r", + " pm.response.to.be.ok;\r", + "});\r", + "pm.test(\"User requests amount\", function () {\r", + " var name = pm.collectionVariables.get(\"itemName\");\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.have.property('id');\r", + " pm.expect(jsonData).to.have.property('description');\r", + " pm.expect(jsonData).to.have.property('created');\r", + " pm.expect(jsonData).to.have.property('items');\r", + " pm.expect(jsonData.items[0].name, `\"item name\" must be ${name}`).to.eql(name);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}" + } + ], + "url": { + "raw": "localhost:8080/requests/{{requestId}}", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "requests", + "{{requestId}}" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "bookings", + "item": [ + { + "name": "Booking unavailable item", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " var moment = require('moment');\r", + " const currentDate = moment();\r", + " var start = currentDate.clone().add(1, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " var end = currentDate.clone().add(2, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " pm.collectionVariables.set('start', start);\r", + " pm.collectionVariables.set('end', end);\r", + " \r", + " user1 = await api.addUser(rnd.getUser());\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + " it = rnd.getItem()\r", + " it.available = false\r", + " item = await api.addItem(it, user1.id)\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"itemId\": {{itemId}},\n \"start\": \"{{start}}\",\n \"end\": \"{{end}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/bookings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "bookings" + ] + } + }, + "response": [] + }, + { + "name": "Booking create failed by wrong userId", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 500\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([500, 404, 403]);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " var moment = require('moment');\r", + " const currentDate = moment();\r", + " var start = currentDate.clone().add(1, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " var end = currentDate.clone().add(2, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " pm.collectionVariables.set('start', start);\r", + " pm.collectionVariables.set('end', end);\r", + " \r", + " user1 = await api.addUser(rnd.getUser());\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user2.id + 1);\r", + " it = rnd.getItem()\r", + " it.available = false\r", + " item = await api.addItem(it, user1.id)\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"itemId\": {{itemId}},\n \"start\": \"{{start}}\",\n \"end\": \"{{end}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/bookings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "bookings" + ] + } + }, + "response": [] + }, + { + "name": "Booking create failed by not found itemId", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 404\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([404]);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " var moment = require('moment');\r", + " const currentDate = moment();\r", + " var start = currentDate.clone().add(1, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " var end = currentDate.clone().add(2, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " pm.collectionVariables.set('start', start);\r", + " pm.collectionVariables.set('end', end);\r", + " \r", + " user1 = await api.addUser(rnd.getUser());\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + " it = rnd.getItem()\r", + " it.available = false\r", + " item = await api.addItem(it, user1.id)\r", + " pm.collectionVariables.set(\"itemId\", item.id + 1);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"itemId\": {{itemId}},\n \"start\": \"{{start}}\",\n \"end\": \"{{end}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/bookings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "bookings" + ] + } + }, + "response": [] + }, + { + "name": "Booking create failed by end in past", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " var moment = require('moment');\r", + " const currentDate = moment();\r", + " var start = currentDate.clone().subtract(3, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " var end = currentDate.clone().subtract(2, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " pm.collectionVariables.set('start', start);\r", + " pm.collectionVariables.set('end', end);\r", + " \r", + " user1 = await api.addUser(rnd.getUser());\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + " it = rnd.getItem()\r", + " it.available = false\r", + " item = await api.addItem(it, user1.id)\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"itemId\": {{itemId}},\n \"start\": \"{{start}}\",\n \"end\": \"{{end}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/bookings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "bookings" + ] + } + }, + "response": [] + }, + { + "name": "Booking create failed by start equal end", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " var moment = require('moment');\r", + " const currentDate = moment();\r", + " var start = currentDate.clone().add(1, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " var end = currentDate.clone().add(1, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " pm.collectionVariables.set('start', start);\r", + " pm.collectionVariables.set('end', end);\r", + " \r", + " user1 = await api.addUser(rnd.getUser());\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + " it = rnd.getItem()\r", + " it.available = false\r", + " item = await api.addItem(it, user1.id)\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"itemId\": {{itemId}},\n \"start\": \"{{start}}\",\n \"end\": \"{{end}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/bookings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "bookings" + ] + } + }, + "response": [] + }, + { + "name": "Booking create failed by start equal null", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "var moment = require('moment');\r", + "\r", + "var start = moment().add(2, 'd');\r", + "pm.environment.set('start_null', start.format('YYYY-MM-DDTHH:mm:ss'));\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"itemId\": {{itemId}},\n \"end\": \"{{end}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/bookings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "bookings" + ] + } + }, + "response": [] + }, + { + "name": "Booking create failed by end equal null", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "var moment = require('moment');\r", + "\r", + "var start = moment().add(2, 'd');\r", + "pm.environment.set('end_null', start.format('YYYY-MM-DDTHH:mm:ss'));\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"itemId\": {{itemId}},\n \"start\": \"{{start}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/bookings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "bookings" + ] + } + }, + "response": [] + }, + { + "name": "Booking create failed by start in past", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([400, 500]);", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " var moment = require('moment');\r", + " const currentDate = moment();\r", + " var start = currentDate.clone().subtract(1, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " var end = currentDate.clone().add(2, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " pm.collectionVariables.set('start', start);\r", + " pm.collectionVariables.set('end', end);\r", + " \r", + " user1 = await api.addUser(rnd.getUser());\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + " it = rnd.getItem()\r", + " it.available = false\r", + " item = await api.addItem(it, user1.id)\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"itemId\": {{itemId}},\n \"start\": \"{{start}}\",\n \"end\": \"{{end}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/bookings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "bookings" + ] + } + }, + "response": [] + }, + { + "name": "Booking available item", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200 or 201\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([200,201]);", + "});", + "pm.test(\"Has booking create response\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + "});", + "var item = pm.collectionVariables.get('item')", + "var user1 = pm.collectionVariables.get('user1')", + "var user2 = pm.collectionVariables.get('user2')", + "var start = pm.collectionVariables.get('start')", + "var end = pm.collectionVariables.get('end')", + "pm.test(\"Test booking 'start' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('start');", + " pm.expect(jsonData.start, '\"start\" must be \"' + pm.collectionVariables.get('start') + '\"').to.eql(pm.collectionVariables.get('start'));", + "});", + "pm.test(\"Test booking 'end' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('end');", + " pm.expect(jsonData.end, '\"end\" must be \"' + pm.collectionVariables.get('end') + '\"').to.eql(pm.collectionVariables.get('end'));", + "});", + "pm.test(\"Test booking 'status' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('status');", + " pm.expect(jsonData.status, '\"status\" must be \"WAITING\"').to.eql('WAITING');", + "});", + "pm.test(\"Test booking 'booker.id' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('booker');", + " pm.expect(jsonData.booker).to.have.property('id');", + " pm.expect(jsonData.booker.id, '\"booker.id\" must be ${user2.id}').to.eql(user2.id);", + "});", + "pm.test(\"Test booking 'item.id' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('item');", + " pm.expect(jsonData.item).to.have.property('id');", + " pm.expect(jsonData.item.id, '\"item.id\" must be ${item.id}').to.eql(item.id);", + "});", + "pm.test(\"Test booking 'item.name' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('item');", + " pm.expect(jsonData.item).to.have.property('name');", + " pm.expect(jsonData.item.name, '\"item.name\" must be ${item.name}').to.eql(item.name);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " var moment = require('moment');\r", + " const currentDate = moment();\r", + " var start = currentDate.clone().add(1, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " var end = currentDate.clone().add(2, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " pm.collectionVariables.set('start', start);\r", + " pm.collectionVariables.set('end', end);\r", + " \r", + " user1 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user1\", user1);\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user2\", user2);\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + " it = rnd.getItem()\r", + " it.available = true\r", + " item = await api.addItem(it, user1.id)\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + " pm.collectionVariables.set(\"item\", item);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"itemId\": {{itemId}},\n \"start\": \"{{start}}\",\n \"end\": \"{{end}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/bookings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "bookings" + ] + } + }, + "response": [] + }, + { + "name": "Booking approve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([200]);", + "});", + "pm.test(\"Has booking patch response\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + "});", + "var item = pm.collectionVariables.get('item')", + "var book = pm.collectionVariables.get('booking')", + "var user1 = pm.collectionVariables.get('user1')", + "var user2 = pm.collectionVariables.get('user2')", + "pm.test(\"Test booking 'id' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('id');", + " pm.expect(jsonData.id, '\"id\" must be ${book.id}').to.eql(book.id);", + "});", + "pm.test(\"Test booking 'start' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('start');", + " pm.expect(jsonData.start, '\"start\" must be \"' + pm.collectionVariables.get('start') + '\"').to.eql(pm.collectionVariables.get('start'));", + "});", + "pm.test(\"Test booking 'end' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('end');", + " pm.expect(jsonData.end, '\"end\" must be \"' + pm.collectionVariables.get('end') + '\"').to.eql(pm.collectionVariables.get('end'));", + "});", + "pm.test(\"Test booking 'status' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('status');", + " pm.expect(jsonData.status, '\"status\" must be \"APPROVED\"').to.eql('APPROVED');", + "});", + "pm.test(\"Test booking 'booker.id' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('booker');", + " pm.expect(jsonData.booker).to.have.property('id');", + " pm.expect(jsonData.booker.id, '\"booker.id\" must be ${user2.id}').to.eql(user2.id);", + "});", + "pm.test(\"Test booking 'item.id' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('item');", + " pm.expect(jsonData.item).to.have.property('id');", + " pm.expect(jsonData.item.id, '\"item.id\" must be ${item.id}').to.eql(item.id);", + "});", + "pm.test(\"Test booking 'item.name' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('item');", + " pm.expect(jsonData.item).to.have.property('name');", + " pm.expect(jsonData.item.name, '\"item.name\" must be ${item.name}').to.eql(item.name);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " var moment = require('moment');\r", + " const currentDate = moment();\r", + " var start = currentDate.clone().add(1, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " var end = currentDate.clone().add(2, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " pm.collectionVariables.set('start', start);\r", + " pm.collectionVariables.set('end', end);\r", + " \r", + " user1 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user1\", user1);\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user2\", user2);\r", + " pm.collectionVariables.set(\"userId\", user1.id);\r", + " it = rnd.getItem()\r", + " it.available = true\r", + " item = await api.addItem(it, user1.id)\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + " pm.collectionVariables.set(\"item\", item);\r", + " book = await api.addBooking(rnd.getBooking(item.id, start, end), user2.id)\r", + " pm.collectionVariables.set(\"bookingId\", book.id);\r", + " pm.collectionVariables.set(\"booking\", book);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/bookings/{{bookingId}}?approved=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "bookings", + "{{bookingId}}" + ], + "query": [ + { + "key": "approved", + "value": "true" + } + ] + } + }, + "response": [] + }, + { + "name": "Booking approve by wrong user", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 403\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([400, 403, 500]);", + "});", + "pm.test(\"Has booking patch response\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " var moment = require('moment');\r", + " const currentDate = moment();\r", + " var start = currentDate.clone().add(1, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " var end = currentDate.clone().add(2, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " pm.collectionVariables.set('start', start);\r", + " pm.collectionVariables.set('end', end);\r", + " \r", + " user1 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user1\", user1);\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user2\", user2);\r", + " pm.collectionVariables.set(\"userId\", user1.id + 2);\r", + " it = rnd.getItem()\r", + " it.available = true\r", + " item = await api.addItem(it, user1.id)\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + " pm.collectionVariables.set(\"item\", item);\r", + " book = await api.addBooking(rnd.getBooking(item.id, start, end), user2.id)\r", + " pm.collectionVariables.set(\"bookingId\", book.id);\r", + " pm.collectionVariables.set(\"booking\", book);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/bookings/{{bookingId}}?approved=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "bookings", + "{{bookingId}}" + ], + "query": [ + { + "key": "approved", + "value": "true" + } + ] + } + }, + "response": [] + }, + { + "name": "Get booking by booker", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.be.ok;", + "});", + "pm.test(\"Has booking create response\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + "});", + "var item = pm.collectionVariables.get('item')", + "var user1 = pm.collectionVariables.get('user1')", + "var user2 = pm.collectionVariables.get('user2')", + "var start = pm.collectionVariables.get('start')", + "var end = pm.collectionVariables.get('end')", + "pm.test(\"Test booking 'start' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('start');", + " pm.expect(jsonData.start, '\"start\" must be \"' + pm.collectionVariables.get('start') + '\"').to.eql(pm.collectionVariables.get('start'));", + "});", + "pm.test(\"Test booking 'end' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('end');", + " pm.expect(jsonData.end, '\"end\" must be \"' + pm.collectionVariables.get('end') + '\"').to.eql(pm.collectionVariables.get('end'));", + "});", + "pm.test(\"Test booking 'status' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('status');", + " pm.expect(jsonData.status, '\"status\" must be \"WAITING\"').to.eql('WAITING');", + "});", + "pm.test(\"Test booking 'booker.id' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('booker');", + " pm.expect(jsonData.booker).to.have.property('id');", + " pm.expect(jsonData.booker.id, '\"booker.id\" must be ${user2.id}').to.eql(user2.id);", + "});", + "pm.test(\"Test booking 'item.id' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('item');", + " pm.expect(jsonData.item).to.have.property('id');", + " pm.expect(jsonData.item.id, '\"item.id\" must be ${item.id}').to.eql(item.id);", + "});", + "pm.test(\"Test booking 'item.name' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('item');", + " pm.expect(jsonData.item).to.have.property('name');", + " pm.expect(jsonData.item.name, '\"item.name\" must be ${item.name}').to.eql(item.name);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " var moment = require('moment');\r", + " const currentDate = moment();\r", + " var start = currentDate.clone().add(1, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " var end = currentDate.clone().add(2, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " pm.collectionVariables.set('start', start);\r", + " pm.collectionVariables.set('end', end);\r", + " \r", + " user1 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user1\", user1);\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user2\", user2);\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + " it = rnd.getItem()\r", + " it.available = true\r", + " item = await api.addItem(it, user1.id)\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + " pm.collectionVariables.set(\"item\", item);\r", + " book = await api.addBooking(rnd.getBooking(item.id, start, end), user2.id)\r", + " pm.collectionVariables.set(\"bookingId\", book.id);\r", + " pm.collectionVariables.set(\"booking\", book);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/bookings/{{bookingId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "bookings", + "{{bookingId}}" + ] + } + }, + "response": [] + }, + { + "name": "Get booking by owner", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.be.ok;", + "});", + "pm.test(\"Has booking create response\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + "});", + "var item = pm.collectionVariables.get('item')", + "var user1 = pm.collectionVariables.get('user1')", + "var user2 = pm.collectionVariables.get('user2')", + "var start = pm.collectionVariables.get('start')", + "var end = pm.collectionVariables.get('end')", + "pm.test(\"Test booking 'start' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('start');", + " pm.expect(jsonData.start, '\"start\" must be \"' + pm.collectionVariables.get('start') + '\"').to.eql(pm.collectionVariables.get('start'));", + "});", + "pm.test(\"Test booking 'end' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('end');", + " pm.expect(jsonData.end, '\"end\" must be \"' + pm.collectionVariables.get('end') + '\"').to.eql(pm.collectionVariables.get('end'));", + "});", + "pm.test(\"Test booking 'status' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('status');", + " pm.expect(jsonData.status, '\"status\" must be \"WAITING\"').to.eql('WAITING');", + "});", + "pm.test(\"Test booking 'booker.id' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('booker');", + " pm.expect(jsonData.booker).to.have.property('id');", + " pm.expect(jsonData.booker.id, '\"booker.id\" must be ${user2.id}').to.eql(user2.id);", + "});", + "pm.test(\"Test booking 'item.id' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('item');", + " pm.expect(jsonData.item).to.have.property('id');", + " pm.expect(jsonData.item.id, '\"item.id\" must be ${item.id}').to.eql(item.id);", + "});", + "pm.test(\"Test booking 'item.name' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('item');", + " pm.expect(jsonData.item).to.have.property('name');", + " pm.expect(jsonData.item.name, '\"item.name\" must be ${item.name}').to.eql(item.name);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " var moment = require('moment');\r", + " const currentDate = moment();\r", + " var start = currentDate.clone().add(1, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " var end = currentDate.clone().add(2, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " pm.collectionVariables.set('start', start);\r", + " pm.collectionVariables.set('end', end);\r", + " \r", + " user1 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user1\", user1);\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user2\", user2);\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + " it = rnd.getItem()\r", + " it.available = true\r", + " item = await api.addItem(it, user1.id)\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + " pm.collectionVariables.set(\"item\", item);\r", + " book = await api.addBooking(rnd.getBooking(item.id, start, end), user2.id)\r", + " pm.collectionVariables.set(\"bookingId\", book.id);\r", + " pm.collectionVariables.set(\"booking\", book);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/bookings/{{bookingId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "bookings", + "{{bookingId}}" + ] + } + }, + "response": [] + }, + { + "name": "Get all bookings from wrong user", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 500\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([500,404,403]);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"userId\", user2.id + 1);\r", + "\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/bookings/owner", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "bookings", + "owner" + ] + } + }, + "response": [] + }, + { + "name": "Get all user bookings", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.be.ok;", + "});", + "pm.test(\"Has booking patch response\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + "});", + "var item = pm.collectionVariables.get('item')", + "var book = pm.collectionVariables.get('booking')", + "var user1 = pm.collectionVariables.get('user1')", + "var user2 = pm.collectionVariables.get('user2')", + "var jsonData = pm.response.json()[0];", + "", + "pm.test(\"Test booking 'id' field\", function () {", + " pm.expect(jsonData).to.have.property('id');", + " pm.expect(jsonData.id, '\"id\" must be ${book.id}').to.eql(book.id);", + "});", + "pm.test(\"Test booking 'start' field\", function () {", + " pm.expect(jsonData).to.have.property('start');", + " pm.expect(jsonData.start, '\"start\" must be \"' + pm.collectionVariables.get('start') + '\"').to.eql(pm.collectionVariables.get('start'));", + "});", + "pm.test(\"Test booking 'end' field\", function () {", + " pm.expect(jsonData).to.have.property('end');", + " pm.expect(jsonData.end, '\"end\" must be \"' + pm.collectionVariables.get('end') + '\"').to.eql(pm.collectionVariables.get('end'));", + "});", + "pm.test(\"Test booking 'status' field\", function () {", + " pm.expect(jsonData).to.have.property('status');", + " pm.expect(jsonData.status, '\"status\" must be \"WAITING\"').to.eql('WAITING');", + "});", + "pm.test(\"Test booking 'booker.id' field\", function () {", + " pm.expect(jsonData).to.have.property('booker');", + " pm.expect(jsonData.booker).to.have.property('id');", + " pm.expect(jsonData.booker.id, '\"booker.id\" must be ${user2.id}').to.eql(user2.id);", + "});", + "pm.test(\"Test booking 'item.id' field\", function () {", + " pm.expect(jsonData).to.have.property('item');", + " pm.expect(jsonData.item).to.have.property('id');", + " pm.expect(jsonData.item.id, '\"item.id\" must be ${item.id}').to.eql(item.id);", + "});", + "pm.test(\"Test booking 'item.name' field\", function () {", + " pm.expect(jsonData).to.have.property('item');", + " pm.expect(jsonData.item).to.have.property('name');", + " pm.expect(jsonData.item.name, '\"item.name\" must be ${item.name}').to.eql(item.name);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " var moment = require('moment');\r", + " const currentDate = moment();\r", + " var start = currentDate.clone().add(1, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " var end = currentDate.clone().add(2, 'days').format('YYYY-MM-DDTHH:mm:ss');\r", + " pm.collectionVariables.set('start', start);\r", + " pm.collectionVariables.set('end', end);\r", + " \r", + " user1 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user1\", user1);\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user2\", user2);\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + " it = rnd.getItem()\r", + " it.available = true\r", + " item = await api.addItem(it, user1.id)\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + " pm.collectionVariables.set(\"item\", item);\r", + " book = await api.addBooking(rnd.getBooking(item.id, start, end), user2.id)\r", + " pm.collectionVariables.set(\"bookingId\", book.id);\r", + " pm.collectionVariables.set(\"booking\", book);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/bookings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "bookings" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "comments", + "item": [ + { + "name": "Comment past booking", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200 or 201\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([200,201]);", + "});", + "pm.test(\"Response have body\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + "});", + "var item = pm.collectionVariables.get(\"item\");", + "var user1 = pm.collectionVariables.get(\"user1\");", + "var user2 = pm.collectionVariables.get(\"user2\");", + "var text = pm.collectionVariables.get(\"commentText\")", + "pm.test(\"Response data equal to request\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('id');", + " pm.expect(jsonData).to.have.property('text');", + " pm.expect(jsonData).to.have.property('authorName');", + " pm.expect(jsonData).to.have.property('created');", + " pm.expect(jsonData.text, `\"text\" must be ` + text).to.eql(text);", + " pm.expect(jsonData.authorName, `\"authorName\" must be ${user2.name}`).to.eql(user2.name);", + "});" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " var moment = require('moment');\r", + " const currentDate = moment();\r", + " var start = currentDate.clone().add(1, 'seconds').format('YYYY-MM-DDTHH:mm:ss');\r", + " var end = currentDate.clone().add(2, 'seconds').format('YYYY-MM-DDTHH:mm:ss');\r", + " pm.collectionVariables.set('start', start);\r", + " pm.collectionVariables.set('end', end);\r", + " \r", + " user1 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user1\", user1);\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user2\", user2);\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + " it = rnd.getItem()\r", + " it.available = true\r", + " item = await api.addItem(it, user1.id)\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + " pm.collectionVariables.set(\"item\", item);\r", + " book = await api.addBooking(rnd.getBooking(item.id, start, end), user2.id)\r", + " await api.approveBooking(book.id, user1.id)\r", + " pm.collectionVariables.set(\"bookingId\", book.id);\r", + " pm.collectionVariables.set(\"booking\", book);\r", + " pm.collectionVariables.set(\"commentText\", rnd.getWord(50));\r", + " setTimeout(function(){}, 3000);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + }, + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"text\": \"{{commentText}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/items/{{itemId}}/comment", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items", + "{{itemId}}", + "comment" + ] + } + }, + "response": [] + }, + { + "name": "Comment approved booking", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 400\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([400, 500])", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " var moment = require('moment');\r", + " const currentDate = moment();\r", + " var start = currentDate.clone().add(1, 'seconds').format('YYYY-MM-DDTHH:mm:ss');\r", + " var end = currentDate.clone().add(2, 'seconds').format('YYYY-MM-DDTHH:mm:ss');\r", + " pm.collectionVariables.set('start', start);\r", + " pm.collectionVariables.set('end', end);\r", + " \r", + " user1 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user1\", user1);\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user2\", user2);\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + " it = rnd.getItem()\r", + " it.available = true\r", + " item = await api.addItem(it, user1.id)\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + " pm.collectionVariables.set(\"item\", item);\r", + " book = await api.addBooking(rnd.getBooking(item.id, start, end), user2.id)\r", + " await api.approveBooking(book.id, user1.id)\r", + " pm.collectionVariables.set(\"bookingId\", book.id);\r", + " pm.collectionVariables.set(\"booking\", book);\r", + " pm.collectionVariables.set(\"commentText\", rnd.getWord(50));\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\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", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + }, + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"text\": \"{{commentText}}\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/items/{{itemId}}/comment", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items", + "{{itemId}}", + "comment" + ] + } + }, + "response": [] + }, + { + "name": "Get item with comments", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.be.ok;", + "});", + "pm.test(\"Has item create response\", function () {", + " pm.response.to.be.withBody;", + " pm.response.to.be.json;", + "});", + "pm.test(\"Test item 'id' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('id');", + "});", + "pm.test(\"Test item 'name' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('name');", + "});", + "pm.test(\"Test item 'description' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('description');", + "});", + "pm.test(\"Test item 'available' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('available');", + "});", + "pm.test(\"Test item 'lastBooking' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('lastBooking');", + " pm.expect(jsonData.lastBooking, '\"lastBooking\" must be \"null\"').null;", + "});", + "pm.test(\"Test item 'nextBooking' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('nextBooking');", + " pm.expect(jsonData.nextBooking, '\"nextBooking\" must be \"null\"').null;", + "});", + "pm.test(\"Test item 'comments' field\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData).to.have.property('comments');", + " pm.expect(jsonData.comments.length, 'length of \"comments\" must be \"1\"').to.eql(1);", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " var moment = require('moment');\r", + " const currentDate = moment();\r", + " var start = currentDate.clone().add(1, 'seconds').format('YYYY-MM-DDTHH:mm:ss');\r", + " var end = currentDate.clone().add(2, 'seconds').format('YYYY-MM-DDTHH:mm:ss');\r", + " pm.collectionVariables.set('start', start);\r", + " pm.collectionVariables.set('end', end);\r", + " \r", + " user1 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user1\", user1);\r", + " user2 = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"user2\", user2);\r", + " pm.collectionVariables.set(\"userId\", user2.id);\r", + " it = rnd.getItem()\r", + " it.available = true\r", + " item = await api.addItem(it, user1.id)\r", + " pm.collectionVariables.set(\"itemId\", item.id);\r", + " pm.collectionVariables.set(\"item\", item);\r", + " book = await api.addBooking(rnd.getBooking(item.id, start, end), user2.id)\r", + " await api.approveBooking(book.id, user1.id)\r", + " pm.collectionVariables.set(\"bookingId\", book.id);\r", + " pm.collectionVariables.set(\"booking\", book);\r", + " pm.collectionVariables.set(\"commentText\", rnd.getWord(50));\r", + " pausecomp(3000);\r", + " await api.getBooking(book.id, user1.id)\r", + " comment = await api.addComment({\"text\": rnd.getWord(50)}, item.id, user2.id)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "function pausecomp(millis)\r", + " {\r", + " var date = new Date();\r", + " var curDate = null;\r", + " do { curDate = new Date(); }\r", + " while(curDate-date < millis);\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", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "X-Sharer-User-Id", + "value": "{{userId}}", + "type": "text" + }, + { + "key": "Accept", + "value": "*/*", + "type": "text" + } + ], + "url": { + "raw": "{{baseUrl}}/items/{{itemId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "items", + "{{itemId}}" + ] + } + }, + "response": [] + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "API = class {\r", + " constructor(postman, verbose = false, baseUrl = \"http://localhost:8080\") {\r", + " this.baseUrl = baseUrl;\r", + " this.pm = postman;\r", + " this._verbose = verbose;\r", + " }\r", + "\r", + " async addUser(user, id=0, verbose=null) {\r", + " return this.post(\"/users\", user, id, \"Ошибка при добавлении нового пользователя: \", verbose);\r", + " }\r", + "\r", + " async addItem(item, id=0, verbose=null) {\r", + " return this.post(\"/items\", item, id, \"Ошибка при добавлении новой вещи: \", verbose);\r", + " }\r", + "\r", + " async addBooking(booking, id=0, verbose=null) {\r", + " return this.post(\"/bookings\", booking, id, \"Ошибка при добавлении нового бронирования: \", verbose);\r", + " }\r", + "\r", + " async addComment(comment, itemId, id=0, verbose=null) {\r", + " return this.post(\"/items/\" + itemId + \"/comment\", comment, id, \"Ошибка при добавлении нового комментария: \", verbose);\r", + " }\r", + "\r", + " async getBooking(bookingId, id=0, verbose=null) {\r", + " return this.get(\"/bookings/\"+bookingId, {}, id, \"Ошибка при получении информации о бронировании: \", verbose);\r", + " }\r", + "\r", + " async approveBooking(bookingId, id=0, verbose=null) {\r", + " return this.patch(\"/bookings/\"+bookingId+\"?approved=true\", {}, id, \"Ошибка при подтверждении бронирования: \", verbose);\r", + " }\r", + "\r", + " async addRequest(request, id=0, verbose=null) {\r", + " return this.post(\"/requests\", request, id, \"Ошибка при добавлении нового запроса: \", verbose);\r", + " }\r", + " \r", + " async post(path, body, id=0, errorText = \"Ошибка при выполнении post-запроса: \", verbose=null) {\r", + " return this.sendRequest(\"POST\", path, body, id, errorText, verbose);\r", + " }\r", + "\r", + " async patch(path, body = null, id=0, errorText = \"Ошибка при выполнении patch-запроса: \", verbose=null) {\r", + " return this.sendRequest(\"PATCH\", path, body, id, errorText, verbose);\r", + " }\r", + "\r", + " async get(path, body = null, id=0, errorText = \"Ошибка при выполнении get-запроса: \", verbose=null) {\r", + " return this.sendRequest(\"GET\", path, body, id, errorText, verbose);\r", + " }\r", + "\r", + " async put(path, body = null, id=0, errorText = \"Ошибка при выполнении put-запроса: \", verbose=null) {\r", + " return this.sendRequest(\"PUT\", path, body, id, errorText, verbose);\r", + " }\r", + "\r", + " async delete(path, body = null, id=0, errorText = \"Ошибка при выполнении delte-запроса: \", verbose=null) {\r", + " return this.sendRequest(\"DELETE\", path, body, id, errorText, verbose);\r", + " }\r", + "\r", + " async sendRequest(method, path, body=null, id=0, errorText = \"Ошибка при выполнении запроса: \", verbose=null) {\r", + " return new Promise((resolve, reject) => {\r", + " verbose = verbose == null ? this._verbose : verbose;\r", + " var req = {};\r", + " if (id == 0){\r", + " req = {\r", + " url: this.baseUrl + path,\r", + " method: method,\r", + " body: body == null ? \"\" : JSON.stringify(body),\r", + " header: { \"Content-Type\": \"application/json\"},\r", + " };\r", + " }else{\r", + " req = {\r", + " url: this.baseUrl + path,\r", + " method: method,\r", + " body: body == null ? \"\" : JSON.stringify(body),\r", + " header: [{\r", + " \"key\": \"X-Sharer-User-Id\",\r", + " \"value\": id,\r", + " \"type\": \"text\",\r", + " },\r", + " {\r", + " \"key\": \"Content-Type\",\r", + " \"name\": \"Content-Type\",\r", + " \"value\": \"application/json\",\r", + " \"type\": \"text\"\r", + " }]\r", + " };\r", + " }\r", + " if(verbose) {\r", + " console.log(\"Отправляю запрос: \", req);\r", + " }\r", + "\r", + " try {\r", + " this.pm.sendRequest(req, (error, response) => {\r", + " if(error || (response.code >= 400 && response.code <= 599)) {\r", + " let err = error ? error : JSON.stringify(response.json());\r", + " console.error(\"При выполнении запроса к серверу возникла ошибка.\\n\", err,\r", + " \"\\nДля отладки проблемы повторите такой же запрос к вашей программе \" + \r", + " \"на локальном компьютере. Данные запроса:\\n\", JSON.stringify(request));\r", + "\r", + " reject(new Error(errorText + err));\r", + " }\r", + " if(verbose) {\r", + " console.log(\"Результат обработки запроса: код состояния - \", response.code, \", тело: \", response.json());\r", + " }\r", + " if (response.stream.length === 0){\r", + " resolve(null);\r", + " }else{\r", + " resolve(response.json());\r", + " }\r", + " });\r", + " \r", + " } catch(err) {\r", + " if(verbose) {\r", + " console.error(errorText, err);\r", + " }\r", + " return Promise.reject(err);\r", + " }\r", + " });\r", + " }\r", + "};\r", + "\r", + "RandomUtils = class {\r", + " constructor() {}\r", + "\r", + " getUser() {\r", + " return {\r", + " name: pm.variables.replaceIn('{{$randomFullName}}'),\r", + " email: pm.variables.replaceIn('{{$randomEmail}}'),\r", + " };\r", + " }\r", + "\r", + " getRequest() {\r", + " return {\r", + " description: this.getWord(50)\r", + " };\r", + " }\r", + "\r", + " getBooking(id, startBook, endBook) {\r", + " return {\r", + " itemId: id, \r", + " start: startBook,\r", + " end: endBook \r", + " };\r", + " }\r", + "\r", + " getItem() {\r", + " return {\r", + " name: this.getWord(10),\r", + " description: this.getWord(50),\r", + " available: pm.variables.replaceIn('{{$randomBoolean}}')\t\r", + " };\r", + " }\r", + "\r", + " getItemForRequest(id) {\r", + " return {\r", + " name: this.getWord(10),\r", + " description: this.getWord(50),\r", + " available: pm.variables.replaceIn('{{$randomBoolean}}'),\r", + " requestId: id\r", + " };\r", + " }\r", + "\r", + " getFilm(director=null) {\r", + " let date = new Date(new Date(1960, 0, 1).getTime() + Math.random() * (new Date(2010, 0, 1).getTime() - new Date(1960, 0, 1).getTime()));\r", + " var toReturn = {\r", + " name: this.getWord(15),\r", + " description: this.getWord(50),\r", + " releaseDate: date.toISOString().slice(0,10),\r", + " duration: Math.floor(Math.random() * (180 - 60 + 1) + 60),\r", + " mpa: { id: Math.floor(Math.random() * (5 - 1 + 1) + 1)},\r", + " genres: [{ id: Math.floor(Math.random() * (6 - 1 + 1) + 1)}]\r", + " };\r", + " if (director!==null)\r", + " toReturn.directors = [{ id: director.id}];\r", + " return toReturn;\r", + " }\r", + "\r", + "\r", + " getWord(length = 1) {\r", + " let result = '';\r", + " const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\r", + " const charactersLength = characters.length;\r", + " let counter = 0;\r", + " while (counter < length) {\r", + " result += characters.charAt(Math.floor(Math.random() * charactersLength));\r", + " counter += 1;\r", + " }\r", + " return result;\r", + " }\r", + "\r", + " getName(length = 1) {\r", + " let result = '';\r", + " const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\r", + " const charactersLength = characters.length;\r", + " let counter = 0;\r", + " while (counter < length) {\r", + " result += characters.charAt(Math.floor(Math.random() * charactersLength));\r", + " counter += 1;\r", + " }\r", + " return result;\r", + " }\r", + "\r", + "}" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "userName", + "value": "" + }, + { + "key": "userEmail", + "value": "" + }, + { + "key": "userId", + "value": "1" + }, + { + "key": "itemName", + "value": "" + }, + { + "key": "itemAvailable", + "value": "" + }, + { + "key": "itemDescription", + "value": "" + }, + { + "key": "item", + "value": "" + }, + { + "key": "requestId", + "value": "" + }, + { + "key": "requestDescription", + "value": "" + }, + { + "key": "baseUrl", + "value": "localhost:8080" + }, + { + "key": "start", + "value": "" + }, + { + "key": "end", + "value": "" + }, + { + "key": "itemId", + "value": "" + }, + { + "key": "user1", + "value": "" + }, + { + "key": "user2", + "value": "" + }, + { + "key": "bookingId", + "value": "1" + }, + { + "key": "booking", + "value": "" + }, + { + "key": "commentText", + "value": "" + } + ] +} diff --git a/server/pom.xml b/server/pom.xml new file mode 100644 index 0000000..cd6cc65 --- /dev/null +++ b/server/pom.xml @@ -0,0 +1,96 @@ + + + 4.0.0 + + ru.practicum + shareit + 0.0.1-SNAPSHOT + + + shareit-server + 0.0.1-SNAPSHOT + + ShareIt Server + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.postgresql + postgresql + runtime + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + coverage + + + + org.jacoco + jacoco-maven-plugin + + + ru/practicum/shareit/ShareItApp.class + ru/practicum/shareit/**/dto/*.* + ru/practicum/shareit/**/*Mapper.class + ru/practicum/shareit/exceptions/*.class + ru/practicum/shareit/**/storage/*.class + ru/practicum/shareit/tool/*.class + + + + + + + + + diff --git a/src/main/java/ru/practicum/shareit/ShareItApp.java b/server/src/main/java/ru/practicum/shareit/ShareItApp.java similarity index 100% rename from src/main/java/ru/practicum/shareit/ShareItApp.java rename to server/src/main/java/ru/practicum/shareit/ShareItApp.java diff --git a/src/main/java/ru/practicum/shareit/booking/BookingController.java b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java similarity index 70% rename from src/main/java/ru/practicum/shareit/booking/BookingController.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingController.java index 0dfb613..763feb6 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -1,6 +1,5 @@ package ru.practicum.shareit.booking; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import ru.practicum.shareit.booking.dto.BookingDto; @@ -14,33 +13,33 @@ public class BookingController { private final BookingService bookingService; @GetMapping("/{id}") - public BookingDto getItem( + public BookingDto getBooking( @PathVariable long id, @RequestHeader("X-Sharer-User-Id") long userId) { - return bookingService.getBooking(id, userId); + return bookingService.findBooking(id, userId); } @GetMapping - public List getItemsForUserId( + public List getBookingForUserId( @RequestHeader("X-Sharer-User-Id") long userId) { - return bookingService.getItemsForUserId(userId); + return bookingService.findBookingsForUserId(userId); } @GetMapping("/owner") - public List getItemsForItemOwnerId( + public List getBookingForItemOwnerId( @RequestHeader("X-Sharer-User-Id") long ownerId) { - return bookingService.getItemsForItemOwnerId(ownerId); + return bookingService.findBookingsForItemOwnerId(ownerId); } @PostMapping - public BookingDto postItem( + public BookingDto postBooking( @RequestHeader("X-Sharer-User-Id") long userId, - @Valid @RequestBody BookingDto bookingDto) { + @RequestBody BookingDto bookingDto) { return bookingService.createBooking(bookingDto, userId); } @PatchMapping("/{id}") - public BookingDto patchItem( + public BookingDto patchBooking( @PathVariable long id, @RequestParam(name = "approved") boolean approved, @RequestHeader("X-Sharer-User-Id") long userId) { diff --git a/src/main/java/ru/practicum/shareit/booking/BookingMapper.java b/server/src/main/java/ru/practicum/shareit/booking/BookingMapper.java similarity index 80% rename from src/main/java/ru/practicum/shareit/booking/BookingMapper.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingMapper.java index 7d59f31..b645522 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingMapper.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingMapper.java @@ -12,9 +12,9 @@ import java.text.SimpleDateFormat; public class BookingMapper { - private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - public BookingDto toDto(BookingEntity entity, UserDto userDto, ItemDto itemDto) { + public static BookingDto toDto(BookingEntity entity, UserDto userDto, ItemDto itemDto) { return BookingDto .builder() .id(entity.getId()) @@ -27,7 +27,7 @@ public BookingDto toDto(BookingEntity entity, UserDto userDto, ItemDto itemDto) .build(); } - public BookingEntity toEntity(BookingDto dto, UserEntity user, ItemEntity item) throws ParseException { + public static BookingEntity toEntity(BookingDto dto, UserEntity user, ItemEntity item) throws ParseException { var entity = new BookingEntity(); entity.setId(dto.id()); entity.setBooker(user); diff --git a/server/src/main/java/ru/practicum/shareit/booking/BookingService.java b/server/src/main/java/ru/practicum/shareit/booking/BookingService.java new file mode 100644 index 0000000..ddbb996 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingService.java @@ -0,0 +1,110 @@ +package ru.practicum.shareit.booking; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.storage.BookingEntity; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.exceptions.NotValidException; +import ru.practicum.shareit.item.ItemMapper; +import ru.practicum.shareit.item.ItemStorage; +import ru.practicum.shareit.user.UserMapper; +import ru.practicum.shareit.user.UserStorage; + +import java.text.ParseException; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class BookingService { + private final ItemStorage itemStorage; + private final UserStorage userStorage; + private final BookingStorage bookingStorage; + + public BookingDto findBooking(long bookingId, long userId) { + var userEntity = userStorage.findUserId(userId) + .orElseThrow(() -> new NotFoundException("Пользователь не найден")); + + var bookingEntity = bookingStorage.findBooking(bookingId) + .orElseThrow(() -> new NotFoundException("Бронь не найдена")); + + if (bookingEntity.getBooker().getId() != userId && + bookingEntity.getItem().getOwner().getId() != userId) + throw new NotValidException("Данные о бронировании может получить заказчик или владелец"); + + return bookingEntity.toDto(); + } + + public List findBookingsForUserId(long userId) { + if (userStorage.findUserId(userId).isEmpty()) + throw new NotFoundException("Пользователь не найден"); + + var entities = bookingStorage.findBookingsByBookerId(userId); + + return entities + .stream() + .map(BookingEntity::toDto) + .toList(); + } + + public List findBookingsForItemOwnerId(long userId) { + if (userStorage.findUserId(userId).isEmpty()) + throw new NotFoundException("Пользователь не найден"); + + var entities = bookingStorage.findBookingsByOwnerId(userId); + + return entities + .stream() + .map(BookingEntity::toDto) + .toList(); + } + + public BookingDto createBooking(BookingDto bookingDto, long userId) { + var userEntity = userStorage.findUserId(userId) + .orElseThrow(() -> new NotFoundException("Пользователь не найден")); + var itemEntity = itemStorage.findItem(bookingDto.itemId()) + .orElseThrow(() -> new NotFoundException("Вещь не найдена")); + + if (!itemEntity.isAvailable()) { + throw new NotValidException("Вещь не доступна к бронированию"); + } + + try { + var bookingEntity = BookingMapper.toEntity(bookingDto, userEntity, itemEntity); + bookingEntity.setStatus(BookingStatus.WAITING); + + bookingStorage.updateBooking(bookingEntity); + + return bookingEntity.toDto(); + } catch (ParseException e) { + throw new NotValidException("Произошла ошибка чтения данных"); + } + } + + public BookingDto setBookingStatus(long id, boolean approved, long userId) { + if (!userStorage.existsById(userId)) { + throw new NotValidException("Пользователь не найден"); + } + + var bookingEntity = bookingStorage.findBooking(id) + .orElseThrow(() -> new NotFoundException("Бронь не найдена")); + + var itemEntity = bookingEntity.getItem(); + + if (itemEntity.getOwner().getId() != userId) { + throw new NotValidException("Подтвердить бронь может только владелец"); + } + + if (approved) + bookingEntity.setStatus(BookingStatus.APPROVED); + else + bookingEntity.setStatus(BookingStatus.REJECTED); + + bookingStorage.updateBooking(bookingEntity); + + return BookingMapper.toDto( + bookingEntity, + UserMapper.toDto(bookingEntity.getBooker()), + ItemMapper.toDto(itemEntity)); + } +} diff --git a/src/main/java/ru/practicum/shareit/booking/BookingStatus.java b/server/src/main/java/ru/practicum/shareit/booking/BookingStatus.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/BookingStatus.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingStatus.java diff --git a/src/main/java/ru/practicum/shareit/booking/BookingStorage.java b/server/src/main/java/ru/practicum/shareit/booking/BookingStorage.java similarity index 81% rename from src/main/java/ru/practicum/shareit/booking/BookingStorage.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingStorage.java index 621fd56..5c5ad44 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingStorage.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingStorage.java @@ -6,9 +6,7 @@ import java.util.Optional; public interface BookingStorage { - Optional getBooking(long bookingId); - - boolean userIdIsBookerOrOwner(BookingEntity bookingEntity, long userId); + Optional findBooking(long bookingId); void updateBooking(BookingEntity entity); diff --git a/src/main/java/ru/practicum/shareit/booking/Review.java b/server/src/main/java/ru/practicum/shareit/booking/Review.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/Review.java rename to server/src/main/java/ru/practicum/shareit/booking/Review.java diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java rename to server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java diff --git a/server/src/main/java/ru/practicum/shareit/booking/dto/BookingStatusRequestDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingStatusRequestDto.java new file mode 100644 index 0000000..2bd64ee --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingStatusRequestDto.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.booking.dto; + +public enum BookingStatusRequestDto { + ALL, + CURRENT, + PAST, + FUTURE, + WAITING, + REJECTED, + APPROVED +} diff --git a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/ReviewDto.java similarity index 50% rename from src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java rename to server/src/main/java/ru/practicum/shareit/booking/dto/ReviewDto.java index e8e0b13..a87829f 100644 --- a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java +++ b/server/src/main/java/ru/practicum/shareit/booking/dto/ReviewDto.java @@ -1,12 +1,13 @@ -package ru.practicum.shareit.request.dto; +package ru.practicum.shareit.booking.dto; import lombok.Builder; import lombok.Value; @Value @Builder(toBuilder = true) -public class ItemRequestDto { +public class ReviewDto { Long id; - Long userId; + Long bookingId; String text; + boolean isSuccess; } diff --git a/src/main/java/ru/practicum/shareit/booking/dto/ReviewMapper.java b/server/src/main/java/ru/practicum/shareit/booking/dto/ReviewMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dto/ReviewMapper.java rename to server/src/main/java/ru/practicum/shareit/booking/dto/ReviewMapper.java diff --git a/src/main/java/ru/practicum/shareit/booking/storage/BookingEntity.java b/server/src/main/java/ru/practicum/shareit/booking/storage/BookingEntity.java similarity index 75% rename from src/main/java/ru/practicum/shareit/booking/storage/BookingEntity.java rename to server/src/main/java/ru/practicum/shareit/booking/storage/BookingEntity.java index 97fc183..f454e7b 100644 --- a/src/main/java/ru/practicum/shareit/booking/storage/BookingEntity.java +++ b/server/src/main/java/ru/practicum/shareit/booking/storage/BookingEntity.java @@ -2,8 +2,12 @@ import jakarta.persistence.*; import lombok.Data; +import ru.practicum.shareit.booking.BookingMapper; import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.item.ItemMapper; import ru.practicum.shareit.item.storage.ItemEntity; +import ru.practicum.shareit.user.UserMapper; import ru.practicum.shareit.user.storage.UserEntity; import java.sql.Timestamp; @@ -42,4 +46,11 @@ public BookingEntity() { public String toString() { return String.format("id=%d,booker=%d,item=%d,status=%s,start=%s,end=%s", id, booker.getId(), item.getId(), status, start.toString(), end.toString()); } + + public BookingDto toDto() { + return BookingMapper.toDto( + this, + UserMapper.toDto(getBooker()), + ItemMapper.toDto(getItem())); + } } diff --git a/src/main/java/ru/practicum/shareit/booking/storage/BookingRepository.java b/server/src/main/java/ru/practicum/shareit/booking/storage/BookingRepository.java similarity index 96% rename from src/main/java/ru/practicum/shareit/booking/storage/BookingRepository.java rename to server/src/main/java/ru/practicum/shareit/booking/storage/BookingRepository.java index b20711d..094dc89 100644 --- a/src/main/java/ru/practicum/shareit/booking/storage/BookingRepository.java +++ b/server/src/main/java/ru/practicum/shareit/booking/storage/BookingRepository.java @@ -3,12 +3,14 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; import ru.practicum.shareit.booking.BookingStatus; import java.sql.Timestamp; import java.util.List; import java.util.Optional; +@Repository public interface BookingRepository extends JpaRepository { List findByBookerId(long userId); diff --git a/src/main/java/ru/practicum/shareit/booking/storage/BookingStorageInDb.java b/server/src/main/java/ru/practicum/shareit/booking/storage/BookingStorageInDb.java similarity index 82% rename from src/main/java/ru/practicum/shareit/booking/storage/BookingStorageInDb.java rename to server/src/main/java/ru/practicum/shareit/booking/storage/BookingStorageInDb.java index 8d70bbd..2612972 100644 --- a/src/main/java/ru/practicum/shareit/booking/storage/BookingStorageInDb.java +++ b/server/src/main/java/ru/practicum/shareit/booking/storage/BookingStorageInDb.java @@ -20,21 +20,10 @@ public class BookingStorageInDb implements BookingStorage { private final BookingRepository bookingRepository; @Override - public Optional getBooking(long bookingId) { + public Optional findBooking(long bookingId) { return bookingRepository.findById(bookingId); } - @Override - public boolean userIdIsBookerOrOwner(BookingEntity bookingEntity, long userId) { - if (bookingEntity.getBooker().getId() == userId) - return true; - - if (bookingEntity.getItem().getOwner().getId() == userId) - return true; - - return false; - } - @Override public void updateBooking(BookingEntity bookingEntity) { bookingRepository.saveAndFlush(bookingEntity); diff --git a/src/main/java/ru/practicum/shareit/exceptions/IdIsAlreadyInUseException.java b/server/src/main/java/ru/practicum/shareit/exceptions/IdIsAlreadyInUseException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exceptions/IdIsAlreadyInUseException.java rename to server/src/main/java/ru/practicum/shareit/exceptions/IdIsAlreadyInUseException.java diff --git a/src/main/java/ru/practicum/shareit/exceptions/NotFoundException.java b/server/src/main/java/ru/practicum/shareit/exceptions/NotFoundException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exceptions/NotFoundException.java rename to server/src/main/java/ru/practicum/shareit/exceptions/NotFoundException.java diff --git a/src/main/java/ru/practicum/shareit/exceptions/NotValidException.java b/server/src/main/java/ru/practicum/shareit/exceptions/NotValidException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exceptions/NotValidException.java rename to server/src/main/java/ru/practicum/shareit/exceptions/NotValidException.java diff --git a/src/main/java/ru/practicum/shareit/item/storage/CommentMapper.java b/server/src/main/java/ru/practicum/shareit/item/CommentMapper.java similarity index 69% rename from src/main/java/ru/practicum/shareit/item/storage/CommentMapper.java rename to server/src/main/java/ru/practicum/shareit/item/CommentMapper.java index 14ac1ba..ba5abe3 100644 --- a/src/main/java/ru/practicum/shareit/item/storage/CommentMapper.java +++ b/server/src/main/java/ru/practicum/shareit/item/CommentMapper.java @@ -1,10 +1,12 @@ -package ru.practicum.shareit.item.storage; +package ru.practicum.shareit.item; import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.storage.CommentEntity; +import ru.practicum.shareit.item.storage.ItemEntity; import ru.practicum.shareit.user.storage.UserEntity; public class CommentMapper { - public CommentDto toDto( + public static CommentDto toDto( CommentEntity entity) { var build = CommentDto .builder() @@ -16,7 +18,7 @@ public CommentDto toDto( return build.build(); } - public CommentEntity toEntity(CommentDto dto, UserEntity userEntity, ItemEntity itemEntity) { + public static CommentEntity toEntity(CommentDto dto, UserEntity userEntity, ItemEntity itemEntity) { var entity = new CommentEntity(); entity.setId(dto.id()); entity.setText(dto.text()); diff --git a/src/main/java/ru/practicum/shareit/item/ItemController.java b/server/src/main/java/ru/practicum/shareit/item/ItemController.java similarity index 80% rename from src/main/java/ru/practicum/shareit/item/ItemController.java rename to server/src/main/java/ru/practicum/shareit/item/ItemController.java index 6538f36..423cb16 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -1,6 +1,5 @@ package ru.practicum.shareit.item; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import ru.practicum.shareit.item.dto.CommentDto; @@ -23,7 +22,7 @@ public List getItems(@RequestHeader("X-Sharer-User-Id") long userId) { @GetMapping("/{id}") public ItemDto getItem(@PathVariable long id) { - return itemService.getItem(id); + return itemService.findItem(id); } @GetMapping("/search") @@ -33,7 +32,7 @@ public List findItemsByText(@RequestParam String text) { @PostMapping public ItemDto postItem( - @Valid @RequestBody ItemDto itemDto, + @RequestBody ItemDto itemDto, @RequestHeader("X-Sharer-User-Id") long userId) { return itemService.createItem(itemDto, userId); } @@ -42,13 +41,13 @@ public ItemDto postItem( public CommentDto postComment( @PathVariable long id, @RequestHeader("X-Sharer-User-Id") long userId, - @Valid @RequestBody CommentDto commentDto) { + @RequestBody CommentDto commentDto) { return itemService.addComment(commentDto, id, userId); } @PutMapping() public ItemDto putItem( - @Valid @RequestBody ItemDto itemDto, + @RequestBody ItemDto itemDto, @RequestHeader("X-Sharer-User-Id") long userId) { return itemService.updateItem(itemDto, userId); @@ -57,15 +56,13 @@ public ItemDto putItem( @PatchMapping("/{id}") public ItemDto patchItem( @PathVariable long id, - @Valid @RequestBody ItemToUpdateDto item, + @RequestBody ItemToUpdateDto item, @RequestHeader("X-Sharer-User-Id") long userId) { - itemService.updateItem(id, item, userId); - - return itemService.getItem(id); + return itemService.updateItem(id, item, userId); } @DeleteMapping("/{id}") - public ItemDto deleteItem(@RequestParam long id, + public ItemDto deleteItem(@PathVariable long id, @RequestHeader("X-Sharer-User-Id") long userId) { return itemService.deleteItem(id, userId); } diff --git a/src/main/java/ru/practicum/shareit/item/ItemMapper.java b/server/src/main/java/ru/practicum/shareit/item/ItemMapper.java similarity index 74% rename from src/main/java/ru/practicum/shareit/item/ItemMapper.java rename to server/src/main/java/ru/practicum/shareit/item/ItemMapper.java index 320794c..01ae09b 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemMapper.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemMapper.java @@ -5,6 +5,7 @@ import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.item.storage.ItemEntity; import ru.practicum.shareit.item.storage.ItemWithDateOfBookings; +import ru.practicum.shareit.request.storage.RequestEntity; import ru.practicum.shareit.user.storage.UserEntity; import java.sql.Timestamp; @@ -12,7 +13,7 @@ import java.util.Optional; public class ItemMapper { - public ItemDto toDto(ItemWithDateOfBookings entity) { + public static ItemDto toDto(ItemWithDateOfBookings entity) { return toDto( entity.getItemEntity(), Optional.ofNullable(entity.getLastDateBooking()), @@ -20,7 +21,7 @@ public ItemDto toDto(ItemWithDateOfBookings entity) { List.of()); } - public ItemDto toDto(ItemWithDateOfBookings entity, List comments) { + public static ItemDto toDto(ItemWithDateOfBookings entity, List comments) { return toDto( entity.getItemEntity(), Optional.ofNullable(entity.getLastDateBooking()), @@ -28,15 +29,15 @@ public ItemDto toDto(ItemWithDateOfBookings entity, List comments) { comments); } - public ItemDto toDto(ItemEntity itemEntity) { + public static ItemDto toDto(ItemEntity itemEntity) { return toDto(itemEntity, Optional.empty(),Optional.empty(), List.of()); } - public ItemDto toDto(ItemEntity itemEntity, List comments) { + public static ItemDto toDto(ItemEntity itemEntity, List comments) { return toDto(itemEntity, Optional.empty(),Optional.empty(), comments); } - public ItemDto toDto( + public static ItemDto toDto( ItemEntity itemEntity, @NotNull Optional lastBooking, @NotNull Optional nextBooking, @@ -49,19 +50,24 @@ public ItemDto toDto( .available(itemEntity.isAvailable()) .comments(comments); + var request = itemEntity.getRequest(); + if (request != null) + build.requestId(request.getId()); + lastBooking.ifPresent(build::lastBooking); nextBooking.ifPresent(build::nextBooking); return build.build(); } - public ItemEntity toEntity(ItemDto itemDto, UserEntity userEntity) { + public static ItemEntity toEntity(ItemDto itemDto, UserEntity userEntity, RequestEntity requestEntity) { var entity = new ItemEntity(); entity.setId(itemDto.id()); entity.setOwner(userEntity); entity.setName(itemDto.name()); entity.setDescription(itemDto.description()); entity.setAvailable(itemDto.available()); + entity.setRequest(requestEntity); return entity; } diff --git a/src/main/java/ru/practicum/shareit/item/ItemService.java b/server/src/main/java/ru/practicum/shareit/item/ItemService.java similarity index 66% rename from src/main/java/ru/practicum/shareit/item/ItemService.java rename to server/src/main/java/ru/practicum/shareit/item/ItemService.java index cc9957b..462d797 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemService.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemService.java @@ -9,7 +9,7 @@ import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.item.dto.ItemToUpdateDto; -import ru.practicum.shareit.item.storage.CommentMapper; +import ru.practicum.shareit.request.RequestStorage; import ru.practicum.shareit.user.UserStorage; import java.sql.Timestamp; @@ -23,51 +23,55 @@ public class ItemService { private final ItemStorage itemStorage; private final UserStorage userStorage; private final BookingStorage bookingStorage; - private final ItemMapper itemMapper = new ItemMapper(); - private final CommentMapper commentMapper = new CommentMapper(); + private final RequestStorage requestStorage; - public ItemDto getItem(long itemId) { - var itemEntity = itemStorage.getItem(itemId) + public ItemDto findItem(long itemId) { + var itemEntity = itemStorage.findItem(itemId) .orElseThrow(() -> new NotFoundException("Вещь не найдена")); - var comments = itemEntity.getComments() - .stream() - .map(commentMapper::toDto) - .toList(); - - return itemMapper.toDto(itemEntity, comments); + return itemEntity.toDto(); } public ItemDto createItem(ItemDto item, long userId) { - var userEntity = userStorage.getUser(userId) + var userEntity = userStorage.findUserId(userId) .orElseThrow(() -> new NotFoundException("Пользователь не найден")); - var itemEntity = itemMapper.toEntity(item, userEntity); + var requestEntity = item.requestId() != null + ? requestStorage.findRequestById(item.requestId()) + .orElseThrow(() -> new NotFoundException("Запрос не найден")) + : null; + + var itemEntity = ItemMapper.toEntity(item, userEntity, requestEntity); itemStorage.updateItem(itemEntity); - return itemMapper.toDto(itemEntity); + return ItemMapper.toDto(itemEntity); } public ItemDto updateItem(ItemDto item, long userId) { - var itemEntityOld = itemStorage.getItem(item.id()) + var itemEntityOld = itemStorage.findItem(item.id()) .orElseThrow(() -> new NotFoundException("Вещь не найдена")); var owner = itemEntityOld.getOwner(); if (owner.getId() != userId) { - throw new NotFoundException("Вещь не доступна"); + throw new NotFoundException("Владелец не найден"); } - var itemEntity = itemMapper.toEntity(item, owner); + var requestEntity = item.requestId() != null + ? requestStorage.findRequestById(item.requestId()) + .orElseThrow(() -> new NotFoundException("Запрос не найден")) + : null; + + var itemEntity = ItemMapper.toEntity(item, owner, requestEntity); itemStorage.updateItem(itemEntity); - return itemMapper.toDto(itemEntity); + return ItemMapper.toDto(itemEntity); } public ItemDto updateItem(long itemId, ItemToUpdateDto item, long userId) { - var itemEntity = itemStorage.getItem(itemId) + var itemEntity = itemStorage.findItem(itemId) .orElseThrow(() -> new NotFoundException("Вещь не найдена")); var owner = itemEntity.getOwner(); @@ -85,11 +89,11 @@ public ItemDto updateItem(long itemId, ItemToUpdateDto item, long userId) { itemStorage.updateItem(itemEntity); - return itemMapper.toDto(itemEntity); + return ItemMapper.toDto(itemEntity); } public ItemDto deleteItem(long itemId, long userId) { - var itemEntityOld = itemStorage.getItem(itemId) + var itemEntityOld = itemStorage.findItem(itemId) .orElseThrow(() -> new NotFoundException("Вещь не найдена")); var owner = itemEntityOld.getOwner(); @@ -100,18 +104,18 @@ public ItemDto deleteItem(long itemId, long userId) { itemStorage.deleteItem(itemId); - return itemMapper.toDto(itemEntityOld); + return ItemMapper.toDto(itemEntityOld); } public List getItems(long userId) { - var userEntity = userStorage.getUser(userId) + var userEntity = userStorage.findUserId(userId) .orElseThrow(() -> new NotFoundException("Пользователь не найден")); var itemEntities = userEntity.getItems(); return itemEntities .stream() - .map(itemMapper::toDto) + .map(ItemMapper::toDto) .toList(); } @@ -119,35 +123,35 @@ public List findFreeItemsByText(String text, boolean available) { if (text == null || text.isEmpty()) return List.of(); - var itemEntities = itemStorage.findItemsByTextAndStatus(text, true); + var itemEntities = itemStorage.findItemsByTextAndStatus(text, available); return itemEntities .stream() - .map(itemMapper::toDto) + .map(ItemMapper::toDto) .toList(); } public CommentDto addComment(CommentDto commentDto, long itemId, long userId) { - log.trace(String.format("addComment: itemId = %d, userId = %d", itemId, userId)); + //log.trace(String.format("addComment: itemId = %d, userId = %d", itemId, userId)); - var userEntity = userStorage.getUser(userId) + var userEntity = userStorage.findUserId(userId) .orElseThrow(() -> new NotFoundException("Пользователь не найден")); - var itemEntity = itemStorage.getItem(itemId) + var itemEntity = itemStorage.findItem(itemId) .orElseThrow(() -> new NotFoundException("Вешь не найдена")); - log.trace(String.format("addComment: itemEntity = {%s}", itemEntity)); + //log.trace(String.format("addComment: itemEntity = {%s}", itemEntity)); if (!bookingStorage.existsByBookerIdAndItemIdAndAfterEnd(userId, itemId)) { throw new NotValidException("Пользователь не брал вещь, не может оставить комментарий"); } - var commentEntity = commentMapper.toEntity(commentDto, userEntity, itemEntity); + var commentEntity = CommentMapper.toEntity(commentDto, userEntity, itemEntity); commentEntity.setCreated(Timestamp.valueOf(LocalDateTime.now())); itemStorage.updateComment(commentEntity); - return commentMapper.toDto(commentEntity); + return CommentMapper.toDto(commentEntity); } } diff --git a/src/main/java/ru/practicum/shareit/item/ItemStorage.java b/server/src/main/java/ru/practicum/shareit/item/ItemStorage.java similarity index 74% rename from src/main/java/ru/practicum/shareit/item/ItemStorage.java rename to server/src/main/java/ru/practicum/shareit/item/ItemStorage.java index 7cb4d79..2591818 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemStorage.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemStorage.java @@ -7,18 +7,19 @@ import java.util.Optional; public interface ItemStorage { - Optional getItem(long itemId); + Optional findItem(long itemId); void updateItem(ItemEntity itemEntity); void updateComment(CommentEntity commentEntity); - Optional deleteItem(long itemId); + void deleteItem(long itemId); - Optional deleteComment(long commentId); + void deleteComment(long commentId); List getItemsForOwner(long userId); List findItemsByTextAndStatus(String text, boolean available); + List getAllItems(); } diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java new file mode 100644 index 0000000..6b72310 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java @@ -0,0 +1,14 @@ +package ru.practicum.shareit.item.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; + +import java.sql.Timestamp; + +@Builder(toBuilder = true) +public record CommentDto( + Long id, + @NotBlank String text, + String authorName, + Timestamp created +) { } diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java new file mode 100644 index 0000000..842bbd5 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -0,0 +1,20 @@ +package ru.practicum.shareit.item.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +import java.sql.Timestamp; +import java.util.List; + +@Builder(toBuilder = true) +public record ItemDto( + Long id, + @NotBlank String name, + @NotBlank String description, + @NotNull Boolean available, + Timestamp lastBooking, + Timestamp nextBooking, + List comments, + Long requestId +) { } diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/ItemToUpdateDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemToUpdateDto.java new file mode 100644 index 0000000..af7523d --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemToUpdateDto.java @@ -0,0 +1,10 @@ +package ru.practicum.shareit.item.dto; + +import lombok.Builder; + +@Builder +public record ItemToUpdateDto( + String name, + String description, + Boolean available +) { } diff --git a/src/main/java/ru/practicum/shareit/item/storage/CommentEntity.java b/server/src/main/java/ru/practicum/shareit/item/storage/CommentEntity.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/storage/CommentEntity.java rename to server/src/main/java/ru/practicum/shareit/item/storage/CommentEntity.java diff --git a/src/main/java/ru/practicum/shareit/item/storage/CommentRepository.java b/server/src/main/java/ru/practicum/shareit/item/storage/CommentRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/storage/CommentRepository.java rename to server/src/main/java/ru/practicum/shareit/item/storage/CommentRepository.java diff --git a/src/main/java/ru/practicum/shareit/item/storage/ItemEntity.java b/server/src/main/java/ru/practicum/shareit/item/storage/ItemEntity.java similarity index 58% rename from src/main/java/ru/practicum/shareit/item/storage/ItemEntity.java rename to server/src/main/java/ru/practicum/shareit/item/storage/ItemEntity.java index 55af6c1..0bbd06a 100644 --- a/src/main/java/ru/practicum/shareit/item/storage/ItemEntity.java +++ b/server/src/main/java/ru/practicum/shareit/item/storage/ItemEntity.java @@ -2,6 +2,10 @@ import jakarta.persistence.*; import lombok.Data; +import ru.practicum.shareit.item.CommentMapper; +import ru.practicum.shareit.item.ItemMapper; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.request.storage.RequestEntity; import ru.practicum.shareit.user.storage.UserEntity; import java.util.List; @@ -29,6 +33,10 @@ public class ItemEntity { @OneToMany(fetch = FetchType.LAZY, mappedBy = "item") private List comments; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "request_id") + private RequestEntity request; + public ItemEntity() { } @@ -37,4 +45,18 @@ public ItemEntity() { public String toString() { return String.format("id=%d,owner=%d,name=%s,desc=%s,available=%b", id, owner.getId(), name, description, available); } + + public ItemDto toDto() { + var comments = getComments(); + + if (comments == null) + return ItemMapper.toDto(this, List.of()); + + var commentDtos = comments + .stream() + .map(CommentMapper::toDto) + .toList(); + + return ItemMapper.toDto(this, commentDtos); + } } diff --git a/src/main/java/ru/practicum/shareit/item/storage/ItemRepository.java b/server/src/main/java/ru/practicum/shareit/item/storage/ItemRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/storage/ItemRepository.java rename to server/src/main/java/ru/practicum/shareit/item/storage/ItemRepository.java diff --git a/src/main/java/ru/practicum/shareit/item/storage/ItemStorageInDb.java b/server/src/main/java/ru/practicum/shareit/item/storage/ItemStorageInDb.java similarity index 57% rename from src/main/java/ru/practicum/shareit/item/storage/ItemStorageInDb.java rename to server/src/main/java/ru/practicum/shareit/item/storage/ItemStorageInDb.java index e96b71a..b5c2b18 100644 --- a/src/main/java/ru/practicum/shareit/item/storage/ItemStorageInDb.java +++ b/server/src/main/java/ru/practicum/shareit/item/storage/ItemStorageInDb.java @@ -4,10 +4,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; -import ru.practicum.shareit.booking.storage.BookingRepository; -import ru.practicum.shareit.item.ItemMapper; import ru.practicum.shareit.item.ItemStorage; -import ru.practicum.shareit.user.storage.UserRepository; import java.util.List; import java.util.Optional; @@ -17,15 +14,11 @@ @Qualifier("ItemStorageInDb") @Primary public class ItemStorageInDb implements ItemStorage { - private final UserRepository userRepository; private final ItemRepository itemRepository; - private final BookingRepository bookingRepository; private final CommentRepository commentRepository; - private final ItemMapper itemMapper = new ItemMapper(); - private final CommentMapper commentMapper = new CommentMapper(); @Override - public Optional getItem(long itemId) { + public Optional findItem(long itemId) { return itemRepository.findById(itemId); } @@ -40,25 +33,13 @@ public void updateComment(CommentEntity commentEntity) { } @Override - public Optional deleteItem(long itemId) { - var entity = itemRepository.findById(itemId); - - if (entity.isPresent()) { - itemRepository.deleteById(itemId); - } - - return entity; + public void deleteItem(long itemId) { + itemRepository.deleteById(itemId); } @Override - public Optional deleteComment(long commentId) { - var entity = commentRepository.findById(commentId); - - if (entity.isPresent()) { - commentRepository.deleteById(commentId); - } - - return entity; + public void deleteComment(long commentId) { + commentRepository.deleteById(commentId); } @Override @@ -70,4 +51,9 @@ public List getItemsForOwner(long userId) { public List findItemsByTextAndStatus(String text, boolean available) { return itemRepository.findByTextAndAvailable(text, available); } + + @Override + public List getAllItems() { + return itemRepository.findAll(); + } } diff --git a/src/main/java/ru/practicum/shareit/item/storage/ItemWithDateOfBookings.java b/server/src/main/java/ru/practicum/shareit/item/storage/ItemWithDateOfBookings.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/storage/ItemWithDateOfBookings.java rename to server/src/main/java/ru/practicum/shareit/item/storage/ItemWithDateOfBookings.java diff --git a/server/src/main/java/ru/practicum/shareit/request/RequestController.java b/server/src/main/java/ru/practicum/shareit/request/RequestController.java new file mode 100644 index 0000000..39264f8 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/RequestController.java @@ -0,0 +1,36 @@ +package ru.practicum.shareit.request; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.request.dto.RequestDto; + +import java.util.List; + +@RestController +@RequestMapping(path = "/requests") +@RequiredArgsConstructor +public class RequestController { + private final RequestService requestService; + + @GetMapping + public List getSelfRequests(@RequestHeader("X-Sharer-User-Id") long userId) { + return requestService.findRequestsByUserId(userId, true); + } + + @GetMapping("/{id}") + public RequestDto getRequestById(@PathVariable long id) { + return requestService.findRequestById(id); + } + + @GetMapping("/all") + public List getOtherUserRequests(@RequestHeader("X-Sharer-User-Id") long userId) { + return requestService.findRequestsByUserId(userId, false); + } + + @PostMapping + public RequestDto postRequestDto( + @RequestBody RequestDto requestDto, + @RequestHeader("X-Sharer-User-Id") long userId) { + return requestService.createRequest(requestDto, userId); + } +} diff --git a/server/src/main/java/ru/practicum/shareit/request/RequestMapper.java b/server/src/main/java/ru/practicum/shareit/request/RequestMapper.java new file mode 100644 index 0000000..62ca50f --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/RequestMapper.java @@ -0,0 +1,39 @@ +package ru.practicum.shareit.request; + +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.request.dto.RequestDto; +import ru.practicum.shareit.request.storage.RequestEntity; +import ru.practicum.shareit.user.storage.UserEntity; + +import java.util.List; + +public class RequestMapper { + public static RequestDto toDto(RequestEntity entity, List items) { + var build = RequestDto + .builder() + .id(entity.getId()) + .text(entity.getText()) + .description(entity.getDescription()) + .created(entity.getCreated()) + .items(items); + + return build.build(); + } + + public static RequestEntity toEntity( + RequestDto requestDto, + UserEntity userEntity) { + var entity = new RequestEntity(); + entity.setId(requestDto.id()); + entity.setUser(userEntity); + entity.setText(requestDto.text()); + entity.setDescription(requestDto.description()); + + var created = requestDto.created(); + + if (created != null) + entity.setCreated(created); + + return entity; + } +} diff --git a/server/src/main/java/ru/practicum/shareit/request/RequestService.java b/server/src/main/java/ru/practicum/shareit/request/RequestService.java new file mode 100644 index 0000000..8063583 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/RequestService.java @@ -0,0 +1,53 @@ +package ru.practicum.shareit.request; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.request.dto.RequestDto; +import ru.practicum.shareit.request.storage.RequestEntity; +import ru.practicum.shareit.user.UserStorage; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class RequestService { + private final RequestStorage requestStorage; + private final UserStorage userStorage; + + public RequestDto createRequest(@Valid RequestDto requestDto, long userId) { + var userEntity = userStorage.findUserId(userId) + .orElseThrow(() -> new NotFoundException("Пользователь не найден")); + + var requestEntity = RequestMapper.toEntity(requestDto, userEntity); + requestEntity.setCreated(Timestamp.valueOf(LocalDateTime.now())); + + requestStorage.updateRequest(requestEntity); + + return requestEntity.toDto(); + } + + public List findRequestsByUserId(long userId, boolean findByEqualUserId) { + if (findByEqualUserId && !userStorage.existsById(userId)) + throw new NotFoundException("Пользователь не найден"); + + var requests = findByEqualUserId + ? requestStorage.findRequestsByUserId(userId) + : requestStorage.findRequestsByNotUserId(userId); + + return requests + .stream() + .map(RequestEntity::toDto) + .toList(); + } + + public RequestDto findRequestById(long id) { + var requestEntity = requestStorage.findRequestById(id) + .orElseThrow(() -> new NotFoundException("Запрос не найден")); + + return requestEntity.toDto(); + } +} diff --git a/server/src/main/java/ru/practicum/shareit/request/RequestStorage.java b/server/src/main/java/ru/practicum/shareit/request/RequestStorage.java new file mode 100644 index 0000000..6b6a0c9 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/RequestStorage.java @@ -0,0 +1,16 @@ +package ru.practicum.shareit.request; + +import ru.practicum.shareit.request.storage.RequestEntity; + +import java.util.List; +import java.util.Optional; + +public interface RequestStorage { + void updateRequest(RequestEntity requestEntity); + + List findRequestsByUserId(long userId); + + List findRequestsByNotUserId(long userId); + + Optional findRequestById(long id); +} diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/RequestDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/RequestDto.java new file mode 100644 index 0000000..7b74515 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dto/RequestDto.java @@ -0,0 +1,19 @@ +package ru.practicum.shareit.request.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import ru.practicum.shareit.item.dto.ItemDto; + +import java.sql.Timestamp; +import java.util.List; + +@Builder(toBuilder = true) +public record RequestDto( + Long id, + String text, + @NotBlank + String description, + Timestamp created, + List items +) { +} diff --git a/server/src/main/java/ru/practicum/shareit/request/storage/RequestEntity.java b/server/src/main/java/ru/practicum/shareit/request/storage/RequestEntity.java new file mode 100644 index 0000000..5bdf255 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/storage/RequestEntity.java @@ -0,0 +1,51 @@ +package ru.practicum.shareit.request.storage; + +import jakarta.persistence.*; +import lombok.Data; +import ru.practicum.shareit.item.ItemMapper; +import ru.practicum.shareit.item.storage.ItemEntity; +import ru.practicum.shareit.request.RequestMapper; +import ru.practicum.shareit.request.dto.RequestDto; +import ru.practicum.shareit.user.storage.UserEntity; + +import java.sql.Timestamp; +import java.util.List; +import java.util.Optional; + +@Entity +@Table(name = "Requests") +@Data +public class RequestEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "request_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private UserEntity user; + + @Column(nullable = false) + private String text; + @Column + private String description; + @Column + private Timestamp created; + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "request") + private List items; + + public RequestEntity() { + + } + + public RequestDto toDto() { + var items = Optional.ofNullable(getItems()) + .orElse(List.of()) + .stream() + .map(ItemMapper::toDto) + .toList(); + + return RequestMapper.toDto(this, items); + } +} diff --git a/server/src/main/java/ru/practicum/shareit/request/storage/RequestRepository.java b/server/src/main/java/ru/practicum/shareit/request/storage/RequestRepository.java new file mode 100644 index 0000000..357049b --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/storage/RequestRepository.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.request.storage; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface RequestRepository extends JpaRepository { + List findByUserId(long id); + + List findByUserIdNot(long id); +} diff --git a/server/src/main/java/ru/practicum/shareit/request/storage/RequestStorageInDb.java b/server/src/main/java/ru/practicum/shareit/request/storage/RequestStorageInDb.java new file mode 100644 index 0000000..3b7bc32 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/storage/RequestStorageInDb.java @@ -0,0 +1,36 @@ +package ru.practicum.shareit.request.storage; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; +import ru.practicum.shareit.request.RequestStorage; + +import java.util.List; +import java.util.Optional; + +@Component +@RequiredArgsConstructor +@Primary +public class RequestStorageInDb implements RequestStorage { + private final RequestRepository requestRepository; + + @Override + public void updateRequest(RequestEntity requestEntity) { + requestRepository.saveAndFlush(requestEntity); + } + + @Override + public List findRequestsByUserId(long userId) { + return requestRepository.findByUserId(userId); + } + + @Override + public List findRequestsByNotUserId(long userId) { + return requestRepository.findByUserIdNot(userId); + } + + @Override + public Optional findRequestById(long id) { + return requestRepository.findById(id); + } +} diff --git a/src/main/java/ru/practicum/shareit/tool/ErrorHandler.java b/server/src/main/java/ru/practicum/shareit/tool/ErrorHandler.java similarity index 100% rename from src/main/java/ru/practicum/shareit/tool/ErrorHandler.java rename to server/src/main/java/ru/practicum/shareit/tool/ErrorHandler.java diff --git a/src/main/java/ru/practicum/shareit/tool/ErrorResponse.java b/server/src/main/java/ru/practicum/shareit/tool/ErrorResponse.java similarity index 100% rename from src/main/java/ru/practicum/shareit/tool/ErrorResponse.java rename to server/src/main/java/ru/practicum/shareit/tool/ErrorResponse.java diff --git a/server/src/main/java/ru/practicum/shareit/tool/IgnoreJacocoGenerated.java b/server/src/main/java/ru/practicum/shareit/tool/IgnoreJacocoGenerated.java new file mode 100644 index 0000000..4e52be2 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/tool/IgnoreJacocoGenerated.java @@ -0,0 +1,9 @@ +package ru.practicum.shareit.tool; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface IgnoreJacocoGenerated { +} diff --git a/src/main/java/ru/practicum/shareit/user/UserController.java b/server/src/main/java/ru/practicum/shareit/user/UserController.java similarity index 79% rename from src/main/java/ru/practicum/shareit/user/UserController.java rename to server/src/main/java/ru/practicum/shareit/user/UserController.java index c1da386..df1602c 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/server/src/main/java/ru/practicum/shareit/user/UserController.java @@ -1,6 +1,5 @@ package ru.practicum.shareit.user; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import ru.practicum.shareit.user.dto.UserDto; @@ -15,21 +14,21 @@ public class UserController { @GetMapping("/{id}") public UserDto getUser(@PathVariable long id) { - return userService.getUser(id); + return userService.findUserId(id); } @PostMapping - public UserDto postUser(@Valid @RequestBody UserDto user) { + public UserDto postUser(@RequestBody UserDto user) { return userService.updateUser(user); } @PutMapping - public UserDto putUser(@Valid @RequestBody UserDto user) { + public UserDto putUser(@RequestBody UserDto user) { return userService.updateUser(user); } @PatchMapping("/{id}") - public UserDto patchUser(@PathVariable long id, @RequestBody @Valid UserToUpdateDto user) { + public UserDto patchUser(@PathVariable long id, @RequestBody UserToUpdateDto user) { return userService.updateUser(id, user); } diff --git a/src/main/java/ru/practicum/shareit/user/UserMapper.java b/server/src/main/java/ru/practicum/shareit/user/UserMapper.java similarity index 83% rename from src/main/java/ru/practicum/shareit/user/UserMapper.java rename to server/src/main/java/ru/practicum/shareit/user/UserMapper.java index d6e99e6..9b270c6 100644 --- a/src/main/java/ru/practicum/shareit/user/UserMapper.java +++ b/server/src/main/java/ru/practicum/shareit/user/UserMapper.java @@ -4,7 +4,7 @@ import ru.practicum.shareit.user.storage.UserEntity; public class UserMapper { - public UserEntity toEntity(UserDto user) { + public static UserEntity toEntity(UserDto user) { var entity = new UserEntity(); entity.setId(user.id()); entity.setName(user.name()); @@ -13,7 +13,7 @@ public UserEntity toEntity(UserDto user) { return entity; } - public UserDto toDto(UserEntity userEntity) { + public static UserDto toDto(UserEntity userEntity) { return UserDto .builder() .id(userEntity.getId()) diff --git a/src/main/java/ru/practicum/shareit/user/UserService.java b/server/src/main/java/ru/practicum/shareit/user/UserService.java similarity index 70% rename from src/main/java/ru/practicum/shareit/user/UserService.java rename to server/src/main/java/ru/practicum/shareit/user/UserService.java index 1a36078..6ba2042 100644 --- a/src/main/java/ru/practicum/shareit/user/UserService.java +++ b/server/src/main/java/ru/practicum/shareit/user/UserService.java @@ -10,25 +10,24 @@ @RequiredArgsConstructor public class UserService { private final UserStorage userStorage; - private final UserMapper userMapper = new UserMapper(); - public UserDto getUser(long userId) { - var userEntity = userStorage.getUser(userId) + public UserDto findUserId(long userId) { + var userEntity = userStorage.findUserId(userId) .orElseThrow(() -> new NotFoundException("Не нашел userId в системе")); - return userMapper.toDto(userEntity); + return UserMapper.toDto(userEntity); } public UserDto updateUser(UserDto user) { - var userEntity = userMapper.toEntity(user); + var userEntity = UserMapper.toEntity(user); userStorage.updateUser(userEntity); - return userMapper.toDto(userEntity); + return UserMapper.toDto(userEntity); } public UserDto updateUser(long userId, UserToUpdateDto user) { - var userEntity = userStorage.getUser(userId) + var userEntity = userStorage.findUserId(userId) .orElseThrow(() -> new NotFoundException("Не нашел userId в системе")); if (user.name() != null) @@ -38,15 +37,15 @@ public UserDto updateUser(long userId, UserToUpdateDto user) { userStorage.updateUser(userEntity); - return userMapper.toDto(userEntity); + return UserMapper.toDto(userEntity); } public UserDto deleteUser(long userId) { - var userEntity = userStorage.getUser(userId) + var userEntity = userStorage.findUserId(userId) .orElseThrow(() -> new NotFoundException("Не нашел userId в системе")); userStorage.deleteUser(userEntity); - return userMapper.toDto(userEntity); + return UserMapper.toDto(userEntity); } } diff --git a/src/main/java/ru/practicum/shareit/user/UserStorage.java b/server/src/main/java/ru/practicum/shareit/user/UserStorage.java similarity index 84% rename from src/main/java/ru/practicum/shareit/user/UserStorage.java rename to server/src/main/java/ru/practicum/shareit/user/UserStorage.java index 151ed33..7e9922f 100644 --- a/src/main/java/ru/practicum/shareit/user/UserStorage.java +++ b/server/src/main/java/ru/practicum/shareit/user/UserStorage.java @@ -5,7 +5,7 @@ import java.util.Optional; public interface UserStorage { - Optional getUser(long userId); + Optional findUserId(long userId); void updateUser(UserEntity user); diff --git a/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java new file mode 100644 index 0000000..ec096cc --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -0,0 +1,18 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +@Builder(toBuilder = true) +public record UserDto( + Long id, + + @NotBlank + String name, + + @NotNull + @Email + String email +) { } diff --git a/server/src/main/java/ru/practicum/shareit/user/dto/UserToUpdateDto.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserToUpdateDto.java new file mode 100644 index 0000000..918e784 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/user/dto/UserToUpdateDto.java @@ -0,0 +1,10 @@ +package ru.practicum.shareit.user.dto; + +import lombok.Builder; + +@Builder +public record UserToUpdateDto( + Long id, + String name, + String email +) { } diff --git a/src/main/java/ru/practicum/shareit/user/storage/UserEntity.java b/server/src/main/java/ru/practicum/shareit/user/storage/UserEntity.java similarity index 95% rename from src/main/java/ru/practicum/shareit/user/storage/UserEntity.java rename to server/src/main/java/ru/practicum/shareit/user/storage/UserEntity.java index 621f169..da64f3b 100644 --- a/src/main/java/ru/practicum/shareit/user/storage/UserEntity.java +++ b/server/src/main/java/ru/practicum/shareit/user/storage/UserEntity.java @@ -15,7 +15,7 @@ public class UserEntity { @Column(name = "user_id", nullable = false) private Long id; - @Column + @Column(nullable = false) private String name; @Column(nullable = false) private String email; diff --git a/src/main/java/ru/practicum/shareit/user/storage/UserRepository.java b/server/src/main/java/ru/practicum/shareit/user/storage/UserRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/storage/UserRepository.java rename to server/src/main/java/ru/practicum/shareit/user/storage/UserRepository.java diff --git a/src/main/java/ru/practicum/shareit/user/storage/UserStorageInDB.java b/server/src/main/java/ru/practicum/shareit/user/storage/UserStorageInDB.java similarity index 85% rename from src/main/java/ru/practicum/shareit/user/storage/UserStorageInDB.java rename to server/src/main/java/ru/practicum/shareit/user/storage/UserStorageInDB.java index f5e48ab..75d8fff 100644 --- a/src/main/java/ru/practicum/shareit/user/storage/UserStorageInDB.java +++ b/server/src/main/java/ru/practicum/shareit/user/storage/UserStorageInDB.java @@ -4,7 +4,6 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; -import ru.practicum.shareit.user.UserMapper; import ru.practicum.shareit.user.UserStorage; import java.util.Optional; @@ -15,10 +14,9 @@ @Primary public class UserStorageInDB implements UserStorage { private final UserRepository userRepository; - private final UserMapper userMapper = new UserMapper(); @Override - public Optional getUser(long userId) { + public Optional findUserId(long userId) { return userRepository.findById(userId); } diff --git a/src/main/resources/application-dev.yaml b/server/src/main/resources/application-dev.yaml similarity index 76% rename from src/main/resources/application-dev.yaml rename to server/src/main/resources/application-dev.yaml index a93fcf0..39e9429 100644 --- a/src/main/resources/application-dev.yaml +++ b/server/src/main/resources/application-dev.yaml @@ -6,12 +6,12 @@ spring: driverClassName: org.h2.Driver url: jdbc:h2:mem:shareit jpa: - #show-sql: true + #show-sql: true show-sql: false logging.level: ru.practicum.shareit: TRACE org: zalando.logbook: TRACE - hibernate: - type.descriptor.sql.BasicBinder: TRACE - #orm.jdbc.bind: TRACE +# hibernate: +# type.descriptor.sql.BasicBinder: TRACE +# orm.jdbc.bind: TRACE diff --git a/server/src/main/resources/application.properties b/server/src/main/resources/application.properties new file mode 100644 index 0000000..5ff0285 --- /dev/null +++ b/server/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=9090 \ No newline at end of file diff --git a/src/main/resources/application.yaml b/server/src/main/resources/application.yaml similarity index 61% rename from src/main/resources/application.yaml rename to server/src/main/resources/application.yaml index 38cb0fe..55085a4 100644 --- a/src/main/resources/application.yaml +++ b/server/src/main/resources/application.yaml @@ -1,9 +1,4 @@ spring: -# datasource: -# driverClassName: org.postgresql.Driver -# url: jdbc:postgresql://localhost:5432/shareit -# username: dbuser -# password: 12345 jpa: hibernate.ddl-auto: none properties: diff --git a/src/main/resources/schema.sql b/server/src/main/resources/schema.sql similarity index 61% rename from src/main/resources/schema.sql rename to server/src/main/resources/schema.sql index 284dc0b..66ec3f6 100644 --- a/src/main/resources/schema.sql +++ b/server/src/main/resources/schema.sql @@ -1,19 +1,28 @@ -CREATE TABLE if not exists Users( +CREATE TABLE if NOT EXISTS Users( user_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL, CONSTRAINT uniqueEmails UNIQUE(email) ); -CREATE TABLE IF NOT exists Items( +CREATE TABLE IF NOT EXISTS Requests( + request_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + user_id BIGINT REFERENCES Users(user_id), + text VARCHAR(1000), + description VARCHAR(1000), + created timestamp WITHOUT TIME ZONE NOT NULL +); + +CREATE TABLE IF NOT EXISTS Items( item_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, owner_id BIGINT REFERENCES Users(user_id), name VARCHAR(100) NOT NULL, - description VARCHAR(100) NOT NULL, - available boolean + description VARCHAR(1000) NOT NULL, + available boolean, + request_id BIGINT REFERENCES Requests(request_id) ); -CREATE TABLE IF NOT exists Comments( +CREATE TABLE IF NOT EXISTS Comments( comment_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, user_id BIGINT REFERENCES Users(user_id), item_id BIGINT REFERENCES Items(item_id), @@ -21,12 +30,12 @@ CREATE TABLE IF NOT exists Comments( created timestamp WITHOUT TIME ZONE NOT NULL ); -CREATE TABLE IF NOT exists Bookings( +CREATE TABLE IF NOT EXISTS Bookings( booking_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, booker_id BIGINT REFERENCES Users(user_id), item_id BIGINT REFERENCES Items(item_id), status VARCHAR(100) NOT NULL, - description VARCHAR(100), + description VARCHAR(1000), start timestamp WITHOUT TIME ZONE NOT NULL, finish timestamp WITHOUT TIME ZONE NOT NULL ); \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/ShareItTests.java b/server/src/test/java/ru/practicum/shareit/ShareItTests.java new file mode 100644 index 0000000..4d79052 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/ShareItTests.java @@ -0,0 +1,13 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ShareItTests { + + @Test + void contextLoads() { + } + +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java new file mode 100644 index 0000000..5aae708 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java @@ -0,0 +1,274 @@ +package ru.practicum.shareit.booking; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import ru.practicum.shareit.booking.dto.BookingDto; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.hamcrest.Matchers.is; + +@SpringBootTest +public class BookingControllerTest { + @Mock + BookingService bookingService; + + @InjectMocks + BookingController bookingController; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void requestPathWithoutUserIdInHead() throws Exception { + List needIdUserFromHeadRequest = + List.of(get("/bookings/1"), + get("/bookings"), + get("/bookings/owner"), + post("/bookings"), + patch("/bookings/1?approved=true")); + + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + for (var requestBuilder: needIdUserFromHeadRequest) { + var result = mockMvc.perform(requestBuilder) + .andReturn(); + + var status = result + .getResponse() + .getStatus(); + + var path = result.getRequest().getMethod() + " '" + result.getRequest().getRequestURI() + "'"; + + Assertions.assertEquals(400, status, path + " was needed userid as RequestHeader \"X-Sharer-User-Id\""); + } + } + + @Test + public void getBookingTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + var dto = BookingDto.builder() + .id(10L) + .build(); + + Mockito.when(bookingService.findBooking(Mockito.anyLong(), Mockito.anyLong())) + .thenReturn(dto); + + mockMvc.perform(get("/bookings/1") + .header("X-Sharer-User-Id", 1L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(10L), Long.class)); + } + + @Test + void getItemParsingRequestId() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + mockMvc.perform(get("/bookings/100") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()); + + Mockito.verify(bookingService, Mockito.times(1)) + .findBooking(100, 10); + } + + @Test + void getItemsForUserIdTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + var expectedResult = List.of( + BookingDto.builder().id(1L).build(), + BookingDto.builder().id(2L).build(), + BookingDto.builder().id(3L).build()); + + Mockito.when(bookingService.findBookingsForUserId(Mockito.anyLong())) + .thenReturn(expectedResult); + + mockMvc.perform(get("/bookings") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.[0].id",is(1L), Long.class)) + .andExpect(jsonPath("$.[1].id",is(2L), Long.class)) + .andExpect(jsonPath("$.[2].id",is(3L), Long.class)); + } + + @Test + void getItemsForUserIdParsingRequestTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + mockMvc.perform(get("/bookings") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()); + + Mockito.verify(bookingService, Mockito.times(1)) + .findBookingsForUserId(10); + } + + @Test + void getItemsForItemOwnerIdTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + var expectedResult = List.of( + BookingDto.builder().id(1L).build(), + BookingDto.builder().id(2L).build(), + BookingDto.builder().id(3L).build()); + + Mockito.when(bookingService.findBookingsForItemOwnerId(Mockito.anyLong())) + .thenReturn(expectedResult); + + mockMvc.perform(get("/bookings/owner") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.[0].id",is(1L), Long.class)) + .andExpect(jsonPath("$.[1].id",is(2L), Long.class)) + .andExpect(jsonPath("$.[2].id",is(3L), Long.class)); + } + + @Test + void getItemsForItemOwnerIdParsingRequestTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + mockMvc.perform(get("/bookings/owner") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()); + + Mockito.verify(bookingService, Mockito.times(1)) + .findBookingsForItemOwnerId(10); + } + + @Test + void postItemTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + Mockito.when(bookingService.createBooking(Mockito.any(BookingDto.class), Mockito.anyLong())) + .thenReturn(BookingDto.builder().id(50L).build()); + + mockMvc.perform(post("/bookings") + .header("X-Sharer-User-Id", 10L) + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(BookingDto.builder().build())) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(50L), Long.class)); + } + + @Test + void postItemParsingRequestTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + var dto = BookingDto.builder().build(); + + mockMvc.perform(post("/bookings") + .header("X-Sharer-User-Id", 10L) + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()); + + Mockito.verify(bookingService, Mockito.times(1)) + .createBooking(dto, 10L); + } + + @Test + void postItemWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + mockMvc.perform(post("/bookings") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().is(400)); + } + + @Test + void patchItemApprovedTrueTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + Mockito.when(bookingService.setBookingStatus(Mockito.anyLong(), Mockito.anyBoolean(), Mockito.anyLong())) + .thenReturn(BookingDto.builder().status(BookingStatus.APPROVED).build()); + + mockMvc.perform(patch("/bookings/10?approved=true") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status",is(BookingStatus.APPROVED.name()), String.class)); + } + + @Test + void patchItemApprovedFalseTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + Mockito.when(bookingService.setBookingStatus(Mockito.anyLong(), Mockito.anyBoolean(), Mockito.anyLong())) + .thenReturn(BookingDto.builder().status(BookingStatus.REJECTED).build()); + + mockMvc.perform(patch("/bookings/10?approved=false") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status",is(BookingStatus.REJECTED.name()), String.class)); + } + + @Test + void patchItemParsingRequestTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + mockMvc.perform(patch("/bookings/100?approved=true") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()); + + mockMvc.perform(patch("/bookings/100?approved=false") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()); + + Mockito.verify(bookingService, Mockito.times(1)) + .setBookingStatus(100, true, 10L); + + Mockito.verify(bookingService, Mockito.times(1)) + .setBookingStatus(100, false, 10L); + } + + @Test + void patchItemWithoutParamsTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(bookingController) + .build(); + + mockMvc.perform(patch("/bookings/100") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().is(400)); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingServiceTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingServiceTest.java new file mode 100644 index 0000000..139990f --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingServiceTest.java @@ -0,0 +1,561 @@ +package ru.practicum.shareit.booking; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.storage.BookingEntity; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.exceptions.NotValidException; +import ru.practicum.shareit.item.ItemStorage; +import ru.practicum.shareit.item.storage.ItemEntity; +import ru.practicum.shareit.user.UserStorage; +import ru.practicum.shareit.user.storage.UserEntity; + +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@SpringBootTest +public class BookingServiceTest { + @Mock + BookingStorage bookingStorage; + @Mock + ItemStorage itemStorage; + @Mock + UserStorage userStorage; + + @InjectMocks + BookingService bookingService; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void findBookingWhileNotFoundUser() { + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + bookingService.findBooking(10L, 10L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Пользователь не найден", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Пользователь не найден\")"); + } + + @Test + public void findBookingWhileNotFoundBookingId() { + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(new UserEntity())); + + Mockito.when(bookingStorage.findBooking(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + bookingService.findBooking(10L, 10L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Бронь не найдена", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Бронь не найдена\")"); + } + + @Test + public void findBookingWhileUserIsNotCreatorAndOwner() { + var ownerEntity = new UserEntity(); + ownerEntity.setId(1L); + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(ownerEntity)); + + var itemEntity = new ItemEntity(); + itemEntity.setId(2L); + itemEntity.setOwner(ownerEntity); + + var bookerEntity = new UserEntity(); + bookerEntity.setId(4L); + + var bookingEntity = new BookingEntity(); + bookingEntity.setId(3L); + bookingEntity.setItem(itemEntity); + bookingEntity.setBooker(bookerEntity); + + Mockito.when(bookingStorage.findBooking(Mockito.anyLong())) + .thenReturn(Optional.of(bookingEntity)); + + try { + bookingService.findBooking(10L, 10L); + } catch (NotValidException ex) { + Assertions.assertEquals("Данные о бронировании может получить заказчик или владелец", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotValidException(\"Данные о бронировании может получить заказчик или владелец\")"); + } + + @Test + public void findBookingWhileUserIsNotCreator() { + var userEntity = new UserEntity(); + userEntity.setId(1L); + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(userEntity)); + + var itemEntity = new ItemEntity(); + itemEntity.setId(2L); + itemEntity.setOwner(userEntity); + + var bookerEntity = new UserEntity(); + bookerEntity.setId(4L); + + var bookingEntity = new BookingEntity(); + bookingEntity.setId(3L); + bookingEntity.setItem(itemEntity); + bookingEntity.setBooker(bookerEntity); + bookingEntity.setStart(Timestamp.from(Instant.now())); + bookingEntity.setEnd(Timestamp.from(Instant.now())); + + Mockito.when(bookingStorage.findBooking(Mockito.anyLong())) + .thenReturn(Optional.of(bookingEntity)); + + bookingService.findBooking(10L, 1L); + } + + @Test + public void findBookingWhileUserIsNotItemOwner() { + var userEntity = new UserEntity(); + userEntity.setId(1L); + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(userEntity)); + + var itemEntity = new ItemEntity(); + itemEntity.setId(2L); + itemEntity.setOwner(userEntity); + + var bookerEntity = new UserEntity(); + bookerEntity.setId(4L); + + var bookingEntity = new BookingEntity(); + bookingEntity.setId(3L); + bookingEntity.setItem(itemEntity); + bookingEntity.setBooker(bookerEntity); + bookingEntity.setStart(Timestamp.from(Instant.now())); + bookingEntity.setEnd(Timestamp.from(Instant.now())); + + Mockito.when(bookingStorage.findBooking(Mockito.anyLong())) + .thenReturn(Optional.of(bookingEntity)); + + bookingService.findBooking(10L, 4L); + } + + @Test + public void findBooking() { + var userEntity = new UserEntity(); + userEntity.setId(1L); + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(userEntity)); + + var itemEntity = new ItemEntity(); + itemEntity.setId(2L); + itemEntity.setOwner(userEntity); + + var bookerEntity = new UserEntity(); + bookerEntity.setId(4L); + + var bookingEntity = new BookingEntity(); + bookingEntity.setId(3L); + bookingEntity.setItem(itemEntity); + bookingEntity.setBooker(bookerEntity); + bookingEntity.setStart(Timestamp.from(Instant.now())); + bookingEntity.setEnd(Timestamp.from(Instant.now())); + + Mockito.when(bookingStorage.findBooking(Mockito.anyLong())) + .thenReturn(Optional.of(bookingEntity)); + + var expectedBookingDto = bookingEntity.toDto(); + var actualBookingDto = bookingService.findBooking(13L, 1L); + + Assertions.assertEquals(expectedBookingDto, actualBookingDto); + + Mockito.verify(bookingStorage, Mockito.times(1)) + .findBooking(13L); + } + + @Test + public void findBookingsForUserIdWhileNotFoundUserId() { + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + bookingService.findBookingsForUserId(10L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Пользователь не найден", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Пользователь не найден\")"); + } + + @Test + public void findBookingsForUserId() { + var userEntity = new UserEntity(); userEntity.setId(2L); + + var itemEntity = new ItemEntity(); itemEntity.setId(3L); + + var expectedBookingEntity1 = new BookingEntity(); + expectedBookingEntity1.setId(4L); + expectedBookingEntity1.setBooker(userEntity); + expectedBookingEntity1.setItem(itemEntity); + expectedBookingEntity1.setStart(Timestamp.from(Instant.now())); + expectedBookingEntity1.setEnd(Timestamp.from(Instant.now())); + + var expectedBookingEntity2 = new BookingEntity(); + expectedBookingEntity2.setId(5L); + expectedBookingEntity2.setBooker(userEntity); + expectedBookingEntity2.setItem(itemEntity); + expectedBookingEntity2.setStart(Timestamp.from(Instant.now())); + expectedBookingEntity2.setEnd(Timestamp.from(Instant.now())); + + var expectedBookingEntity3 = new BookingEntity(); + expectedBookingEntity3.setId(6L); + expectedBookingEntity3.setBooker(userEntity); + expectedBookingEntity3.setItem(itemEntity); + expectedBookingEntity3.setStart(Timestamp.from(Instant.now())); + expectedBookingEntity3.setEnd(Timestamp.from(Instant.now())); + + var bookingEntities = List.of( + expectedBookingEntity1, + expectedBookingEntity2, + expectedBookingEntity3 + ); + + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(userEntity)); + + Mockito.when(bookingStorage.findBookingsByBookerId(Mockito.anyLong())) + .thenReturn(bookingEntities); + + var actualBookingList = bookingService.findBookingsForUserId(4L); + var expectedBookingList = bookingEntities.stream().map(BookingEntity::toDto).toList(); + + Assertions.assertEquals(actualBookingList, expectedBookingList); + } + + @Test + public void findBookingsForOwnerIdWhileNotFoundUserId() { + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + bookingService.findBookingsForItemOwnerId(10L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Пользователь не найден", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Пользователь не найден\")"); + } + + @Test + public void findBookingsForOwnerId() { + var userEntity = new UserEntity(); userEntity.setId(2L); + + var itemEntity = new ItemEntity(); itemEntity.setId(3L); + + var expectedBookingEntity1 = new BookingEntity(); + expectedBookingEntity1.setId(4L); + expectedBookingEntity1.setBooker(userEntity); + expectedBookingEntity1.setItem(itemEntity); + expectedBookingEntity1.setStart(Timestamp.from(Instant.now())); + expectedBookingEntity1.setEnd(Timestamp.from(Instant.now())); + + var expectedBookingEntity2 = new BookingEntity(); + expectedBookingEntity2.setId(5L); + expectedBookingEntity2.setBooker(userEntity); + expectedBookingEntity2.setItem(itemEntity); + expectedBookingEntity2.setStart(Timestamp.from(Instant.now())); + expectedBookingEntity2.setEnd(Timestamp.from(Instant.now())); + + var expectedBookingEntity3 = new BookingEntity(); + expectedBookingEntity3.setId(6L); + expectedBookingEntity3.setBooker(userEntity); + expectedBookingEntity3.setItem(itemEntity); + expectedBookingEntity3.setStart(Timestamp.from(Instant.now())); + expectedBookingEntity3.setEnd(Timestamp.from(Instant.now())); + + var bookingEntities = List.of( + expectedBookingEntity1, + expectedBookingEntity2, + expectedBookingEntity3 + ); + + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(userEntity)); + + Mockito.when(bookingStorage.findBookingsByOwnerId(Mockito.anyLong())) + .thenReturn(bookingEntities); + + var actualBookingList = bookingService.findBookingsForItemOwnerId(6L); + var expectedBookingList = bookingEntities.stream().map(BookingEntity::toDto).toList(); + + Assertions.assertEquals(expectedBookingList, actualBookingList); + + Mockito.verify(bookingStorage, Mockito.times(1)) + .findBookingsByOwnerId(6L); + } + + @Test + public void createBookingWhileNotFoundUserId() { + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + bookingService.createBooking(BookingDto.builder().build(), 1L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Пользователь не найден", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Пользователь не найден\")"); + } + + @Test + public void createBookingWhileNotFoundItemId() { + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(new UserEntity())); + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + var dto = BookingDto + .builder() + .itemId(10L) + .build(); + + try { + bookingService.createBooking(dto, 1L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Вещь не найдена", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Вещь не найдена\")"); + } + + @Test + public void createBookingWhileItemIsNotFree() { + var itemEntity = new ItemEntity(); + itemEntity.setAvailable(false); + + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(new UserEntity())); + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.of(itemEntity)); + + var dto = BookingDto + .builder() + .itemId(10L) + .build(); + + try { + bookingService.createBooking(dto, 1L); + } catch (NotValidException ex) { + Assertions.assertEquals("Вещь не доступна к бронированию", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotValidException(\"Вещь не доступна к бронированию\")"); + } + + @Test + public void createBooking() { + var itemEntity = new ItemEntity(); + itemEntity.setAvailable(true); + itemEntity.setId(3L); + + var userEntity = new UserEntity(); + userEntity.setId(4L); + + var bookingDto = BookingDto + .builder() + .id(5L) + .itemId(10L) + .start("2024-12-03T00:00:00") + .end("2024-12-04T00:00:00") + .build(); + + var expectedBookingEntity = new BookingEntity(); + expectedBookingEntity.setId(5L); + expectedBookingEntity.setBooker(userEntity); + expectedBookingEntity.setItem(itemEntity); + expectedBookingEntity.setStart(Timestamp.valueOf(LocalDateTime.of(2024,12,03,0,0))); + expectedBookingEntity.setEnd(Timestamp.valueOf(LocalDateTime.of(2024,12,04,0,0))); + expectedBookingEntity.setStatus(BookingStatus.WAITING); + + var expectedBookingDto = expectedBookingEntity.toDto(); + + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(userEntity)); + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.of(itemEntity)); + + var actualBooking = bookingService.createBooking(bookingDto, 7L); + + Assertions.assertEquals(expectedBookingDto, actualBooking); + + Mockito.verify(bookingStorage, Mockito.times(1)) + .updateBooking(expectedBookingEntity); + } + + @Test + public void setBookingStatusWhileNotFoundUserId() { + Mockito.when(userStorage.existsById(Mockito.anyLong())) + .thenReturn(false); + + try { + bookingService.setBookingStatus(1L, true, 1L); + } catch (NotValidException ex) { + Assertions.assertEquals("Пользователь не найден", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotValidException(\"Пользователь не найден\")"); + } + + @Test + public void setBookingStatusWhileNotFoundBookingId() { + Mockito.when(userStorage.existsById(Mockito.anyLong())) + .thenReturn(true); + Mockito.when(bookingStorage.findBooking(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + bookingService.setBookingStatus(1L, true, 1L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Бронь не найдена", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Бронь не найдена\"\")"); + } + + @Test + public void setBookingStatusWhileUserIdIsNotOwner() { + var ownerEntity = new UserEntity(); + ownerEntity.setId(4L); + + var bookerEntity = new UserEntity(); + bookerEntity.setId(7L); + + var itemEntity = new ItemEntity(); + itemEntity.setAvailable(true); + itemEntity.setId(3L); + itemEntity.setOwner(ownerEntity); + + var bookingEntity = new BookingEntity(); + bookingEntity.setId(5L); + bookingEntity.setBooker(bookerEntity); + bookingEntity.setItem(itemEntity); + bookingEntity.setStart(Timestamp.valueOf(LocalDateTime.of(2024,12,03,0,0))); + bookingEntity.setEnd(Timestamp.valueOf(LocalDateTime.of(2024,12,04,0,0))); + bookingEntity.setStatus(BookingStatus.WAITING); + + Mockito.when(userStorage.existsById(Mockito.anyLong())) + .thenReturn(true); + Mockito.when(bookingStorage.findBooking(Mockito.anyLong())) + .thenReturn(Optional.of(bookingEntity)); + + try { + bookingService.setBookingStatus(1L, true, 1L); + } catch (NotValidException ex) { + Assertions.assertEquals("Подтвердить бронь может только владелец", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotValidException(\"Подтвердить бронь может только владелец\")"); + } + + @Test + public void setBookingStatusApprovedTest() { + var ownerEntity = new UserEntity(); + ownerEntity.setId(4L); + + var bookerEntity = new UserEntity(); + bookerEntity.setId(7L); + + var itemEntity = new ItemEntity(); + itemEntity.setAvailable(true); + itemEntity.setId(3L); + itemEntity.setOwner(ownerEntity); + + var bookingEntity = new BookingEntity(); + bookingEntity.setId(5L); + bookingEntity.setBooker(bookerEntity); + bookingEntity.setItem(itemEntity); + bookingEntity.setStart(Timestamp.valueOf(LocalDateTime.of(2024,12,03,0,0))); + bookingEntity.setEnd(Timestamp.valueOf(LocalDateTime.of(2024,12,04,0,0))); + bookingEntity.setStatus(BookingStatus.WAITING); + + Mockito.when(userStorage.existsById(Mockito.anyLong())) + .thenReturn(true); + Mockito.when(bookingStorage.findBooking(Mockito.anyLong())) + .thenReturn(Optional.of(bookingEntity)); + + var expectedBookingDto = bookingEntity.toDto() + .toBuilder() + .status(BookingStatus.APPROVED) + .build(); + + var actualBooking = bookingService.setBookingStatus(1L, true, 4L); + + Assertions.assertEquals(expectedBookingDto, actualBooking); + + Mockito.verify(bookingStorage, Mockito.times(1)) + .updateBooking(bookingEntity); + } + + @Test + public void setBookingStatusRejectTest() { + var ownerEntity = new UserEntity(); + ownerEntity.setId(4L); + + var bookerEntity = new UserEntity(); + bookerEntity.setId(7L); + + var itemEntity = new ItemEntity(); + itemEntity.setAvailable(true); + itemEntity.setId(3L); + itemEntity.setOwner(ownerEntity); + + var bookingEntity = new BookingEntity(); + bookingEntity.setId(5L); + bookingEntity.setBooker(bookerEntity); + bookingEntity.setItem(itemEntity); + bookingEntity.setStart(Timestamp.valueOf(LocalDateTime.of(2024,12,03,0,0))); + bookingEntity.setEnd(Timestamp.valueOf(LocalDateTime.of(2024,12,04,0,0))); + bookingEntity.setStatus(BookingStatus.WAITING); + + Mockito.when(userStorage.existsById(Mockito.anyLong())) + .thenReturn(true); + Mockito.when(bookingStorage.findBooking(Mockito.anyLong())) + .thenReturn(Optional.of(bookingEntity)); + + var expectedBookingDto = bookingEntity.toDto() + .toBuilder() + .status(BookingStatus.REJECTED) + .build(); + + var actualBooking = bookingService.setBookingStatus(1L, false, 4L); + + Assertions.assertEquals(expectedBookingDto, actualBooking); + + Mockito.verify(bookingStorage, Mockito.times(1)) + .updateBooking(bookingEntity); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingStorageInDbTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingStorageInDbTest.java new file mode 100644 index 0000000..0eff88a --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingStorageInDbTest.java @@ -0,0 +1,127 @@ +package ru.practicum.shareit.booking; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.ComponentScan; +import ru.practicum.shareit.booking.storage.BookingEntity; +import ru.practicum.shareit.booking.storage.BookingStorageInDb; +import ru.practicum.shareit.item.ItemStorage; +import ru.practicum.shareit.item.storage.ItemEntity; +import ru.practicum.shareit.user.UserStorage; +import ru.practicum.shareit.user.storage.UserEntity; + +import java.sql.Timestamp; +import java.time.Instant; + +@DataJpaTest(properties = { + "spring.datasource.url=jdbc:h2:mem:testdb", + "spring.jpa.hibernate.ddl-auto=create-drop", + // "driverClassName=org.h2.Driver" + }, +showSql = false) +@ComponentScan(basePackages = "ru.practicum.shareit") +public class BookingStorageInDbTest { + @Autowired + BookingStorageInDb bookingStorageInDb; + @Autowired + UserStorage userStorage; + @Autowired + ItemStorage itemStorage; + + @Test + public void findBookingAndNotResult() { + var userEntity = new UserEntity(); + userEntity.setName("test"); + userEntity.setEmail("aa@BB.cc"); + userStorage.updateUser(userEntity); + + var itemEntity = new ItemEntity(); + itemEntity.setName("test"); + itemEntity.setOwner(userEntity); + + itemStorage.updateItem(itemEntity); + + var maxId = 0L; + + for (int i = 0; i < 10; i++) { + var entity = new BookingEntity(); + entity.setStatus(BookingStatus.APPROVED); + entity.setBooker(userEntity); + entity.setItem(itemEntity); + entity.setStart(Timestamp.from(Instant.now())); + entity.setEnd(Timestamp.from(Instant.now())); + + bookingStorageInDb.updateBooking(entity); + + var id = entity.getId(); + if (maxId < id) maxId = id; + } + + var actualBookingEntity = bookingStorageInDb.findBooking(maxId + 1L); + Assertions.assertTrue(actualBookingEntity.isEmpty()); + } + + @Test + public void findBooking() { + var userEntity = new UserEntity(); + userEntity.setName("test"); + userEntity.setEmail("aa@BB.cc"); + userStorage.updateUser(userEntity); + + var itemEntity = new ItemEntity(); + itemEntity.setName("test"); + itemEntity.setOwner(userEntity); + + itemStorage.updateItem(itemEntity); + + var entity = new BookingEntity(); + entity.setStatus(BookingStatus.APPROVED); + entity.setBooker(userEntity); + entity.setItem(itemEntity); + entity.setStart(Timestamp.from(Instant.now())); + entity.setEnd(Timestamp.from(Instant.now())); + + bookingStorageInDb.updateBooking(entity); + + var actualBookingEntity = bookingStorageInDb.findBooking(entity.getId()); + Assertions.assertTrue(actualBookingEntity.isPresent()); + Assertions.assertEquals(entity, actualBookingEntity.get()); + } + + @Test + public void updateBooking() { + var userEntity = new UserEntity(); + userEntity.setName("test"); + userEntity.setEmail("aa@BB.cc"); + userStorage.updateUser(userEntity); + + var itemEntity = new ItemEntity(); + itemEntity.setName("test"); + itemEntity.setOwner(userEntity); + + itemStorage.updateItem(itemEntity); + + var entity = new BookingEntity(); + entity.setStatus(BookingStatus.APPROVED); + entity.setBooker(userEntity); + entity.setItem(itemEntity); + + entity.setStart(Timestamp.from(Instant.now())); + entity.setEnd(Timestamp.from(Instant.now())); + + var otherUserEntity = new UserEntity(); + otherUserEntity.setName("test2"); + otherUserEntity.setEmail("aa2@BB.cc"); + userStorage.updateUser(otherUserEntity); + + bookingStorageInDb.updateBooking(entity); //Создание записи + + entity.setBooker(otherUserEntity); + + bookingStorageInDb.updateBooking(entity); + + Assertions.assertEquals(otherUserEntity, entity.getBooker()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java new file mode 100644 index 0000000..6280811 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/ItemControllerTest.java @@ -0,0 +1,333 @@ +package ru.practicum.shareit.item; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.ItemToUpdateDto; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +public class ItemControllerTest { + @Mock + ItemService itemService; + + @InjectMocks + ItemController itemController; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void requestPathWithoutUserIdInHead() throws Exception { + List needIdUserFromHeadRequest = + List.of(post("/items"), + post("/items/1/comment"), + put("/items"), + patch("/items/1"), + delete("/items/1")); + + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + for (var requestBuilder: needIdUserFromHeadRequest) { + var result = mockMvc.perform(requestBuilder) + .andReturn(); + + var status = result + .getResponse() + .getStatus(); + + var path = result.getRequest().getMethod() + " '" + result.getRequest().getRequestURI() + "'"; + + Assertions.assertEquals(400, status, path + " was needed userid as RequestHeader \"X-Sharer-User-Id\""); + } + } + + @Test + public void getItemTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + var dto = ItemDto.builder() + .id(10L) + .build(); + + Mockito.when(itemService.findItem(Mockito.anyLong())) + .thenReturn(dto); + + mockMvc.perform(get("/items/1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(10L), Long.class)); + } + + @Test + void getItemParsingRequestId() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + mockMvc.perform(get("/items/100")) + .andExpect(status().isOk()); + + Mockito.verify(itemService, Mockito.times(1)) + .findItem(100); + } + + @Test + void getItemsForUserIdTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + var expectedResult = List.of( + ItemDto.builder().id(11L).build(), + ItemDto.builder().id(42L).build(), + ItemDto.builder().id(37L).build()); + + Mockito.when(itemService.getItems(Mockito.anyLong())) + .thenReturn(expectedResult); + + mockMvc.perform(get("/items") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.[0].id",is(11L), Long.class)) + .andExpect(jsonPath("$.[1].id",is(42L), Long.class)) + .andExpect(jsonPath("$.[2].id",is(37L), Long.class)); + } + + @Test + void getItemsForUserIdParsingRequestTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + mockMvc.perform(get("/items") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()); + + Mockito.verify(itemService, Mockito.times(1)) + .getItems(10); + } + + @Test + void findItemsByTextTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + var expectedResult = List.of( + ItemDto.builder().id(1L).build(), + ItemDto.builder().id(42L).build(), + ItemDto.builder().id(37L).build()); + + Mockito.when(itemService.findFreeItemsByText(Mockito.anyString(), Mockito.anyBoolean())) + .thenReturn(expectedResult); + + mockMvc.perform(get("/items/search?text=abc")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.[0].id",is(1L), Long.class)) + .andExpect(jsonPath("$.[1].id",is(42L), Long.class)) + .andExpect(jsonPath("$.[2].id",is(37L), Long.class)); + + Mockito.verify(itemService, Mockito.times(1)) + .findFreeItemsByText("abc", true); + + Mockito.verify(itemService, Mockito.never()) + .findFreeItemsByText("abc", false); + } + + @Test + void findItemsByTextFailParsingRequestTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + mockMvc.perform(get("/items/search")) + .andExpect(status().is(400)); + } + + @Test + void postItemTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + Mockito.when(itemService.createItem(Mockito.any(ItemDto.class), Mockito.anyLong())) + .thenReturn(ItemDto.builder().id(50L).build()); + + var dto = ItemDto.builder() + .id(13L) + .name("la") + .available(true) + .description("ma") + .build(); + + mockMvc.perform(post("/items") + .header("X-Sharer-User-Id", 10L) + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(50L), Long.class)); + + Mockito.verify(itemService, Mockito.times(1)) + .createItem(dto, 10L); + } + + @Test + void postItemWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + mockMvc.perform(post("/items") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().is(400)); + } + + @Test + void postCommentTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + var dto = CommentDto.builder() + .id(47L) + .text("la") + .build(); + + Mockito.when(itemService.addComment(Mockito.any(CommentDto.class), Mockito.anyLong(), Mockito.anyLong())) + .thenReturn(CommentDto.builder().id(50L).build()); + + mockMvc.perform(post("/items/13/comment") + .header("X-Sharer-User-Id", 10L) + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(50L), Long.class)); + + Mockito.verify(itemService, Mockito.times(1)) + .addComment(dto, 13L, 10L); + } + + @Test + void postCommentWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + mockMvc.perform(post("/items/13/comment") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().is(400)); + } + + @Test + void putItemTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + Mockito.when(itemService.updateItem(Mockito.any(ItemDto.class), Mockito.anyLong())) + .thenReturn(ItemDto.builder().id(50L).build()); + + var dto = ItemDto.builder() + .id(13L) + .name("la") + .description("ma") + .available(true) + .build(); + + mockMvc.perform(put("/items") + .header("X-Sharer-User-Id", 10L) + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(50L), Long.class)); + + Mockito.verify(itemService, Mockito.times(1)) + .updateItem(dto, 10L); + } + + @Test + void putItemWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + mockMvc.perform(put("/items") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().is(400)); + } + + @Test + void patchItemTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + var dto = ItemToUpdateDto.builder() + .build(); + + Mockito.when(itemService.updateItem(Mockito.anyLong(), Mockito.any(ItemToUpdateDto.class), Mockito.anyLong())) + .thenReturn(ItemDto.builder().id(50L).build()); + + mockMvc.perform(patch("/items/17") + .header("X-Sharer-User-Id", 10L) + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(50L), Long.class)); + + Mockito.verify(itemService, Mockito.times(1)) + .updateItem(17, dto, 10L); + } + + @Test + void patchItemWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + mockMvc.perform(put("/items") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().is(400)); + } + + @Test + void deleteItemTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(itemController) + .build(); + + Mockito.when(itemService.deleteItem(17L, 10L)) + .thenReturn(ItemDto.builder().id(50L).build()); + + mockMvc.perform(delete("/items/17") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(50L), Long.class)); + + Mockito.verify(itemService, Mockito.times(1)) + .deleteItem(17L, 10L); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemServiceTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemServiceTest.java new file mode 100644 index 0000000..16d9f54 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/ItemServiceTest.java @@ -0,0 +1,769 @@ +package ru.practicum.shareit.item; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import ru.practicum.shareit.booking.BookingStorage; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.exceptions.NotValidException; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.ItemToUpdateDto; +import ru.practicum.shareit.item.storage.ItemEntity; +import ru.practicum.shareit.request.RequestStorage; +import ru.practicum.shareit.request.storage.RequestEntity; +import ru.practicum.shareit.user.UserStorage; +import ru.practicum.shareit.user.storage.UserEntity; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +@SpringBootTest +public class ItemServiceTest { + @Mock + UserStorage userStorage; + @Mock + ItemStorage itemStorage; + @Mock + RequestStorage requestStorage; + @Mock + BookingStorage bookingStorage; + + @InjectMocks + ItemService itemService; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void findItemIdWhileNotFoundUserTest() { + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + itemService.findItem(10L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Вещь не найдена", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Вещь не найдена\")"); + } + + @Test + public void findItemIdTest() { + var itemEntity = new ItemEntity(); + itemEntity.setId(54L);; + + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.of(itemEntity)); + + var actualItemDto = itemService.findItem(4L); + + var expectedItemDto = itemEntity.toDto(); + + Assertions.assertEquals(expectedItemDto, actualItemDto); + + Mockito.verify(itemStorage, Mockito.times(1)) + .findItem(4L); + } + + @Test + public void createItemWhileNotFoundUserIdTest() { + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + itemService.createItem(ItemDto.builder().build(), 14L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Пользователь не найден", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Пользователь не найден\")"); + } + + @Test + public void createItemWhileNotFoundRequestIdTest() { + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(new UserEntity())); + + Mockito.when(requestStorage.findRequestById(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + itemService.createItem(ItemDto.builder().requestId(1L).build(), 14L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Запрос не найден", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Запрос не найден\")"); + } + + @Test + public void createItemWhileRequestIdIsNullTest() { + var userEntity = new UserEntity(); + userEntity.setId(10L); + + RequestEntity requestEntity = null; + + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(userEntity)); + + Mockito.when(requestStorage.findRequestById(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + var itemDto = ItemDto.builder() + .id(14L) + .available(true) + .build(); + + var itemEntity = ItemMapper.toEntity(itemDto, userEntity, null); + + var actualItemDto = itemService.createItem(itemDto, 16L); + + var expectedItemDto = itemDto.toBuilder() + .comments(List.of()) + .build(); + + Assertions.assertEquals(expectedItemDto, actualItemDto); + + Mockito.verify(userStorage, Mockito.times(1)) + .findUserId(16L); + + Mockito.verify(requestStorage, Mockito.never()) + .findRequestById(Mockito.anyLong()); + + Mockito.verify(itemStorage, Mockito.times(1)) + .updateItem(itemEntity); + } + + @Test + public void createItemTest() { + var userEntity = new UserEntity(); + userEntity.setId(10L); + + RequestEntity requestEntity = new RequestEntity(); + requestEntity.setId(17L); + + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(userEntity)); + + Mockito.when(requestStorage.findRequestById(Mockito.anyLong())) + .thenReturn(Optional.of(requestEntity)); + + var itemDto = ItemDto.builder() + .id(14L) + .available(true) + .requestId(17L) + .build(); + + var itemEntity = ItemMapper.toEntity(itemDto, userEntity, requestEntity); + + var actualItemDto = itemService.createItem(itemDto, 16L); + + var expectedItemDto = itemDto.toBuilder() + .comments(List.of()) + .build(); + + Assertions.assertEquals(expectedItemDto, actualItemDto); + + Mockito.verify(userStorage, Mockito.times(1)) + .findUserId(16L); + + Mockito.verify(requestStorage, Mockito.times(1)) + .findRequestById(17L); + + Mockito.verify(itemStorage, Mockito.times(1)) + .updateItem(itemEntity); + } + + @Test + public void updateItemWhileNotFoundItemIdTest() { + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + itemService.updateItem(ItemDto.builder().id(2L).build(), 14L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Вещь не найдена", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Вещь не найдена\")"); + } + + @Test + public void updateItemWhileUserIdIsNotOwnerTest() { + var ownerId = new UserEntity(); + ownerId.setId(11L); + + var itemEntity = new ItemEntity(); + itemEntity.setId(13L); + itemEntity.setOwner(ownerId); + + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.of(itemEntity)); + + try { + itemService.updateItem(ItemDto.builder().id(10L).build(), 14L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Владелец не найден", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Владелец не найден\")"); + } + + @Test + public void updateItemWhileNotFoundRequestIdTest() { + var ownerId = new UserEntity(); + ownerId.setId(11L); + + var itemEntity = new ItemEntity(); + itemEntity.setId(13L); + itemEntity.setOwner(ownerId); + + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.of(itemEntity)); + + try { + itemService.updateItem(ItemDto.builder().id(1L).requestId(1L).build(), 11L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Запрос не найден", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Запрос не найден\")"); + } + + @Test + public void updateItemWhileRequestIdIsNullTest() { + var ownerId = new UserEntity(); + ownerId.setId(11L); + + var itemEntity = new ItemEntity(); + itemEntity.setId(13L); + itemEntity.setOwner(ownerId); + itemEntity.setAvailable(true); + + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.of(itemEntity)); + + Mockito.when(requestStorage.findRequestById(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + var itemDto = ItemDto.builder() + .id(13L) + .available(true) + .build(); + + var actualItemDto = itemService.updateItem(itemDto, 11L); + + var expectedItemDto = ItemMapper.toDto(itemEntity); + + Assertions.assertEquals(expectedItemDto, actualItemDto); + + Mockito.verify(requestStorage, Mockito.never()) + .findRequestById(Mockito.anyLong()); + + Mockito.verify(itemStorage, Mockito.times(1)) + .updateItem(itemEntity); + } + + @Test + public void updateItemItemTest() { + RequestEntity requestEntity = new RequestEntity(); + requestEntity.setId(17L); + + Mockito.when(requestStorage.findRequestById(Mockito.anyLong())) + .thenReturn(Optional.of(requestEntity)); + var ownerId = new UserEntity(); + ownerId.setId(11L); + + var itemEntity = new ItemEntity(); + itemEntity.setId(13L); + itemEntity.setOwner(ownerId); + itemEntity.setAvailable(true); + itemEntity.setRequest(requestEntity); + + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.of(itemEntity)); + + var itemDto = ItemDto.builder() + .id(13L) + .available(true) + .requestId(17L) + .build(); + + var actualItemDto = itemService.updateItem(itemDto, 11L); + + var expectedItemDto = ItemMapper.toDto(itemEntity); + + Assertions.assertEquals(expectedItemDto, actualItemDto); + + Mockito.verify(requestStorage, Mockito.times(1)) + .findRequestById(17L); + + Mockito.verify(itemStorage, Mockito.times(1)) + .updateItem(itemEntity); + } + + @Test + public void updateItemForRequestWhileNotFoundItemIdTest() { + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + itemService.updateItem(1L, ItemToUpdateDto.builder().build(), 2L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Вещь не найдена", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Вещь не найдена\")"); + } + + @Test + public void updateItemForRequestWhileUserIdIsNotEqualOwnerTest() { + var ownerEntity = new UserEntity(); + ownerEntity.setId(11L); + + var itemEntity = new ItemEntity(); + itemEntity.setId(12L); + itemEntity.setOwner(ownerEntity); + + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.of(itemEntity)); + + try { + itemService.updateItem(1L, ItemToUpdateDto.builder().build(), 2L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Вещь не доступна", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Вещь не доступна\")"); + } + + @Test + public void updateItemForRequestChangeNameTest() { + var ownerEntity = new UserEntity(); + ownerEntity.setId(11L); + + var itemEntity = new ItemEntity(); + itemEntity.setId(12L); + itemEntity.setOwner(ownerEntity); + itemEntity.setName("test"); + itemEntity.setDescription("test"); + itemEntity.setAvailable(false); + + var expectedItemDto = ItemMapper.toDto(itemEntity).toBuilder() + .name("updated") + .build(); + + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.of(itemEntity)); + + var actualItemDto = itemService.updateItem( + 1L, + ItemToUpdateDto.builder().name("updated").build(), + 11L); + + Assertions.assertEquals(expectedItemDto, actualItemDto); + + itemEntity = new ItemEntity(); + itemEntity.setId(12L); + itemEntity.setOwner(ownerEntity); + itemEntity.setName("updated"); + itemEntity.setDescription("test"); + itemEntity.setAvailable(false); + + Mockito.verify(itemStorage, Mockito.times(1)) + .updateItem(itemEntity); + } + + @Test + public void updateItemForRequestChangeDescriptionTest() { + var ownerEntity = new UserEntity(); + ownerEntity.setId(11L); + + var itemEntity = new ItemEntity(); + itemEntity.setId(12L); + itemEntity.setOwner(ownerEntity); + itemEntity.setName("test"); + itemEntity.setDescription("test"); + itemEntity.setAvailable(false); + + var expectedItemDto = ItemMapper.toDto(itemEntity).toBuilder() + .description("updated") + .build(); + + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.of(itemEntity)); + + var actualItemDto = itemService.updateItem( + 1L, + ItemToUpdateDto.builder().description("updated").build(), + 11L); + + Assertions.assertEquals(expectedItemDto, actualItemDto); + + itemEntity = new ItemEntity(); + itemEntity.setId(12L); + itemEntity.setOwner(ownerEntity); + itemEntity.setName("test"); + itemEntity.setDescription("updated"); + itemEntity.setAvailable(false); + + Mockito.verify(itemStorage, Mockito.times(1)) + .updateItem(itemEntity); + } + + @Test + public void updateItemForRequestChangeSetAvailableTrueTest() { + var ownerEntity = new UserEntity(); + ownerEntity.setId(11L); + + var itemEntity = new ItemEntity(); + itemEntity.setId(12L); + itemEntity.setOwner(ownerEntity); + itemEntity.setName("test"); + itemEntity.setDescription("test"); + itemEntity.setAvailable(false); + + var expectedItemDto = ItemMapper.toDto(itemEntity).toBuilder() + .available(true) + .build(); + + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.of(itemEntity)); + + var actualItemDto = itemService.updateItem( + 1L, + ItemToUpdateDto.builder().available(true).build(), + 11L); + + Assertions.assertEquals(expectedItemDto, actualItemDto); + + itemEntity = new ItemEntity(); + itemEntity.setId(12L); + itemEntity.setOwner(ownerEntity); + itemEntity.setName("test"); + itemEntity.setDescription("test"); + itemEntity.setAvailable(true); + + Mockito.verify(itemStorage, Mockito.times(1)) + .updateItem(itemEntity); + } + + @Test + public void updateItemForRequestChangeSetAvailableFalseTest() { + var ownerEntity = new UserEntity(); + ownerEntity.setId(11L); + + var itemEntity = new ItemEntity(); + itemEntity.setId(12L); + itemEntity.setOwner(ownerEntity); + itemEntity.setName("test"); + itemEntity.setDescription("test"); + itemEntity.setAvailable(true); + + var expectedItemDto = ItemMapper.toDto(itemEntity).toBuilder() + .available(false) + .build(); + + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.of(itemEntity)); + + var actualItemDto = itemService.updateItem( + 1L, + ItemToUpdateDto.builder().available(false).build(), + 11L); + + Assertions.assertEquals(expectedItemDto, actualItemDto); + + itemEntity = new ItemEntity(); + itemEntity.setId(12L); + itemEntity.setOwner(ownerEntity); + itemEntity.setName("test"); + itemEntity.setDescription("test"); + itemEntity.setAvailable(false); + + Mockito.verify(itemStorage, Mockito.times(1)) + .updateItem(itemEntity); + } + + @Test + public void deleteItemWhileNotFoundItemIdTest() { + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + itemService.deleteItem(1L, 2L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Вещь не найдена", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Вещь не найдена\")"); + } + + @Test + public void deleteItemWhileUserIdIsNotEqualOwnerTest() { + var ownerEntity = new UserEntity(); + ownerEntity.setId(11L); + + var itemEntity = new ItemEntity(); + itemEntity.setId(12L); + itemEntity.setOwner(ownerEntity); + + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.of(itemEntity)); + + try { + itemService.deleteItem(1L, 2L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Вещь не доступна", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Вещь не доступна\")"); + } + + @Test + public void deleteItemTest() { + var ownerEntity = new UserEntity(); + ownerEntity.setId(11L); + + var itemEntity = new ItemEntity(); + itemEntity.setId(12L); + itemEntity.setOwner(ownerEntity); + + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.of(itemEntity)); + + var expectedItemDto = ItemMapper.toDto(itemEntity); + + var actualItemDto = itemService.deleteItem(1L, 11L); + + Assertions.assertEquals(expectedItemDto, actualItemDto); + + Mockito.verify(itemStorage, Mockito.times(1)) + .deleteItem(1L); + + Mockito.verify(itemStorage, Mockito.times(1)) + .findItem(1L); + } + + @Test + public void getItemsWhileNotFoundUserIdTest() { + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + itemService.getItems(2L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Пользователь не найден", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Пользователь не найден\")"); + } + + @Test + public void getItemsTest() { + var ownerEntity = new UserEntity(); + ownerEntity.setId(1L); + + var item1 = new ItemEntity(); + item1.setId(11L); + item1.setOwner(ownerEntity); + + var item2 = new ItemEntity(); + item2.setId(12L); + item2.setOwner(ownerEntity); + + ownerEntity.setItems(List.of(item1, item2)); + + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(ownerEntity)); + + var expectedList = Stream.of(item1, item2) + .map(ItemMapper::toDto) + .toList(); + + var actualList = itemService.getItems(14L); + + Assertions.assertEquals(expectedList, actualList); + + Mockito.verify(userStorage, Mockito.times(1)) + .findUserId(14L); + } + + @Test + public void findFreeItemsByTextWhileNullTextTest() { + var item1 = new ItemEntity(); + item1.setId(11L); + + var item2 = new ItemEntity(); + item2.setId(12L); + + Mockito.when(itemStorage.findItemsByTextAndStatus(Mockito.anyString(), Mockito.anyBoolean())) + .thenReturn(List.of(item1, item2)); + + Assertions.assertEquals(0, itemService.findFreeItemsByText(null, true).size()); + + Mockito.verify(itemStorage, Mockito.never()) + .findItemsByTextAndStatus(Mockito.anyString(), Mockito.anyBoolean()); + } + + @Test + public void findFreeItemsByTextWhileBlankTextTest() { + var item1 = new ItemEntity(); + item1.setId(11L); + + var item2 = new ItemEntity(); + item2.setId(12L); + + Mockito.when(itemStorage.findItemsByTextAndStatus(Mockito.anyString(), Mockito.anyBoolean())) + .thenReturn(List.of(item1, item2)); + + Assertions.assertEquals(0, itemService.findFreeItemsByText("", true).size()); + + Mockito.verify(itemStorage, Mockito.never()) + .findItemsByTextAndStatus(Mockito.anyString(), Mockito.anyBoolean()); + } + + @Test + public void findFreeItemsByTextWhileAvailableIsTrueTest() { + var item1 = new ItemEntity(); + item1.setId(11L); + + var item2 = new ItemEntity(); + item2.setId(12L); + + Mockito.when(itemStorage.findItemsByTextAndStatus(Mockito.anyString(), Mockito.anyBoolean())) + .thenReturn(List.of(item1, item2)); + + itemService.findFreeItemsByText("a", true); + + Mockito.verify(itemStorage, Mockito.never()) + .findItemsByTextAndStatus("a", false); + + Mockito.verify(itemStorage, Mockito.times(1)) + .findItemsByTextAndStatus("a", true); + } + + @Test + public void findFreeItemsByTextWhileAvailableIsFalseTest() { + var item1 = new ItemEntity(); + item1.setId(11L); + + var item2 = new ItemEntity(); + item2.setId(12L); + + Mockito.when(itemStorage.findItemsByTextAndStatus(Mockito.anyString(), Mockito.anyBoolean())) + .thenReturn(List.of(item1, item2)); + + itemService.findFreeItemsByText("a", false); + + Mockito.verify(itemStorage, Mockito.never()) + .findItemsByTextAndStatus("a", true); + + Mockito.verify(itemStorage, Mockito.times(1)) + .findItemsByTextAndStatus("a", false); + } + + @Test + public void findFreeItemsByTextTest() { + var item1 = new ItemEntity(); + item1.setId(11L); + + var item2 = new ItemEntity(); + item2.setId(12L); + + Mockito.when(itemStorage.findItemsByTextAndStatus(Mockito.anyString(), Mockito.anyBoolean())) + .thenReturn(List.of(item1, item2)); + + var expectedList = Stream.of(item1, item2) + .map(ItemMapper::toDto) + .toList(); + + var actualList = itemService.findFreeItemsByText("a", false); + + Assertions.assertEquals(expectedList, actualList); + } + + @Test + public void addCommentWhileNotFoundUserIdTest() { + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + itemService.addComment( + CommentDto.builder().build(), + 1L, + 2L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Пользователь не найден", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Пользователь не найден\")"); + } + + @Test + public void addCommentWhileNotFoundItemIdTest() { + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(new UserEntity())); + + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + itemService.addComment( + CommentDto.builder().build(), + 1L, + 2L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Вешь не найдена", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Вешь не найдена\")"); + } + + @Test + public void addCommentWhileUserNotUsedItemTest() { + var userEntity = new UserEntity(); + userEntity.setId(11L); + + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(userEntity)); + + Mockito.when(itemStorage.findItem(Mockito.anyLong())) + .thenReturn(Optional.of(new ItemEntity())); + + Mockito.when( + bookingStorage.existsByBookerIdAndItemIdAndAfterEnd(Mockito.anyLong(), Mockito.anyLong())) + .thenReturn(false); + + try { + itemService.addComment( + CommentDto.builder().build(), + 1L, + 2L); + } catch (NotValidException ex) { + Assertions.assertEquals("Пользователь не брал вещь, не может оставить комментарий", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotValidException(\"Пользователь не брал вещь, не может оставить комментарий\")"); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/request/RequestControllerTest.java b/server/src/test/java/ru/practicum/shareit/request/RequestControllerTest.java new file mode 100644 index 0000000..4e5abc1 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/RequestControllerTest.java @@ -0,0 +1,165 @@ +package ru.practicum.shareit.request; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import ru.practicum.shareit.request.dto.RequestDto; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +public class RequestControllerTest { + @Mock + RequestService requestService; + + @InjectMocks + RequestController requestController; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void requestPathWithoutUserIdInHead() throws Exception { + List needIdUserFromHeadRequest = + List.of(get("/requests"), + get("/requests/all"), + post("/requests")); + + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(requestController) + .build(); + + for (var requestBuilder: needIdUserFromHeadRequest) { + var result = mockMvc.perform(requestBuilder) + .andReturn(); + + var status = result + .getResponse() + .getStatus(); + + var path = result.getRequest().getMethod() + " '" + result.getRequest().getRequestURI() + "'"; + + Assertions.assertEquals(400, status, path + " was needed userid as RequestHeader \"X-Sharer-User-Id\""); + } + } + + @Test + public void getSelfRequestsTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(requestController) + .build(); + + List expectedRequests = List.of( + RequestDto.builder().id(13L).build(), + RequestDto.builder().id(24L).build(), + RequestDto.builder().id(37L).build() + ); + + Mockito.when(requestService.findRequestsByUserId(Mockito.anyLong(), Mockito.anyBoolean())) + .thenReturn(expectedRequests); + + mockMvc.perform(get("/requests") + .header("X-Sharer-User-Id", 7L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id", is(13L), Long.class)) + .andExpect(jsonPath("$[1].id", is(24L), Long.class)) + .andExpect(jsonPath("$[2].id", is(37L), Long.class)); + + Mockito.verify(requestService, Mockito.times(1)) + .findRequestsByUserId(7L, true); + } + + @Test + void getRequestByIdTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(requestController) + .build(); + + var expectedResult = RequestDto.builder().id(13L).build(); + + Mockito.when(requestService.findRequestById(Mockito.anyLong())) + .thenReturn(expectedResult); + + mockMvc.perform(get("/requests/23")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(13L), Long.class)); + + Mockito.verify(requestService, Mockito.times(1)) + .findRequestById(23L); + } + + @Test + void getOtherUserRequestsTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(requestController) + .build(); + + List expectedRequests = List.of( + RequestDto.builder().id(13L).build(), + RequestDto.builder().id(24L).build(), + RequestDto.builder().id(37L).build() + ); + + Mockito.when(requestService.findRequestsByUserId(Mockito.anyLong(), Mockito.anyBoolean())) + .thenReturn(expectedRequests); + + mockMvc.perform(get("/requests/all") + .header("X-Sharer-User-Id", 7L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id", is(13L), Long.class)) + .andExpect(jsonPath("$[1].id", is(24L), Long.class)) + .andExpect(jsonPath("$[2].id", is(37L), Long.class)); + + Mockito.verify(requestService, Mockito.times(1)) + .findRequestsByUserId(7L, false); + } + + @Test + void postRequestDtoTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(requestController) + .build(); + + Mockito.when(requestService.createRequest(Mockito.any(RequestDto.class), Mockito.anyLong())) + .thenReturn(RequestDto.builder().id(50L).build()); + + var dto = RequestDto.builder() + .id(13L) + .description("la") + .build(); + + mockMvc.perform(post("/requests") + .header("X-Sharer-User-Id", 10L) + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(50L), Long.class)); + + Mockito.verify(requestService, Mockito.times(1)) + .createRequest(dto, 10L); + } + + @Test + void postRequestWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(requestController) + .build(); + + mockMvc.perform(post("/requests") + .header("X-Sharer-User-Id", 10L)) + .andExpect(status().is(400)); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/request/RequestServiceTest.java b/server/src/test/java/ru/practicum/shareit/request/RequestServiceTest.java new file mode 100644 index 0000000..94aa014 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/RequestServiceTest.java @@ -0,0 +1,203 @@ +package ru.practicum.shareit.request; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.request.dto.RequestDto; +import ru.practicum.shareit.request.storage.RequestEntity; +import ru.practicum.shareit.user.UserStorage; +import ru.practicum.shareit.user.storage.UserEntity; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.stream.Stream; + +@SpringBootTest +public class RequestServiceTest { + @Mock + UserStorage userStorage; + @Mock + RequestStorage requestStorage; + + @InjectMocks + RequestService requestService; + + @Test + public void createRequestWhileNotFoundUserTest() { + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + requestService.createRequest(RequestDto.builder().build(), 1L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Пользователь не найден", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Пользователь не найден\")"); + } + + @Test + public void createRequestTest() { + var userEntity = new UserEntity(); + userEntity.setId(2L); + + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(userEntity)); + + var requestEntity = new RequestEntity(); + requestEntity.setId(3L); + + var requestDto = RequestDto.builder() + .id(3L) + .build(); + + var createdDate = Timestamp.valueOf(LocalDateTime.now().minusMinutes(1)); + + var actualRequestDto = requestService.createRequest(requestDto, 7L); + + requestEntity.setCreated(createdDate); + requestEntity.setUser(userEntity); + + var expectedRequestDto = requestEntity.toDto(); + + Assertions.assertTrue(createdDate.before(actualRequestDto.created())); + + expectedRequestDto = expectedRequestDto.toBuilder() + .created(actualRequestDto.created()) + .build(); + + Assertions.assertEquals(expectedRequestDto, actualRequestDto); + + Mockito.verify(userStorage, Mockito.times(1)) + .findUserId(7L); + + requestEntity.setCreated(actualRequestDto.created()); + + Mockito.verify(requestStorage, Mockito.times(1)) + .updateRequest(requestEntity); + } + + @Test + public void findRequestsByUserIdWhileNotFoundUserTest() { + Mockito.when(userStorage.existsById(Mockito.anyLong())) + .thenReturn(false); + + try { + requestService.findRequestsByUserId(1L, true); + } catch (NotFoundException ex) { + Assertions.assertEquals("Пользователь не найден", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Пользователь не найден\")"); + } + + @Test + public void findRequestsByUserIdTest() { + Mockito.when(userStorage.existsById(Mockito.anyLong())) + .thenReturn(true); + + var userEntity = new UserEntity(); + userEntity.setId(1L); + + var requestEntities = Stream.of( + RequestDto.builder().id(2L).build(), + RequestDto.builder().id(5L).build(), + RequestDto.builder().id(7L).build() + ) + .map(i -> RequestMapper.toEntity(i, userEntity)) + .toList(); + + Mockito.when(requestStorage.findRequestsByUserId(Mockito.anyLong())) + .thenReturn(requestEntities); + + var actualRequestDtos = requestService.findRequestsByUserId(11L, true); + + var expectedRequestDtos = requestEntities.stream().map(RequestEntity::toDto).toList(); + + Assertions.assertEquals(expectedRequestDtos, actualRequestDtos); + + Mockito.verify(userStorage, Mockito.times(1)) + .existsById(11L); + + Mockito.verify(requestStorage, Mockito.times(1)) + .findRequestsByUserId(11L); + + Mockito.verify(requestStorage, Mockito.never()) + .findRequestsByNotUserId(Mockito.anyLong()); + } + + @Test + public void findRequestsByUserIdWhileParamIsFalseTest() { + Mockito.when(userStorage.existsById(Mockito.anyLong())) + .thenReturn(true); + + var userEntity = new UserEntity(); + userEntity.setId(1L); + + var requestEntities = Stream.of( + RequestDto.builder().id(2L).build(), + RequestDto.builder().id(5L).build(), + RequestDto.builder().id(7L).build() + ) + .map(i -> RequestMapper.toEntity(i, userEntity)) + .toList(); + + Mockito.when(requestStorage.findRequestsByNotUserId(Mockito.anyLong())) + .thenReturn(requestEntities); + + var actualRequestDtos = requestService.findRequestsByUserId(13L, false); + + var expectedRequestDtos = requestEntities.stream().map(RequestEntity::toDto).toList(); + + Assertions.assertEquals(expectedRequestDtos, actualRequestDtos); + + Mockito.verify(userStorage, Mockito.never()) + .existsById(Mockito.anyLong()); + + Mockito.verify(requestStorage, Mockito.never()) + .findRequestsByUserId(Mockito.anyLong()); + + Mockito.verify(requestStorage, Mockito.times(1)) + .findRequestsByNotUserId(13L); + } + + @Test + public void getRequestsIdWhileNotFoundUserTest() { + Mockito.when(requestStorage.findRequestById(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + requestService.findRequestById(1L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Запрос не найден", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Запрос не найден\")"); + } + + @Test + public void findRequestByIdTest() { + var requestEntity = new RequestEntity(); + requestEntity.setId(15L); + + Mockito.when(requestStorage.findRequestById(Mockito.anyLong())) + .thenReturn(Optional.of(requestEntity)); + + var actualRequestDto = requestService.findRequestById(11L); + + var expectedRequestDto = requestEntity.toDto(); + + Assertions.assertEquals(expectedRequestDto, actualRequestDto); + + Mockito.verify(requestStorage, Mockito.times(1)) + .findRequestById(11L); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/user/UserControllerTest.java b/server/src/test/java/ru/practicum/shareit/user/UserControllerTest.java new file mode 100644 index 0000000..5276a5b --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/UserControllerTest.java @@ -0,0 +1,173 @@ +package ru.practicum.shareit.user; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.dto.UserToUpdateDto; + +import java.nio.charset.StandardCharsets; + +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +public class UserControllerTest { + @Mock + UserService userService; + + @InjectMocks + UserController userController; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void getUserTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + var dto = UserDto.builder() + .id(47L) + .name("la") + .email("email@mail.ru") + .build(); + + Mockito.when(userService.findUserId(Mockito.anyLong())) + .thenReturn(dto); + + mockMvc.perform(get("/users/17")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(47L), Long.class)); + + Mockito.verify(userService, Mockito.times(1)) + .findUserId(17L); + } + + @Test + void postUserTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + Mockito.when(userService.updateUser(Mockito.any(UserDto.class))) + .thenReturn(UserDto.builder().id(50L).build()); + + var dto = UserDto.builder() + .name("la") + .email("email@mail.ru") + .build(); + + mockMvc.perform(post("/users") + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(50L), Long.class)); + + Mockito.verify(userService, Mockito.times(1)) + .updateUser(dto); + } + + @Test + void postUserWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + mockMvc.perform(post("/users")) + .andExpect(status().is(400)); + } + + @Test + void putUserTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + Mockito.when(userService.updateUser(Mockito.any(UserDto.class))) + .thenReturn(UserDto.builder().id(52L).build()); + + var dto = UserDto.builder() + .id(10L) + .name("la") + .email("email@mail.ru") + .build(); + + mockMvc.perform(put("/users") + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(52L), Long.class)); + + Mockito.verify(userService, Mockito.times(1)) + .updateUser(dto); + } + + @Test + void putUserWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + mockMvc.perform(put("/users")) + .andExpect(status().is(400)); + } + + @Test + void patchUserTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + Mockito.when(userService.updateUser(Mockito.anyLong(), Mockito.any(UserToUpdateDto.class))) + .thenReturn(UserDto.builder().id(542L).build()); + + var dto = UserToUpdateDto.builder().name("la").build(); + + mockMvc.perform(patch("/users/13") + .characterEncoding(StandardCharsets.UTF_8) + .content(objectMapper.writeValueAsBytes(dto)) + .contentType("application/json")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(542L), Long.class)); + + Mockito.verify(userService, Mockito.times(1)) + .updateUser(13, dto); + } + + @Test + void patchUserWithoutBodyTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + mockMvc.perform(patch("/users/13")) + .andExpect(status().is(400)); + } + + @Test + void deleteUserTest() throws Exception { + MockMvc mockMvc = MockMvcBuilders + .standaloneSetup(userController) + .build(); + + Mockito.when(userService.deleteUser(Mockito.anyLong())) + .thenReturn(UserDto.builder().id(54L).build()); + + mockMvc.perform(delete("/users/13")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id",is(54L), Long.class)); + + Mockito.verify(userService, Mockito.times(1)) + .deleteUser(13); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/user/UserServiceTest.java b/server/src/test/java/ru/practicum/shareit/user/UserServiceTest.java new file mode 100644 index 0000000..4819584 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/UserServiceTest.java @@ -0,0 +1,210 @@ +package ru.practicum.shareit.user; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import ru.practicum.shareit.exceptions.NotFoundException; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.dto.UserToUpdateDto; +import ru.practicum.shareit.user.storage.UserEntity; + +import java.util.Optional; + +@SpringBootTest +public class UserServiceTest { + @Mock + UserStorage userStorage; + + @InjectMocks + UserService userService; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void findUserIdWhileNotFoundUserTest() { + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + userService.findUserId(10L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Не нашел userId в системе", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Не нашел userId в системе\")"); + } + + @Test + public void findUserIdTest() { + var userEntity = new UserEntity(); + userEntity.setId(2L); + + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(userEntity)); + + var expectedUserDto = UserMapper.toDto(userEntity); + + var actualUserDto = userService.findUserId(5L); + + Assertions.assertEquals(expectedUserDto, actualUserDto); + + Mockito.verify(userStorage, Mockito.times(1)) + .findUserId(5L); + } + + @Test + public void updateUserTest() { + var userDto = UserDto.builder() + .id(10L) + .name("test") + .build(); + + var actualUserDto = userService.updateUser(userDto); + + Assertions.assertEquals(userDto, actualUserDto); + + Mockito.verify(userStorage, Mockito.times(1)) + .updateUser(UserMapper.toEntity(userDto)); + } + + @Test + public void updateUserByRequestWhileNotFoundUserId() { + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + var userRequestToUpdate = UserToUpdateDto.builder().build(); + + try { + userService.updateUser(10L, userRequestToUpdate); + } catch (NotFoundException ex) { + Assertions.assertEquals("Не нашел userId в системе", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Не нашел userId в системе\")"); + } + + @Test + public void updateUserByRequestChangeName() { + var userDto = UserDto.builder() + .id(10L) + .name("test") + .build(); + + var updateDto = UserToUpdateDto.builder() + .name("updated") + .build(); + + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(UserMapper.toEntity(userDto))); + + var userEntity = UserMapper.toEntity(userDto); + userEntity.setName("updated"); + + var actualUserDto = userService.updateUser(1L, updateDto); + + Assertions.assertEquals("updated", actualUserDto.name()); + + Mockito.verify(userStorage, Mockito.times(1)) + .findUserId(1L); + + Mockito.verify(userStorage, Mockito.times(1)) + .updateUser(userEntity); + } + + @Test + public void updateUserByRequestChangeEmail() { + var userDto = UserDto.builder() + .id(10L) + .email("test@aa.bb") + .build(); + + var updateDto = UserToUpdateDto.builder() + .email("updated@aa.bb") + .build(); + + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(UserMapper.toEntity(userDto))); + + var userEntity = UserMapper.toEntity(userDto); + userEntity.setEmail("updated@aa.bb"); + + var actualUserDto = userService.updateUser(1L, updateDto); + + Assertions.assertEquals("updated@aa.bb", actualUserDto.email()); + + Mockito.verify(userStorage, Mockito.times(1)) + .findUserId(1L); + + Mockito.verify(userStorage, Mockito.times(1)) + .updateUser(userEntity); + } + + @Test + public void updateUserByRequestNotChanges() { + var userDto = UserDto.builder() + .id(10L) + .name("test") + .email("test@aa.bb") + .build(); + + var updateDto = UserToUpdateDto.builder() + .build(); + + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(UserMapper.toEntity(userDto))); + + var userEntity = UserMapper.toEntity(userDto); + + var actualUserDto = userService.updateUser(1L, updateDto); + + Assertions.assertEquals(userDto, actualUserDto); + + Mockito.verify(userStorage, Mockito.times(1)) + .findUserId(1L); + + Mockito.verify(userStorage, Mockito.times(1)) + .updateUser(userEntity); + } + + @Test + public void deleteUserByRequestWhileNotFoundUserId() { + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.empty()); + + try { + userService.deleteUser(10L); + } catch (NotFoundException ex) { + Assertions.assertEquals("Не нашел userId в системе", ex.getMessage()); + return; + } + + Assertions.fail("Expected NotFoundException(\"Не нашел userId в системе\")"); + } + + @Test + public void deleteUserTest() { + var oldUserEntity = new UserEntity(); + oldUserEntity.setId(17L); + + Mockito.when(userStorage.findUserId(Mockito.anyLong())) + .thenReturn(Optional.of(oldUserEntity)); + + var expectedUserDto = UserMapper.toDto(oldUserEntity); + + var actualUserDto = userService.deleteUser(5L); + + Assertions.assertEquals(expectedUserDto, actualUserDto); + + Mockito.verify(userStorage, Mockito.times(1)) + .findUserId(5L); + + Mockito.verify(userStorage, Mockito.times(1)) + .deleteUser(oldUserEntity); + } +} diff --git a/src/main/java/ru/practicum/shareit/booking/Booking.java b/src/main/java/ru/practicum/shareit/booking/Booking.java deleted file mode 100644 index 2d9c666..0000000 --- a/src/main/java/ru/practicum/shareit/booking/Booking.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.booking; - -/** - * TODO Sprint add-bookings. - */ -public class Booking { -} diff --git a/src/main/java/ru/practicum/shareit/booking/BookingService.java b/src/main/java/ru/practicum/shareit/booking/BookingService.java deleted file mode 100644 index e2bb74b..0000000 --- a/src/main/java/ru/practicum/shareit/booking/BookingService.java +++ /dev/null @@ -1,147 +0,0 @@ -package ru.practicum.shareit.booking; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import ru.practicum.shareit.booking.dto.BookingDto; -import ru.practicum.shareit.booking.dto.BookingStatusRequestDto; -import ru.practicum.shareit.booking.storage.BookingEntity; -import ru.practicum.shareit.exceptions.NotFoundException; -import ru.practicum.shareit.exceptions.NotValidException; -import ru.practicum.shareit.item.ItemMapper; -import ru.practicum.shareit.item.ItemStorage; -import ru.practicum.shareit.user.UserMapper; -import ru.practicum.shareit.user.UserStorage; - -import java.text.ParseException; -import java.util.List; - -@Service -@RequiredArgsConstructor -public class BookingService { - private final ItemStorage itemStorage; - private final UserStorage userStorage; - private final BookingStorage bookingStorage; - private final BookingMapper bookingMapper = new BookingMapper(); - private final UserMapper userMapper = new UserMapper(); - private final ItemMapper itemMapper = new ItemMapper(); - - public BookingDto getBooking(long bookingId, long userId) { - var userEntity = userStorage.getUser(userId) - .orElseThrow(() -> new NotFoundException("Пользователь не найден")); - - var bookingEntity = bookingStorage.getBooking(bookingId) - .orElseThrow(() -> new NotFoundException("Бронь не найдена")); - - if (!bookingStorage.userIdIsBookerOrOwner(bookingEntity, userId)) { - throw new NotValidException("Данные о бронировании может получить заказчик или владелец"); - } - - return bookingMapper.toDto( - bookingEntity, - userMapper.toDto(userEntity), - itemMapper.toDto(bookingEntity.getItem())); - } - - public List getItemsForUserId(long userId) { - return getItemsForUserId(BookingStatusRequestDto.ALL, userId); - } - - public List getItemsForUserId(BookingStatusRequestDto state, long userId) { - var userEntity = userStorage.getUser(userId) - .orElseThrow(() -> new NotFoundException("Пользователь не найден")); - - List bookingEntities = switch (state) { - case ALL, CURRENT, PAST, FUTURE -> bookingStorage.findBookingsByBookerId(userId); - case WAITING -> bookingStorage.findBookingsByBookerId(userId, BookingStatus.WAITING); - case REJECTED -> bookingStorage.findBookingsByBookerId(userId, BookingStatus.REJECTED); - case APPROVED -> bookingStorage.findBookingsByBookerId(userId, BookingStatus.APPROVED); - default -> List.of(); - }; - - return bookingEntities - .stream() - .map(i -> bookingMapper.toDto( - i, - userMapper.toDto(userEntity), - itemMapper.toDto(i.getItem()))) - .toList(); - } - - public List getItemsForItemOwnerId(long userId) { - return getItemsForItemOwnerId(BookingStatusRequestDto.ALL, userId); - } - - public List getItemsForItemOwnerId(BookingStatusRequestDto state, long ownerId) { - if (!userStorage.existsById(ownerId)) { - throw new NotFoundException("Пользователь не найден"); - } - - List bookingEntities = switch (state) { - case ALL, CURRENT, PAST, FUTURE -> bookingStorage.findBookingsByOwnerId(ownerId); - case WAITING -> bookingStorage.findBookingsByOwnerId(ownerId, BookingStatus.WAITING); - case REJECTED -> bookingStorage.findBookingsByOwnerId(ownerId, BookingStatus.REJECTED); - case APPROVED -> bookingStorage.findBookingsByOwnerId(ownerId, BookingStatus.APPROVED); - default -> List.of(); - }; - - return bookingEntities - .stream() - .map(i -> bookingMapper.toDto( - i, - userMapper.toDto(i.getBooker()), - itemMapper.toDto(i.getItem()))) - .toList(); - } - - public BookingDto createBooking(BookingDto bookingDto, long userId) { - var userEntity = userStorage.getUser(userId) - .orElseThrow(() -> new NotFoundException("Пользователь не найден")); - var itemEntity = itemStorage.getItem(bookingDto.itemId()) - .orElseThrow(() -> new NotFoundException("Вещь не найдена")); - - if (!itemEntity.isAvailable()) { - throw new NotValidException("Вещь не доступна к бронированию"); - } - - try { - var bookingEntity = bookingMapper.toEntity(bookingDto, userEntity, itemEntity); - bookingEntity.setStatus(BookingStatus.WAITING); - - bookingStorage.updateBooking(bookingEntity); - - return bookingMapper.toDto( - bookingEntity, - userMapper.toDto(userEntity), - itemMapper.toDto(itemEntity)); - } catch (ParseException e) { - throw new NotValidException("Произошла ошибка чтения данных"); - } - } - - public BookingDto setBookingStatus(long id, boolean approved, long userId) { - if (!userStorage.existsById(userId)) { - throw new NotValidException("Пользователь не найден"); - } - - var bookingEntity = bookingStorage.getBooking(id) - .orElseThrow(() -> new NotFoundException("Бронь не найдена")); - - var itemEntity = bookingEntity.getItem(); - - if (itemEntity.getOwner().getId() != userId) { - throw new NotValidException("Подтвердить бронь может только владелец"); - } - - if (approved) - bookingEntity.setStatus(BookingStatus.APPROVED); - else - bookingEntity.setStatus(BookingStatus.REJECTED); - - bookingStorage.updateBooking(bookingEntity); - - return bookingMapper.toDto( - bookingEntity, - userMapper.toDto(bookingEntity.getBooker()), - itemMapper.toDto(itemEntity)); - } -} diff --git a/src/main/java/ru/practicum/shareit/booking/Status.java b/src/main/java/ru/practicum/shareit/booking/Status.java deleted file mode 100644 index c91c225..0000000 --- a/src/main/java/ru/practicum/shareit/booking/Status.java +++ /dev/null @@ -1,6 +0,0 @@ -package ru.practicum.shareit.booking; - -public enum Status { - Request, - Accept -} diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/ItemRequest.java deleted file mode 100644 index 95d6f23..0000000 --- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.request; - -/** - * TODO Sprint add-item-requests. - */ -public class ItemRequest { -} diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/src/main/java/ru/practicum/shareit/request/ItemRequestController.java deleted file mode 100644 index 064e2e9..0000000 --- a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java +++ /dev/null @@ -1,12 +0,0 @@ -package ru.practicum.shareit.request; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * TODO Sprint add-item-requests. - */ -@RestController -@RequestMapping(path = "/requests") -public class ItemRequestController { -}