diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/.run/ShareItGateway.run.xml b/.run/ShareItGateway.run.xml new file mode 100644 index 0000000..8334725 --- /dev/null +++ b/.run/ShareItGateway.run.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/.run/ShareItServer.run.xml b/.run/ShareItServer.run.xml new file mode 100644 index 0000000..7ac7f15 --- /dev/null +++ b/.run/ShareItServer.run.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/README.md b/README.md index 9fbff3f..5c73228 100644 --- a/README.md +++ b/README.md @@ -1,4 +1 @@ # java-shareit - -### Database map -![SchemaDatabase.png](SchemaDatabase.png) diff --git a/SchemaDatabase.png b/SchemaDatabase.png deleted file mode 100644 index 1f547e7..0000000 Binary files a/SchemaDatabase.png and /dev/null differ diff --git a/compose.yaml b/compose.yaml deleted file mode 100644 index ae1e654..0000000 --- a/compose.yaml +++ /dev/null @@ -1,16 +0,0 @@ -services: - db: - image: postgres:16.1 - ports: - - "5432:5432" - volumes: - - ./volumes/postgres:/var/lib/postgresql/data/ - environment: - - POSTGRES_DB=shareit - - POSTGRES_USER=dbuser - - POSTGRES_PASSWORD=12345 - healthcheck: - test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER - timeout: 5s - interval: 5s - retries: 10 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7f43e6c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,47 @@ +services: + gateway: + build: + context: ./gateway + dockerfile: Dockerfile + image: shareit-gateway + container_name: shareit-gateway + ports: + - "8080:8080" + depends_on: + - server + environment: +# - TZ=Europe/Moscow + - SHAREIT_SERVER_URL=http://server:9090 + + server: + build: + context: ./server + dockerfile: Dockerfile + image: shareit-server + container_name: shareit-server + ports: + - "9090:9090" + depends_on: + - db + environment: +# - TZ=Europe/Moscow + - 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: +# - TZ=Europe/Moscow + - POSTGRES_DB=shareit + - POSTGRES_USER=shareit + - POSTGRES_PASSWORD=shareit + healthcheck: + test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER + timeout: 5s + interval: 5s + retries: 10 + 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..8f94c64 --- /dev/null +++ b/gateway/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + + ru.practicum + shareit + 0.0.1-SNAPSHOT + + + shareit-gateway + + ShareIt Gateway + + + + org.projectlombok + lombok + provided + true + + + org.springframework.boot + spring-boot-starter-test + test + + + 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.springframework.boot + spring-boot-maven-plugin + + + + + \ 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..7b8ca0a --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/ShareItGateway.java @@ -0,0 +1,11 @@ +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/BookingClient.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java new file mode 100644 index 0000000..4ebba2a --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java @@ -0,0 +1,66 @@ +package ru.practicum.shareit.booking; + +import jakarta.validation.ValidationException; +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.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.booking.dto.BookingState; +import ru.practicum.shareit.booking.dto.GBookingDto; +import ru.practicum.shareit.client.BaseClient; + +import java.time.LocalDateTime; +import java.util.Map; + +@Service +public class BookingClient extends BaseClient { + private static final String API_PREFIX = "/bookings"; + + @Autowired + public BookingClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public ResponseEntity getBookings(long userId, BookingState state, Integer from, Integer size) { + Map parameters = Map.of( + "state", state.name(), + "from", from, + "size", size + ); + return get("?state={state}&from={from}&size={size}", userId, parameters); + } + + public ResponseEntity bookItem(long userId, GBookingDto requestDto) { + LocalDateTime start = requestDto.getStart(); + LocalDateTime end = requestDto.getEnd(); + if (start.isAfter(end) || start.isEqual(end)) { + throw new ValidationException("Дата начала бронирования должна быть раньше даты окончания"); + } + return post("", userId, requestDto); + } + + public ResponseEntity getBooking(long userId, Long bookingId) { + return get("/" + bookingId, userId); + } + + public ResponseEntity getBookingsByOwner(long userId, BookingState state, Integer from, Integer size) { + Map parameters = Map.of( + "state", state.name(), + "from", from, + "size", size + ); + return get("/owner?state={state}&from={from}&size={size}", userId, parameters); + } + + public ResponseEntity approveBooking(long userId, long bookingId, boolean approved) { + return patch("/" + bookingId + "?approved=" + approved, userId); + } +} 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..e3b6fea --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -0,0 +1,70 @@ +package ru.practicum.shareit.booking; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.booking.dto.BookingState; +import ru.practicum.shareit.booking.dto.GBookingDto; +import ru.practicum.shareit.util.Marker; + +@Controller +@RequestMapping(path = "/bookings") +@RequiredArgsConstructor +@Slf4j +@Validated +public class BookingController { + private final BookingClient bookingClient; + + @GetMapping + public ResponseEntity getBookingsByBooker( + @RequestHeader("X-Sharer-User-Id") long userId, + @RequestParam(name = "state", defaultValue = "all") String stateParam, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size) { + BookingState state = BookingState.from(stateParam) + .orElseThrow(() -> new IllegalArgumentException("Unknown state: " + stateParam)); + log.info("Get booking with state {}, userId={}, from={}, size={}", stateParam, userId, from, size); + return bookingClient.getBookings(userId, state, from, size); + } + + @GetMapping("/owner") + public ResponseEntity getAllBookingsByOwner( + @RequestHeader("X-Sharer-User-Id") long userId, + @RequestParam(name = "state", defaultValue = "all") String stateParam, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size) { + BookingState state = BookingState.from(stateParam) + .orElseThrow(() -> new IllegalArgumentException("Unknown state: " + stateParam)); + log.info("Get booking by Owner with state {}, userId={}, from={}, size={}", stateParam, userId, from, size); + return bookingClient.getBookingsByOwner(userId, state, from, size); + } + + @GetMapping("/{bookingId}") + public ResponseEntity getBookingById(@RequestHeader("X-Sharer-User-Id") long userId, + @PathVariable Long bookingId) { + log.info("Get booking {}, userId={}", bookingId, userId); + return bookingClient.getBooking(userId, bookingId); + } + + @PostMapping + @Validated(Marker.OnCreate.class) + public ResponseEntity bookItem(@RequestHeader("X-Sharer-User-Id") long userId, + @RequestBody @Valid GBookingDto requestDto) { + log.info("Creating booking {}, userId={}", requestDto.toString(), userId); + return bookingClient.bookItem(userId, requestDto); + } + + @PatchMapping("/{bookingId}") + public ResponseEntity approveBooking(@RequestHeader("X-Sharer-User-Id") long userId, + @PathVariable long bookingId, + @RequestParam boolean approved) { + log.info("Approve booking {}, userId={}", bookingId, userId); + return bookingClient.approveBooking(userId, bookingId, approved); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java new file mode 100644 index 0000000..7bfa164 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingState.java @@ -0,0 +1,27 @@ +package ru.practicum.shareit.booking.dto; + +import java.util.Optional; + +public enum BookingState { + // Все + ALL, + // Текущие + CURRENT, + // Будущие + FUTURE, + // Завершенные + PAST, + // Отклоненные + REJECTED, + // Ожидающие подтверждения + WAITING; + + public static Optional from(String stringState) { + for (BookingState state : values()) { + if (state.name().equalsIgnoreCase(stringState)) { + return Optional.of(state); + } + } + return Optional.empty(); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/GBookingDto.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/GBookingDto.java new file mode 100644 index 0000000..f607999 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/GBookingDto.java @@ -0,0 +1,26 @@ +package ru.practicum.shareit.booking.dto; + +import java.time.LocalDateTime; + +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import ru.practicum.shareit.util.Marker; + +@Data +@AllArgsConstructor +public class GBookingDto { + + @NotNull(groups = Marker.OnCreate.class) + long itemId; + + @FutureOrPresent + @NotNull(groups = Marker.OnCreate.class) + LocalDateTime start; + + @Future + @NotNull(groups = Marker.OnCreate.class) + LocalDateTime end; +} diff --git a/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java new file mode 100644 index 0000000..a336826 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java @@ -0,0 +1,136 @@ +package ru.practicum.shareit.client; + +import jakarta.annotation.Nonnull; +import org.springframework.http.*; +import org.springframework.lang.Nullable; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.Map; + +public class BaseClient { + protected final RestTemplate rest; + + public BaseClient(RestTemplate rest) { + this.rest = rest; + } + + protected ResponseEntity get(String path) { + return get(path, null, null); + } + + protected ResponseEntity get(String path, long userId) { + return get(path, userId, null); + } + + protected ResponseEntity get(String path, + Long userId, + @Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.GET, path, userId, parameters, null); + } + + protected ResponseEntity post(String path, T body) { + return post(path, null, null, body); + } + + protected ResponseEntity post(String path, @Nullable Long userId, T body) { + return post(path, userId, null, body); + } + + protected ResponseEntity post(String path, + @Nullable Long userId, + @Nullable Map parameters, + T body) { + return makeAndSendRequest(HttpMethod.POST, path, userId, parameters, body); + } + + protected ResponseEntity put(String path, long userId, T body) { + return put(path, userId, null, body); + } + + protected ResponseEntity put(String path, + long userId, + @Nullable Map parameters, + T body) { + return makeAndSendRequest(HttpMethod.PUT, path, userId, parameters, body); + } + + protected ResponseEntity patch(String path, T body) { + return patch(path, null, null, body); + } + + protected ResponseEntity patch(String path, long userId) { + return patch(path, userId, null, null); + } + + protected ResponseEntity patch(String path, @Nullable Long userId, T body) { + return patch(path, userId, null, body); + } + + protected ResponseEntity patch(String path, + @Nullable Long userId, + @Nullable Map parameters, + T body) { + return makeAndSendRequest(HttpMethod.PATCH, path, userId, parameters, body); + } + + protected ResponseEntity delete(String path) { + return delete(path, null, null); + } + + protected ResponseEntity delete(String path, long userId) { + return delete(path, userId, null); + } + + protected ResponseEntity delete(String path, + Long userId, + @Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.DELETE, path, userId, parameters, null); + } + + private ResponseEntity makeAndSendRequest(HttpMethod method, + String path, + @Nullable Long userId, + @Nullable Map parameters, + @Nullable T body) { + HttpEntity requestEntity = new HttpEntity<>(body, defaultHeaders(userId)); + + ResponseEntity shareitServerResponse; + try { + if (parameters != null) { + shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class, parameters); + } else { + shareitServerResponse = rest.exchange(path, method, requestEntity, Object.class); + } + } catch (HttpStatusCodeException e) { + return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray()); + } + return prepareGatewayResponse(shareitServerResponse); + } + + @Nonnull + private HttpHeaders defaultHeaders(Long userId) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + if (userId != null) { + headers.set("X-Sharer-User-Id", String.valueOf(userId)); + } + return headers; + } + + private static ResponseEntity prepareGatewayResponse(@Nonnull 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/home/HomeController.java b/gateway/src/main/java/ru/practicum/shareit/home/HomeController.java new file mode 100644 index 0000000..41df18e --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/home/HomeController.java @@ -0,0 +1,43 @@ +package ru.practicum.shareit.home; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(path = "/") +public class HomeController { + @GetMapping + public String home() { + return """ +


+
+					___________________¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶
+					_________________¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶
+					__¶_¶__________¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶______¶__¶__¶
+					__¶_¶__¶______¶¶¶¶¶¶____¶¶¶¶¶¶____¶¶¶¶¶¶______¶_¶_¶
+					¶_¶_¶_¶______¶¶¶¶¶¶______¶¶¶¶______¶¶¶¶¶¶¶____¶¶¶¶¶
+					_¶¶¶¶¶______¶¶¶¶¶¶¶___O_¶¶¶¶¶__O__¶¶¶¶¶¶¶¶_¶¶¶¶¶¶¶
+					_¶¶¶¶¶¶¶¶__¶¶¶¶¶¶¶¶¶____¶¶¶¶¶¶____¶¶¶¶¶¶¶¶¶¶___¶¶¶
+					____¶¶____¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶____¶¶
+					____¶_____¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶__¶¶___¶
+					_____¶¶¶¶¶¶¶¶__¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶__¶¶¶¶¶¶
+					__________¶¶¶__¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶__¶¶¶
+					___________¶¶¶_____¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶_____¶¶
+					____________¶¶________¶¶¶¶¶¶¶¶¶¶_______¶¶
+					_____________¶¶¶_______________________¶
+					_______________¶¶____¶¶¶¶¶¶¶¶¶¶¶______¶
+					________________¶¶¶_____¶¶¶¶¶¶¶¶¶¶¶__¶
+					___________________¶¶¶_____¶¶¶¶¶¶¶¶¶¶
+					______________________¶¶¶¶¶__¶¶¶¶¶¶¶¶¶
+					_____________________________¶¶¶¶¶¶¶¶¶¶
+					______________________________¶¶¶¶¶¶¶¶¶
+						      _                    _ _  \s
+						  ___| |__   __ _ _ __ ___(_) |_\s
+						 / __| '_ \\ / _` | '__/ _ \\ | __|
+						 \\__ \\ | | | (_| | | |  __/ | |_\s
+						 |___/_| |_|\\__,_|_|  \\___|_|\\__|
+				
+ """; + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java b/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java new file mode 100644 index 0000000..4b608a1 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java @@ -0,0 +1,74 @@ +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.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.client.BaseClient; +import ru.practicum.shareit.item.dto.GCommentDto; +import ru.practicum.shareit.item.dto.GItemDto; + +import java.util.Collections; +import java.util.Map; + +@Service +public class ItemClient extends BaseClient { + + private static final String API_PREFIX = "/items"; + + @Autowired + public ItemClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public ResponseEntity getItemById(long userId, long itemId) { + return get("/" + itemId, userId); + } + + public ResponseEntity addNewItem(long userId, GItemDto item) { + return post("", userId, item); + } + + public ResponseEntity getAllItemsOfUser(long userId, Integer from, Integer size) { + Map parameters = Map.of( + "from", from, + "size", size + ); + return get("?from={from}&size={size}", userId, parameters); + } + + public ResponseEntity search(long userId, String text, Integer from, Integer size) { + if (text.isBlank()) { + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(Collections.emptyList()); + } + Map parameters = Map.of( + "text", text, + "from", from, + "size", size + ); + return get("/search?text={text}&from={from}&size={size}", userId, parameters); + } + + public ResponseEntity updateItem(long userId, long itemId, GItemDto itemDto) { + return patch("/" + itemId, userId, itemDto); + } + + public ResponseEntity deleteItem(long userId, long itemId) { + return delete("/" + itemId, userId); + } + + public ResponseEntity addComment(long userId, long itemId, GCommentDto commentDto) { + return post("/" + itemId + "/comment", userId, commentDto); + } +} 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..ec9d364 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -0,0 +1,96 @@ +package ru.practicum.shareit.item; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.item.dto.GCommentDto; +import ru.practicum.shareit.item.dto.GItemDto; +import ru.practicum.shareit.util.Marker; + +@Slf4j +@Validated +@Controller +@RequiredArgsConstructor +@RequestMapping(path = "/items") +public class ItemController { + + final ItemClient itemClient; + + @GetMapping("/{itemId}") + public ResponseEntity getItemById(@RequestHeader("X-Sharer-User-Id") long userId, + @PathVariable long itemId + ) { + log.info("Get item by id={} for user with id={}", itemId, userId); + return itemClient.getItemById(userId, itemId); + } + + @GetMapping + public ResponseEntity getAllItemsOfUser( + @RequestHeader("X-Sharer-User-Id") long userId, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size + ) { + log.info("Get all items for user with id={}", userId); + return itemClient.getAllItemsOfUser(userId, from, size); + } + + @GetMapping("/search") + public ResponseEntity search( + @RequestHeader("X-Sharer-User-Id") long userId, + @RequestParam String text, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size + ) { + return itemClient.search(userId, text, from, size); + } + + @PostMapping + @Validated(Marker.OnCreate.class) + public ResponseEntity addNewItem( + @RequestHeader("X-Sharer-User-Id") long userId, + @Valid @RequestBody GItemDto item + ) { + log.info("Add new item for user with id={}", userId); + return itemClient.addNewItem(userId, item); + } + + @PostMapping("/{itemId}/comment") + public ResponseEntity addComment( + @RequestHeader("X-Sharer-User-Id") long userId, + @PathVariable long itemId, + @RequestBody GCommentDto commentDto + ) { + log.info("Add new comment for item with id={} for user with id={}", itemId, userId); + return itemClient.addComment(userId, itemId, commentDto); + } + + @PatchMapping("/{itemId}") + public ResponseEntity updateItem( + @RequestHeader("X-Sharer-User-Id") long userId, + @PathVariable long itemId, + @RequestBody GItemDto itemDto + ) { + log.info("Update item with id={} for user with id={}", itemId, userId); + return itemClient.updateItem(userId, itemId, itemDto); + } + + @DeleteMapping("/{itemId}") + public ResponseEntity deleteItem( + @RequestHeader("X-Sharer-User-Id") long userId, + @PathVariable(name = "itemId") long itemId + ) { + log.info("Delete item with id={} for user with id={}", itemId, userId); + ResponseEntity response = itemClient.deleteItem(userId, itemId); + if (response.getStatusCode().is2xxSuccessful()) { + return new ResponseEntity<>("", HttpStatus.OK); + } + return response; + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/item/dto/GCommentDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/GCommentDto.java new file mode 100644 index 0000000..5fb34bd --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/GCommentDto.java @@ -0,0 +1,13 @@ +package ru.practicum.shareit.item.dto; + +import jakarta.validation.constraints.NotNull; +import ru.practicum.shareit.util.Marker; + +public record GCommentDto( + + @NotNull(groups = Marker.OnCreate.class) + Long itemId, + + @NotNull + String text +) {} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/dto/GItemDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/GItemDto.java new file mode 100644 index 0000000..c4521a0 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/GItemDto.java @@ -0,0 +1,21 @@ +package ru.practicum.shareit.item.dto; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import ru.practicum.shareit.util.Marker; + +public record GItemDto( + + @NotNull(groups = Marker.OnCreate.class) + @Size(min = 1, max = 255) + String name, + + @NotNull(groups = Marker.OnCreate.class) + @Size(max = 500) + String description, + + @NotNull(groups = Marker.OnCreate.class) + Boolean available, + + Long requestId +) {} diff --git a/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java new file mode 100644 index 0000000..ba62411 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestClient.java @@ -0,0 +1,54 @@ +package ru.practicum.shareit.request; + +import jakarta.annotation.Nonnull; +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.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.client.BaseClient; +import ru.practicum.shareit.request.dto.GItemRequestDto; + +import java.util.Map; + +@Service +public class ItemRequestClient extends BaseClient { + + private static final String API_PREFIX = "/requests"; + + @Autowired + public ItemRequestClient(@Value("${shareit-server.url}") String serverUrl, @Nonnull RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public ResponseEntity addNewRequest(long userId, GItemRequestDto requestDto) { + return post("", userId, requestDto); + } + + public ResponseEntity getRequestListByRequestor(long requestorId, Integer from, Integer size) { + Map parameters = Map.of( + "from", from, + "size", size + ); + return get("?from={from}&size={size}", requestorId, parameters); + } + + public ResponseEntity getRequestsListForUser(long userId, Integer from, Integer size) { + Map parameters = Map.of( + "from", from, + "size", size + ); + return get("/all?from={from}&size={size}", userId, parameters); + } + + public ResponseEntity getRequestById(long requestId) { + return get("/" + requestId); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java new file mode 100644 index 0000000..009cf1d --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -0,0 +1,54 @@ +package ru.practicum.shareit.request; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.request.dto.GItemRequestDto; + +@Slf4j +@RestController +@RequestMapping(path = "/requests") +@RequiredArgsConstructor +public class ItemRequestController { + + final ItemRequestClient itemRequestClient; + + @PostMapping + public ResponseEntity addNewRequest( + @RequestHeader("X-Sharer-User-Id") long userId, + @RequestBody @Valid GItemRequestDto requestDto + ) { + log.info("Add new request for user with id={}", userId); + return itemRequestClient.addNewRequest(userId, requestDto); + } + + @GetMapping + public ResponseEntity getRequestListByRequestor( + @RequestHeader("X-Sharer-User-Id") long requestorId, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size + ) { + log.info("Get all requests by requestor with id={}", requestorId); + return itemRequestClient.getRequestListByRequestor(requestorId, from, size); + } + + @GetMapping("/all") + public ResponseEntity getRequestListForUser( + @RequestHeader("X-Sharer-User-Id") long userId, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size + ) { + log.info("Get all requests for user with id={}", userId); + return itemRequestClient.getRequestsListForUser(userId, from, size); + } + + @GetMapping("/{requestId}") + public ResponseEntity getRequestById(@PathVariable long requestId) { + log.info("Get request with id={}", requestId); + return itemRequestClient.getRequestById(requestId); + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/request/dto/GItemRequestDto.java b/gateway/src/main/java/ru/practicum/shareit/request/dto/GItemRequestDto.java new file mode 100644 index 0000000..5c9cf64 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/dto/GItemRequestDto.java @@ -0,0 +1,9 @@ +package ru.practicum.shareit.request.dto; + +import jakarta.validation.constraints.NotBlank; + +public record GItemRequestDto( + + @NotBlank + String description +) {} diff --git a/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java new file mode 100644 index 0000000..88cbd29 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java @@ -0,0 +1,53 @@ +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.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.util.DefaultUriBuilderFactory; +import ru.practicum.shareit.client.BaseClient; +import ru.practicum.shareit.user.dto.GUserDto; + +import java.util.Map; + +@Service +public class UserClient extends BaseClient { + + private static final String API_PREFIX = "/users"; + + @Autowired + public UserClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build() + ); + } + + public ResponseEntity getUser(long userId) { + return get("/" + userId); + } + + public ResponseEntity getAllUsers(Integer from, Integer size) { + Map parameters = Map.of( + "from", from, + "size", size + ); + return get("?from={from}&size={size}", null, parameters); + } + + public ResponseEntity saveUser(GUserDto userDto) { + return post("", null, userDto); + } + + public ResponseEntity updateUser(long userId, GUserDto userDto) { + return patch("/" + userId, null, userDto); + } + + public ResponseEntity deleteUser(long userId) { + return delete("/" + userId); + } +} 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..f170867 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java @@ -0,0 +1,63 @@ +package ru.practicum.shareit.user; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.user.dto.GUserDto; +import ru.practicum.shareit.util.Marker; + +@Slf4j +@Validated +@Controller +@RequiredArgsConstructor +@RequestMapping(path = "/users") +public class UserController { + + final UserClient userClient; + + @GetMapping("/{userId}") + public ResponseEntity getUserById(@PathVariable long userId) { + log.info("Get user by id={}", userId); + return userClient.getUser(userId); + } + + @GetMapping + public ResponseEntity getAllUsers( + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size) { + log.info("Get all users"); + return userClient.getAllUsers(from, size); + } + + @PostMapping + @Validated(Marker.OnCreate.class) + public ResponseEntity addNewUser(@RequestBody @Valid GUserDto userDto) { + log.info("Add new user"); + return userClient.saveUser(userDto); + } + + @PatchMapping("/{userId}") + public ResponseEntity updateUser(@PathVariable long userId, + @RequestBody @Valid GUserDto userDto + ) { + log.info("Update user with id={}", userId); + return userClient.updateUser(userId, userDto); + } + + @DeleteMapping("/{userId}") + public ResponseEntity deleteUser(@PathVariable long userId) { + log.info("Delete user with id={}", userId); + ResponseEntity response = userClient.deleteUser(userId); + if (response.getStatusCode().is2xxSuccessful()) { + return new ResponseEntity<>("", HttpStatus.OK); + } + return response; + } +} diff --git a/gateway/src/main/java/ru/practicum/shareit/user/dto/GUserDto.java b/gateway/src/main/java/ru/practicum/shareit/user/dto/GUserDto.java new file mode 100644 index 0000000..94c6683 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/dto/GUserDto.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.user.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import ru.practicum.shareit.util.Marker; + +public record GUserDto( + + @NotNull(groups = Marker.OnCreate.class) + @Size(min = 1, max = 255) + String name, + + @Email @NotNull(groups = Marker.OnCreate.class) + @Size(min = 1, max = 255) + String email +) {} diff --git a/gateway/src/main/java/ru/practicum/shareit/util/ErrorResponse.java b/gateway/src/main/java/ru/practicum/shareit/util/ErrorResponse.java new file mode 100644 index 0000000..383f103 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/util/ErrorResponse.java @@ -0,0 +1,4 @@ +package ru.practicum.shareit.util; + +public record ErrorResponse(String error) { +} diff --git a/src/main/java/ru/practicum/shareit/util/Marker.java b/gateway/src/main/java/ru/practicum/shareit/util/Marker.java similarity index 71% rename from src/main/java/ru/practicum/shareit/util/Marker.java rename to gateway/src/main/java/ru/practicum/shareit/util/Marker.java index 6d03882..d9e48e5 100644 --- a/src/main/java/ru/practicum/shareit/util/Marker.java +++ b/gateway/src/main/java/ru/practicum/shareit/util/Marker.java @@ -1,5 +1,5 @@ package ru.practicum.shareit.util; public interface Marker { - interface OnCreate {} + interface OnCreate {} } diff --git a/gateway/src/main/java/ru/practicum/shareit/util/ShareItGatewayExceptionHandler.java b/gateway/src/main/java/ru/practicum/shareit/util/ShareItGatewayExceptionHandler.java new file mode 100644 index 0000000..11efc76 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/util/ShareItGatewayExceptionHandler.java @@ -0,0 +1,58 @@ +package ru.practicum.shareit.util; + +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.ValidationException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.Objects; +import java.util.stream.Collectors; + +@Slf4j +@RestControllerAdvice +public class ShareItGatewayExceptionHandler { + + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + protected ErrorResponse handleConstraintViolation(final ConstraintViolationException ex) { + String errorMessage = ex.getConstraintViolations().stream() + .map(violation -> { + String field = violation.getPropertyPath().toString(); + + if (field.contains(".")) { + field = field.substring(field.lastIndexOf('.') + 1); + } + + return "Поле " + field + ": " + violation.getMessage(); + }) + .collect(Collectors.joining("; ")); + + ErrorResponse response = new ErrorResponse(errorMessage); + log.error(errorMessage); + + return response; + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + protected ErrorResponse handleMethodArgumentNotValid(final MethodArgumentNotValidException ex) { + ErrorResponse response = new ErrorResponse( + "Поле " + Objects.requireNonNull(ex.getFieldError()).getField() + " " + + ex.getFieldError().getDefaultMessage() + ); + log.error(response.error()); + return response; + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + protected ErrorResponse handleValidationException(final ValidationException ex) { + ErrorResponse response = new ErrorResponse(ex.getMessage()); + log.error(response.error()); + return response; + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/util/config/ShateItGatewayConfiguration.java b/gateway/src/main/java/ru/practicum/shareit/util/config/ShateItGatewayConfiguration.java new file mode 100644 index 0000000..74b6282 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/util/config/ShateItGatewayConfiguration.java @@ -0,0 +1,27 @@ +package ru.practicum.shareit.util.config; + +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; + +import java.time.format.DateTimeFormatter; + +@SpringBootConfiguration +public class ShateItGatewayConfiguration { + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { + String pattern = "yyyy-MM-dd'T'HH:mm:ss"; + return builder -> { + builder.simpleDateFormat(pattern); + builder.serializers(new LocalDateTimeSerializer( + DateTimeFormatter.ofPattern(pattern) + )); + builder.deserializers(new LocalDateTimeDeserializer( + DateTimeFormatter.ofPattern(pattern) + )); + }; + } +} \ No newline at end of file diff --git a/gateway/src/main/resources/application.yaml b/gateway/src/main/resources/application.yaml new file mode 100644 index 0000000..eacf1f1 --- /dev/null +++ b/gateway/src/main/resources/application.yaml @@ -0,0 +1,10 @@ +server.port: 8080 + +shareit-server.url: http://localhost:9090 + +logging: + level: +# httpclient.wire: DEBUG + org: + springframework.web.client.RestTemplate: DEBUG +# apache.http: DEBUG diff --git a/gateway/src/main/resources/banner.txt b/gateway/src/main/resources/banner.txt new file mode 100644 index 0000000..f664a37 --- /dev/null +++ b/gateway/src/main/resources/banner.txt @@ -0,0 +1,5 @@ + _ _ _ + ___| |__ __ _ _ __ ___(_) |_ + / __| '_ \ / _` | '__/ _ \ | __| + \__ \ | | | (_| | | | __/ | |_ + |___/_| |_|\__,_|_| \___|_|\__| diff --git a/gateway/src/test/java/ShareitGatewayTest.java b/gateway/src/test/java/ShareitGatewayTest.java new file mode 100644 index 0000000..c91be13 --- /dev/null +++ b/gateway/src/test/java/ShareitGatewayTest.java @@ -0,0 +1,13 @@ +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import ru.practicum.shareit.ShareItGateway; + +@SpringBootTest +@ContextConfiguration(classes = ShareItGateway.class) +public class ShareitGatewayTest { + + @Test + public void startingTest() { + } +} diff --git a/suppressions.xml b/gateway/suppressions.xml similarity index 100% rename from suppressions.xml rename to gateway/suppressions.xml diff --git a/lombok.config b/lombok.config index fa7bee0..073822d 100644 --- a/lombok.config +++ b/lombok.config @@ -1,10 +1,27 @@ +# Не искать настройки за пределами проекта config.stopBubbling = true -lombok.anyconstructor.addconstructorproperties = false + +# Хранить имена параметров конструктора +lombok.anyconstructor.addconstructorproperties = true + +# Добавить @Generated над сгенерированнвм классом lombok.addLombokGeneratedAnnotation = true + +# Добавлять @SuppressWarnings в методы lombok.addSuppressWarnings = false + +# Все поля по умолчанию приватные lombok.fieldDefaults.defaultPrivate = true -lombok.equalsAndHashCode.callSuper = call + +# Экуалс и хэшкод учитывают родителя +#lombok.equalsAndHashCode.callSuper = skip + +# Экуалс и хэшкод не используют геттеры lombok.equalsAndHashCode.doNotUseGetters = true + +# Тустринг содержит родителя с его полями lombok.toString.callSuper = call + +# Тустринг не использует геттеры lombok.toString.doNotUseGetters = true -lombok.noArgsConstructor.extraPrivate = true + diff --git a/pom.xml b/pom.xml index e05de59..1e26e32 100644 --- a/pom.xml +++ b/pom.xml @@ -1,254 +1,188 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.3.2 - - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - ru.practicum - shareit - 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 3.5.9 + + - ShareIt + ru.practicum + shareit + 0.0.1-SNAPSHOT + pom - - 21 - + ShareIt - - - org.projectlombok - lombok - true - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.boot - spring-boot-starter-validation - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.postgresql - postgresql - runtime - - - com.h2database - h2 - test - - - org.zalando - logbook-spring-boot-starter - 3.9.0 - - + + gateway + server + - - - - src/main/resources - true - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - test - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.2 - - checkstyle.xml - true - true - true - - - - - check - - compile - - - - - com.puppycrawl.tools - checkstyle - 11.1.0 - compile - - - - - com.github.spotbugs - spotbugs-maven-plugin - 4.8.5.0 - - Max - High - - - - - check - - - - - - org.jacoco - jacoco-maven-plugin - 0.8.12 - - file - - - - jacoco-initialize - - prepare-agent - - - - jacoco-check - - check - - - - - BUNDLE - - - INSTRUCTION - COVEREDRATIO - 0.01 - - - LINE - COVEREDRATIO - 0.9 - - - BRANCH - COVEREDRATIO - 0.6 - - - COMPLEXITY - COVEREDRATIO - 0.6 - - - METHOD - COVEREDRATIO - 0.7 - - - CLASS - MISSEDCOUNT - 1 - - - - - - - - jacoco-report - test - - report - - - - - - - - - - check - - - - org.apache.maven.plugins - maven-checkstyle-plugin - - - com.github.spotbugs - spotbugs-maven-plugin - - - - - - - com.github.spotbugs - spotbugs-maven-plugin - - - - - - coverage - - - - org.jacoco - jacoco-maven-plugin - - - - - + + 21 + + + + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + test + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.2 + + checkstyle.xml + true + true + true + + + + + check + + compile + + + + + com.puppycrawl.tools + checkstyle + 11.1.0 + compile + + + + + com.github.spotbugs + spotbugs-maven-plugin + 4.8.5.0 + + Max + High + + + + + check + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.12 + + file + + + + jacoco-initialize + + prepare-agent + + + + jacoco-check + + check + + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + 0.01 + + + LINE + COVEREDRATIO + 0.9 + + + BRANCH + COVEREDRATIO + 0.6 + + + COMPLEXITY + COVEREDRATIO + 0.6 + + + METHOD + COVEREDRATIO + 0.7 + + + CLASS + MISSEDCOUNT + 1 + + + + + + + + jacoco-report + test + + report + + + + + + + + + + check + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + com.github.spotbugs + spotbugs-maven-plugin + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + + + + + 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/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..0ff1817 --- /dev/null +++ b/server/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/server/pom.xml b/server/pom.xml new file mode 100644 index 0000000..558c593 --- /dev/null +++ b/server/pom.xml @@ -0,0 +1,126 @@ + + + 4.0.0 + + + ru.practicum + shareit + 0.0.1-SNAPSHOT + + + shareit-server + + ShareIt Server + + + 5.1.0 + + + + + org.projectlombok + lombok + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.postgresql + postgresql + runtime + + + com.h2database + h2 + runtime + + + com.querydsl + querydsl-jpa + jakarta + ${querydsl.version} + compile + + + org.zalando + logbook-spring-boot-starter + 4.0.3 + + + + + + + com.mysema.maven + apt-maven-plugin + 1.1.3 + + + + process + + + target/generated-sources/java + com.querydsl.apt.jpa.JPAAnnotationProcessor + + + + + + com.querydsl + querydsl-apt + jakarta + ${querydsl.version} + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + coverage + + + + org.jacoco + jacoco-maven-plugin + + + **/Q*.class + + + + + + + + + diff --git a/src/main/java/ru/practicum/shareit/ShareItApp.java b/server/src/main/java/ru/practicum/shareit/ShareItServer.java similarity index 73% rename from src/main/java/ru/practicum/shareit/ShareItApp.java rename to server/src/main/java/ru/practicum/shareit/ShareItServer.java index 83b60e0..8d611fc 100644 --- a/src/main/java/ru/practicum/shareit/ShareItApp.java +++ b/server/src/main/java/ru/practicum/shareit/ShareItServer.java @@ -4,8 +4,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class ShareItApp { +public class ShareItServer { public static void main(String[] args) { - SpringApplication.run(ShareItApp.class, args); + SpringApplication.run(ShareItServer.class, args); } } 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 85% rename from src/main/java/ru/practicum/shareit/booking/BookingController.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingController.java index 84bbaa7..c59ad12 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingController.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -1,21 +1,14 @@ package ru.practicum.shareit.booking; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import ru.practicum.shareit.booking.dto.BookingDto; import ru.practicum.shareit.booking.dto.IncomingBookingDto; import ru.practicum.shareit.booking.service.BookingService; -import ru.practicum.shareit.util.Marker; import java.util.List; -/** - * TODO Sprint add-bookings. - */ @RestController -@Validated @RequestMapping(path = "/bookings") @RequiredArgsConstructor public class BookingController { @@ -46,10 +39,9 @@ public List getAllBookingsByOwner( } @PostMapping - @Validated(Marker.OnCreate.class) public BookingDto addNewBooking( @RequestHeader("X-Sharer-User-Id") Long userId, - @Valid @RequestBody IncomingBookingDto bookingDto + @RequestBody IncomingBookingDto bookingDto ) { return bookingService.addNewBooking(userId, bookingDto); } 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 78% 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 index fb00380..e364b2d 100644 --- a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java +++ b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java @@ -1,16 +1,11 @@ package ru.practicum.shareit.booking.dto; -import jakarta.validation.constraints.NotNull; import ru.practicum.shareit.booking.model.BookingStatus; import ru.practicum.shareit.item.dto.ItemDto; import ru.practicum.shareit.user.dto.UserDto; import java.time.LocalDateTime; -/** - * TODO Sprint add-bookings. - */ -@NotNull public record BookingDto( Long id, LocalDateTime start, @@ -18,4 +13,5 @@ public record BookingDto( ItemDto item, UserDto booker, BookingStatus status -) {} +) { +} diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingMapper.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingMapper.java similarity index 79% rename from src/main/java/ru/practicum/shareit/booking/dto/BookingMapper.java rename to server/src/main/java/ru/practicum/shareit/booking/dto/BookingMapper.java index 9520b4a..e1d7bee 100644 --- a/src/main/java/ru/practicum/shareit/booking/dto/BookingMapper.java +++ b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingMapper.java @@ -1,6 +1,7 @@ package ru.practicum.shareit.booking.dto; import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; import ru.practicum.shareit.booking.model.Booking; import ru.practicum.shareit.booking.model.BookingStatus; import ru.practicum.shareit.item.dto.ItemMapper; @@ -8,17 +9,16 @@ import ru.practicum.shareit.user.dto.UserMapper; import ru.practicum.shareit.user.model.User; -import java.time.ZoneId; - +@Component public class BookingMapper { @NonNull public static BookingDto toBookingDto(@NonNull Booking booking) { return new BookingDto( booking.getId(), - booking.getStart().atZone(ZoneId.systemDefault()).toLocalDateTime(), - booking.getEnd().atZone(ZoneId.systemDefault()).toLocalDateTime(), - ItemMapper.toItemDto(booking.getItem()), + booking.getStart(), + booking.getEnd(), + ItemMapper.toDto(booking.getItem()), UserMapper.toUserDto(booking.getBooker()), booking.getStatus() ); @@ -26,7 +26,7 @@ public static BookingDto toBookingDto(@NonNull Booking booking) { public static Booking fromIncomingDto(@NonNull IncomingBookingDto bookingDto, Item item, User booker) { return new Booking() - .setId(null) + .setID(null) .setStart(bookingDto.getStart()) .setEnd(bookingDto.getEnd()) .setBooker(booker) diff --git a/server/src/main/java/ru/practicum/shareit/booking/dto/IncomingBookingDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/IncomingBookingDto.java new file mode 100644 index 0000000..039af0d --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/booking/dto/IncomingBookingDto.java @@ -0,0 +1,14 @@ +package ru.practicum.shareit.booking.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +public class IncomingBookingDto { + long itemId; + LocalDateTime start; + LocalDateTime end; +} diff --git a/server/src/main/java/ru/practicum/shareit/booking/model/Booking.java b/server/src/main/java/ru/practicum/shareit/booking/model/Booking.java new file mode 100644 index 0000000..7010a63 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/booking/model/Booking.java @@ -0,0 +1,44 @@ +package ru.practicum.shareit.booking.model; + + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.util.entity.BaseEntity; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "bookings") +@Getter +@Setter +@NoArgsConstructor +@Accessors(chain = true) +public class Booking extends BaseEntity { + + @Column(name = "start_date", nullable = false) + LocalDateTime start; + + @Column(name = "end_date", nullable = false) + LocalDateTime end; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "item_id", nullable = false) + Item item; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "booker_id", nullable = false) + User booker; + + @Enumerated(EnumType.STRING) + BookingStatus status; + + public Booking setID(Long id) { + this.id = id; + return this; + } +} diff --git a/src/main/java/ru/practicum/shareit/booking/model/BookingStatus.java b/server/src/main/java/ru/practicum/shareit/booking/model/BookingStatus.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/model/BookingStatus.java rename to server/src/main/java/ru/practicum/shareit/booking/model/BookingStatus.java diff --git a/src/main/java/ru/practicum/shareit/booking/model/StateOfBooking.java b/server/src/main/java/ru/practicum/shareit/booking/model/StateOfBooking.java similarity index 98% rename from src/main/java/ru/practicum/shareit/booking/model/StateOfBooking.java rename to server/src/main/java/ru/practicum/shareit/booking/model/StateOfBooking.java index 0190228..4929b0f 100644 --- a/src/main/java/ru/practicum/shareit/booking/model/StateOfBooking.java +++ b/server/src/main/java/ru/practicum/shareit/booking/model/StateOfBooking.java @@ -13,6 +13,7 @@ public enum StateOfBooking { @Getter private String invalidValue; + @NonNull public static StateOfBooking of(String text) { if (text == null) { return ALL; diff --git a/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java b/server/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java rename to server/src/main/java/ru/practicum/shareit/booking/repository/BookingRepository.java diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingService.java b/server/src/main/java/ru/practicum/shareit/booking/service/BookingService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/service/BookingService.java rename to server/src/main/java/ru/practicum/shareit/booking/service/BookingService.java diff --git a/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java b/server/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java similarity index 73% rename from src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java index 2cbfcbf..63207fa 100644 --- a/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/booking/service/BookingServiceImpl.java @@ -1,6 +1,5 @@ package ru.practicum.shareit.booking.service; -import jakarta.validation.ValidationException; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import org.springframework.lang.NonNull; @@ -13,15 +12,10 @@ import ru.practicum.shareit.booking.model.StateOfBooking; import ru.practicum.shareit.booking.repository.BookingRepository; import ru.practicum.shareit.item.model.Item; -import ru.practicum.shareit.item.repository.ItemRepository; import ru.practicum.shareit.user.model.User; -import ru.practicum.shareit.user.repository.UserRepository; -import ru.practicum.shareit.util.exception.BookingStatusException; -import ru.practicum.shareit.util.exception.ForbiddenException; -import ru.practicum.shareit.util.exception.NotFoundException; -import ru.practicum.shareit.util.exception.ParameterNotValidException; +import ru.practicum.shareit.util.UtilService; +import ru.practicum.shareit.util.exception.*; -import java.time.LocalDateTime; import java.util.List; @Service @@ -30,25 +24,13 @@ public class BookingServiceImpl implements BookingService { BookingRepository bookingRepository; - ItemRepository itemRepository; - UserRepository userRepository; + UtilService utilService; @Override - public BookingDto addNewBooking(Long userId, @NonNull IncomingBookingDto bookingDto) { - LocalDateTime start = bookingDto.getStart(); - LocalDateTime end = bookingDto.getEnd(); - if (start.isAfter(end) || start.isEqual(end)) { - throw new ValidationException("Дата начала бронирования должна быть раньше даты окончания"); - } - - User booker = userRepository.findById(userId).orElseThrow( - () -> new ForbiddenException("Пользователь с id=" + userId + " не найден") - ); - - long itemId = bookingDto.getItemId(); - Item item = itemRepository.findById(itemId).orElseThrow( - () -> new NotFoundException("Вещь с id=" + itemId + " не найдена") - ); + public BookingDto addNewBooking(Long userId, @NonNull IncomingBookingDto incomingBookingDto) { + User booker = utilService.getUser(userId); + long itemId = incomingBookingDto.getItemId(); + Item item = utilService.getItem(itemId); if (item.getOwner().getId().equals(userId)) { throw new ValidationException("Нельзя забронировать свою-же вещь"); } @@ -56,14 +38,13 @@ public BookingDto addNewBooking(Long userId, @NonNull IncomingBookingDto booking throw new ValidationException("Вещь с id=" + item.getId() + " не доступна для аренды"); } - return BookingMapper.toBookingDto( - bookingRepository.save(BookingMapper.fromIncomingDto(bookingDto, item, booker)) - ); + Booking bookingToSave = BookingMapper.fromIncomingDto(incomingBookingDto, item, booker); + return BookingMapper.toBookingDto(bookingRepository.save(bookingToSave)); } @Override public BookingDto approveBooking(long userId, long bookingId, boolean approved) { - checkUser(userId); + utilService.checkUser(userId); Booking booking = getBooking(bookingId); if (userId != booking.getItem().getOwner().getId()) { throw new ForbiddenException("Только владелец может менять статус вещи"); @@ -85,12 +66,12 @@ public BookingDto approveBooking(long userId, long bookingId, boolean approved) case CANCELED -> throw new BookingStatusException("Бронирование уже было отменено создателем"); } - return BookingMapper.toBookingDto(booking); + return BookingMapper.toBookingDto(bookingRepository.save(booking)); } @Override public BookingDto getBooking(Long userId, long bookingId) { - checkUser(userId); + utilService.checkUser(userId); Booking booking = getBooking(bookingId); if (!booking.getBooker().getId().equals(userId) && !booking.getItem().getOwner().getId().equals(userId)) { throw new ForbiddenException("Может быть выполнено либо автором бронирования, либо владельцем вещи"); @@ -100,7 +81,7 @@ public BookingDto getBooking(Long userId, long bookingId) { @Override public List getAllBookingsByBooker(long userId, String state) { - checkUser(userId); + utilService.checkUser(userId); StateOfBooking stateOfBooking = StateOfBooking.of(state); switch (stateOfBooking) { case ALL -> { @@ -139,19 +120,15 @@ public List getAllBookingsByBooker(long userId, String state) { .map(BookingMapper::toBookingDto) .toList(); } - case null, default -> { - assert stateOfBooking != null; - throw new ParameterNotValidException( + default -> throw new ParameterNotValidException( "state = " + stateOfBooking.getInvalidValue(), - "допустимые значения: " + StateOfBooking.getValidValues() - ); - } + "допустимые значения: " + StateOfBooking.getValidValues()); } } @Override public List getAllBookingsByOwner(long userId, String state) { - checkUser(userId); + utilService.checkUser(userId); StateOfBooking stateOfBooking = StateOfBooking.of(state); switch (stateOfBooking) { case ALL -> { @@ -190,19 +167,9 @@ public List getAllBookingsByOwner(long userId, String state) { .map(BookingMapper::toBookingDto) .toList(); } - case null, default -> { - assert stateOfBooking != null; - throw new ParameterNotValidException( + default -> throw new ParameterNotValidException( "state = " + stateOfBooking.getInvalidValue(), - "допустимые значения: " + StateOfBooking.getValidValues() - ); - } - } - } - - private void checkUser(Long userId) { - if (!userRepository.existsById(userId)) { - throw new ForbiddenException("Пользователь с id=" + userId + " не найден."); + "допустимые значения: " + StateOfBooking.getValidValues()); } } 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 75% rename from src/main/java/ru/practicum/shareit/item/ItemController.java rename to server/src/main/java/ru/practicum/shareit/item/ItemController.java index be70df4..429a6d4 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemController.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemController.java @@ -1,22 +1,15 @@ package ru.practicum.shareit.item; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.validation.annotation.Validated; 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.ItemDtoFullVersion; +import ru.practicum.shareit.item.dto.ItemBigDto; import ru.practicum.shareit.item.service.ItemService; -import ru.practicum.shareit.util.Marker; import java.util.List; -/** - * TODO Sprint add-controllers. - */ @RestController -@Validated @RequestMapping("/items") @RequiredArgsConstructor public class ItemController { @@ -24,7 +17,7 @@ public class ItemController { final ItemService itemService; @GetMapping("/{itemId}") - public ItemDtoFullVersion getItem( + public ItemBigDto getItem( @RequestHeader("X-Sharer-User-Id") long userId, @PathVariable long itemId ) { @@ -32,7 +25,7 @@ public ItemDtoFullVersion getItem( } @GetMapping - public List getAllItemsOfUser(@RequestHeader("X-Sharer-User-Id") long userId) { + public List getAllItemsOfUser(@RequestHeader("X-Sharer-User-Id") long userId) { return itemService.getAllItemsOfUser(userId); } @@ -45,10 +38,9 @@ public List search( } @PostMapping - @Validated(Marker.OnCreate.class) public ItemDto addNewItem( @RequestHeader("X-Sharer-User-Id") long userId, - @Valid @RequestBody ItemDto item + @RequestBody ItemDto item ) { return itemService.addNewItem(userId, item); } @@ -57,7 +49,7 @@ public ItemDto addNewItem( public CommentDto addComment( @RequestHeader("X-Sharer-User-Id") long userId, @PathVariable long itemId, - @Valid @RequestBody CommentDto commentDto + @RequestBody CommentDto commentDto ) { return itemService.addComment(userId, itemId, commentDto); } @@ -66,7 +58,7 @@ public CommentDto addComment( public ItemDto updateItem( @RequestHeader("X-Sharer-User-Id") long userId, @PathVariable long itemId, - @Valid @RequestBody ItemDto itemDto + @RequestBody ItemDto itemDto ) { return itemService.updateItem(userId, itemId, itemDto); } diff --git a/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java similarity index 74% rename from src/main/java/ru/practicum/shareit/item/dto/CommentDto.java rename to server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java index f52f008..1b15e9a 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java +++ b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java @@ -1,8 +1,6 @@ package ru.practicum.shareit.item.dto; import com.fasterxml.jackson.annotation.JsonProperty; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Data; @@ -10,7 +8,6 @@ @Data @Builder -@NotNull public class CommentDto { Long id; @@ -22,6 +19,5 @@ public class CommentDto { @JsonProperty("created") LocalDateTime createdDate; - @NotBlank String text; } diff --git a/src/main/java/ru/practicum/shareit/item/dto/CommentMapper.java b/server/src/main/java/ru/practicum/shareit/item/dto/CommentMapper.java similarity index 77% rename from src/main/java/ru/practicum/shareit/item/dto/CommentMapper.java rename to server/src/main/java/ru/practicum/shareit/item/dto/CommentMapper.java index a6650e0..889e6a9 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/CommentMapper.java +++ b/server/src/main/java/ru/practicum/shareit/item/dto/CommentMapper.java @@ -1,12 +1,14 @@ package ru.practicum.shareit.item.dto; import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; import ru.practicum.shareit.item.model.Comment; import ru.practicum.shareit.item.model.Item; import ru.practicum.shareit.user.model.User; import java.time.LocalDateTime; +@Component public class CommentMapper { public static CommentDto toDto(@NonNull Comment comment) { @@ -21,10 +23,11 @@ public static CommentDto toDto(@NonNull Comment comment) { public static Comment toComment(@NonNull CommentDto dto, @NonNull Item item, @NonNull User author) { return new Comment() - .setId(dto.getId()) + .setID(dto.getId()) .setItem(item) .setAuthor(author) - .setCreatedDate(dto.getCreatedDate() == null ? LocalDateTime.now() : dto.getCreatedDate()) - .setText(dto.getText() == null ? null : dto.getText().trim()); + .setCreatedDate(dto.getCreatedDate() == null ? + LocalDateTime.now() : dto.getCreatedDate()) + .setText(dto.getText()); } } diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDtoFullVersion.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemBigDto.java similarity index 80% rename from src/main/java/ru/practicum/shareit/item/dto/ItemDtoFullVersion.java rename to server/src/main/java/ru/practicum/shareit/item/dto/ItemBigDto.java index caba5a3..1bbbd20 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDtoFullVersion.java +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemBigDto.java @@ -1,21 +1,16 @@ package ru.practicum.shareit.item.dto; import com.fasterxml.jackson.annotation.JsonProperty; -import jakarta.validation.constraints.NotNull; import lombok.*; import lombok.experimental.FieldDefaults; import java.time.LocalDateTime; import java.util.List; -/** - * TODO Sprint add-controllers. - */ @Data -@NotNull @Builder @FieldDefaults(makeFinal = true) -public class ItemDtoFullVersion { +public class ItemBigDto { Long id; String name; 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..ae17b78 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java @@ -0,0 +1,17 @@ +package ru.practicum.shareit.item.dto; + + +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +@Data +@Builder +@FieldDefaults(makeFinal = true) +public class ItemDto { + Long id; + String name; + String description; + Boolean available; + Long requestId; +} diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/ItemLowDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemLowDto.java new file mode 100644 index 0000000..73b8d9d --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemLowDto.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.item.dto; + +public record ItemLowDto( + Long id, + String name, + Long ownerId +) {} diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemMapper.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemMapper.java similarity index 68% rename from src/main/java/ru/practicum/shareit/item/dto/ItemMapper.java rename to server/src/main/java/ru/practicum/shareit/item/dto/ItemMapper.java index cfb2ee2..cb34026 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemMapper.java +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemMapper.java @@ -1,16 +1,19 @@ package ru.practicum.shareit.item.dto; import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.model.ItemRequest; import ru.practicum.shareit.user.model.User; import java.time.LocalDateTime; import java.util.List; +@Component public class ItemMapper { @NonNull - public static ItemDto toItemDto(@NonNull Item item) { + public static ItemDto toDto(@NonNull Item item) { return ItemDto.builder() .id(item.getId()) .name(item.getName()) @@ -20,13 +23,13 @@ public static ItemDto toItemDto(@NonNull Item item) { .build(); } - public static ItemDtoFullVersion toFullItemDto( + public static ItemBigDto toBigDto( @NonNull Item item, LocalDateTime lastBookingDate, LocalDateTime nextBookingDate, List comments ) { - return ItemDtoFullVersion.builder() + return ItemBigDto.builder() .id(item.getId()) .name(item.getName()) .description(item.getDescription()) @@ -38,14 +41,22 @@ public static ItemDtoFullVersion toFullItemDto( .build(); } + public static ItemLowDto toLowDto(Item item) { + return new ItemLowDto( + item.getId(), + item.getName(), + item.getOwner().getId() + ); + } + @NonNull - public static Item toItem(@NonNull ItemDto itemDto, User owner) { + public static Item toEntity(@NonNull ItemDto itemDto, User owner, ItemRequest request) { return new Item() - .setId(itemDto.getId()) + .setID(itemDto.getId()) .setName(itemDto.getName()) .setDescription(itemDto.getDescription()) .setAvailable(itemDto.getAvailable()) .setOwner(owner) - .setRequest(null); + .setRequest(request); } } diff --git a/server/src/main/java/ru/practicum/shareit/item/model/Comment.java b/server/src/main/java/ru/practicum/shareit/item/model/Comment.java new file mode 100644 index 0000000..bde6bc0 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/model/Comment.java @@ -0,0 +1,35 @@ +package ru.practicum.shareit.item.model; + +import jakarta.persistence.*; +import lombok.*; +import lombok.experimental.Accessors; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.util.entity.BaseEntity; +import java.time.LocalDateTime; + +@Entity +@Table(name = "comments") +@Getter +@Setter +@NoArgsConstructor +@Accessors(chain = true) +public class Comment extends BaseEntity { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "item_id") + Item item; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "author_id") + User author; + + LocalDateTime createdDate; + + @Column(name = "comment_text", length = 500) + String text; + + public Comment setID(Long id) { + this.id = id; + return this; + } +} diff --git a/server/src/main/java/ru/practicum/shareit/item/model/Item.java b/server/src/main/java/ru/practicum/shareit/item/model/Item.java new file mode 100644 index 0000000..b37c968 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -0,0 +1,40 @@ +package ru.practicum.shareit.item.model; + +import jakarta.persistence.*; +import lombok.*; +import lombok.experimental.Accessors; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.util.entity.BaseEntity; + +@Entity +@Table(name = "items") +@Getter +@Setter +@NoArgsConstructor +@Accessors(chain = true) +public class Item extends BaseEntity { + + @Column(name = "item_name", nullable = false, length = 100) + String name; + + @Column + String description; + + /// статус о том, доступна или нет вещь для аренды + @Column(nullable = false) + boolean available; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "owner_id", nullable = false) + User owner; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "request_id") + ItemRequest request; + + public Item setID(Long id) { + this.id = id; + return this; + } +} diff --git a/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java b/server/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java rename to server/src/main/java/ru/practicum/shareit/item/repository/CommentRepository.java diff --git a/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java b/server/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java rename to server/src/main/java/ru/practicum/shareit/item/repository/ItemRepository.java diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemService.java b/server/src/main/java/ru/practicum/shareit/item/service/ItemService.java similarity index 86% rename from src/main/java/ru/practicum/shareit/item/service/ItemService.java rename to server/src/main/java/ru/practicum/shareit/item/service/ItemService.java index 81e1582..fb6396b 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemService.java +++ b/server/src/main/java/ru/practicum/shareit/item/service/ItemService.java @@ -4,7 +4,7 @@ import org.springframework.transaction.annotation.Transactional; import ru.practicum.shareit.item.dto.CommentDto; import ru.practicum.shareit.item.dto.ItemDto; -import ru.practicum.shareit.item.dto.ItemDtoFullVersion; +import ru.practicum.shareit.item.dto.ItemBigDto; import java.util.List; @@ -12,10 +12,10 @@ public interface ItemService { /// Получить вещь по id - ItemDtoFullVersion getItem(long userId, long itemId); + ItemBigDto getItem(long userId, long itemId); /// Получить все вещи - List getAllItemsOfUser(long userId); + List getAllItemsOfUser(long userId); /// Поиск вещей по подстроке List search(long userId, String search); diff --git a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java b/server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java similarity index 64% rename from src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java index c91badc..a09e785 100644 --- a/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/item/service/ItemServiceImpl.java @@ -11,15 +11,15 @@ import ru.practicum.shareit.item.model.Item; import ru.practicum.shareit.item.repository.CommentRepository; import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.request.model.ItemRequest; import ru.practicum.shareit.user.model.User; -import ru.practicum.shareit.user.repository.UserRepository; +import ru.practicum.shareit.util.UtilService; import ru.practicum.shareit.util.exception.ForbiddenException; import ru.practicum.shareit.util.exception.NotFoundException; import ru.practicum.shareit.util.exception.ParameterNotValidException; import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -31,23 +31,23 @@ public class ItemServiceImpl implements ItemService { ItemRepository itemRepository; - UserRepository userRepository; BookingRepository bookingRepository; CommentRepository commentRepository; + UtilService utilService; @Override - public ItemDtoFullVersion getItem(long userId, long itemId) { - Item item = getItemWithCheckPresent(itemId); + public ItemBigDto getItem(long userId, long itemId) { + Item item = utilService.getItem(itemId); if (item.getOwner().getId() != userId) { - return ItemMapper.toFullItemDto( + return ItemMapper.toBigDto( item, null, null, getCommentDtos(itemId) ); } - return ItemMapper.toFullItemDto( + return ItemMapper.toBigDto( item, bookingRepository.findLastBookingDate(itemId), bookingRepository.findNextBookingDate(itemId), @@ -56,8 +56,8 @@ public ItemDtoFullVersion getItem(long userId, long itemId) { } @Override - public List getAllItemsOfUser(long userId) { - checkUser(userId); + public List getAllItemsOfUser(long userId) { + utilService.checkUser(userId); Map items = itemRepository.findAllByOwner_Id(userId) .stream() .collect(Collectors.toMap(Item::getId, Function.identity())); @@ -65,12 +65,12 @@ public List getAllItemsOfUser(long userId) { Map comments = commentRepository.findAllByItemIdIn(items.keySet()).stream() .collect(Collectors.toMap(comment -> comment.getItem().getId(), Function.identity())); - List result = new ArrayList<>(); + List result = new ArrayList<>(); for (Item item : items.values()) { - result.add(ItemMapper.toFullItemDto( + result.add(ItemMapper.toBigDto( item, - getLastBookingDate(bookings), - getNextBookingDate(bookings), + getLastBookingDate(bookings, item.getId()), + getNextBookingDate(bookings, item.getId()), comments.entrySet().stream() .filter(entry -> entry.getKey().equals(item.getId())) .map(Map.Entry::getValue) @@ -84,103 +84,93 @@ public List getAllItemsOfUser(long userId) { @Override public List search(long userId, String text) { - if (text == null || text.isEmpty()) { - return Collections.emptyList(); - } + utilService.checkUser(userId); return itemRepository.search(text).stream() - .map(ItemMapper::toItemDto) + .filter(item -> item.getOwner().getId() != userId) + .map(ItemMapper::toDto) .toList(); } @Override - public ItemDto addNewItem(Long userId, ItemDto itemDto) { - User user = getUserWithCheckPresent(userId); - return ItemMapper.toItemDto(itemRepository.save(ItemMapper.toItem(itemDto, user))); + public ItemDto addNewItem(Long userId, @NonNull ItemDto itemDto) { + User user = utilService.getUser(userId); + Long itemRequestId = itemDto.getRequestId(); + if (itemDto.getRequestId() == null) { + return ItemMapper.toDto(itemRepository.save(ItemMapper.toEntity(itemDto, user, null))); + } else { + ItemRequest request = utilService.getItemRequest(itemRequestId); + return ItemMapper.toDto(itemRepository.save(ItemMapper.toEntity(itemDto, user, request))); + } } @Override public ItemDto updateItem(long userId, long itemId, @NonNull ItemDto itemDto) { - checkUser(userId); - Item oldItem = getItemWithCheckPresentAndOwner(itemId, userId); + utilService.checkUser(userId); + Item oldItem = getItemWithOwner(itemId, userId); Item item = new Item() - .setId(itemId) + .setID(itemId) .setName(itemDto.getName() == null ? oldItem.getName() : itemDto.getName()) .setDescription(itemDto.getDescription() == null ? oldItem.getDescription() : itemDto.getDescription()) .setAvailable(itemDto.getAvailable() == null ? oldItem.isAvailable() : itemDto.getAvailable()) - .setOwner(getUserWithCheckPresent(userId)) - .setRequest(null); - return ItemMapper.toItemDto(itemRepository.save(item)); + .setOwner(utilService.getUser(userId)) + .setRequest(itemDto.getRequestId() == null ? null : utilService.getItemRequest(itemDto.getRequestId())); + return ItemMapper.toDto(itemRepository.save(item)); } @Override public void deleteItem(long userId, long itemId) { - checkUser(userId); - getItemWithCheckPresentAndOwner(itemId, userId); + utilService.checkUser(userId); + getItemWithOwner(itemId, userId); itemRepository.deleteById(itemId); } @Override public CommentDto addComment(long userId, long itemId, @NonNull CommentDto commentDto) { - User user = getUserWithCheckPresent(userId); - Item item = getItemWithCheckPresent(itemId); + User booker = utilService.getUser(userId); + Item item = utilService.getItem(itemId); if (item.getOwner().getId().equals(userId)) { throw new ForbiddenException("Нельзя оставить комментарий к своей вещи"); } - if (bookingRepository.findAllBookingByItem_Id(item.getId()).stream() - .noneMatch(booking -> booking.getBooker().getId().equals(userId) && - booking.getEnd().isBefore(LocalDateTime.now())) - ) { + + List bookings = bookingRepository.findAllBookingByItem_Id(item.getId()).stream() + .filter(booking -> booking.getBooker().getId() == userId) + .toList(); + + if (bookings.isEmpty() || bookings.stream() + .noneMatch(booking -> booking.getEnd().isBefore(LocalDateTime.now()))) { throw new ParameterNotValidException( "userId", "Комментарии доступны только тем, кто уже по пользовался вещью" ); } - return CommentMapper.toDto(commentRepository.save( - CommentMapper.toComment(commentDto, item, user) - ) - ); - } - - private void checkUser(long userId) { - if (!userRepository.existsById(userId)) { - throw new NotFoundException("Пользователь с id=" + userId + " не найден."); - } - } - - @NonNull - private User getUserWithCheckPresent(long userId) { - return userRepository.findById(userId).orElseThrow(() -> - new NotFoundException("Пользователь с id=" + userId + " не найден.") - ); - } - @NonNull - private Item getItemWithCheckPresent(long itemId) { - return itemRepository.findById(itemId).orElseThrow(() -> - new NotFoundException("Вещь с id=" + itemId + " не найдена.") - ); + Comment commentToSave = CommentMapper.toComment(commentDto, item, booker); + Comment savedComment = commentRepository.save(commentToSave); + return CommentMapper.toDto(savedComment); } @NonNull - private Item getItemWithCheckPresentAndOwner(long itemId, long userId) { - Item item = getItemWithCheckPresent(itemId); + private Item getItemWithOwner(long itemId, long userId) { + Item item = utilService.getItem(itemId); if (item.getOwner().getId() == userId) { return item; } throw new NotFoundException("Пользователь с id=" + userId + " не является владельцем вещи с id=" + itemId); } - private LocalDateTime getLastBookingDate(@NonNull List bookings) { + private LocalDateTime getLastBookingDate(@NonNull List bookings, Long itemId) { LocalDateTime now = LocalDateTime.now(); return bookings.stream() + .filter(booking -> booking.getItem().getId().equals(itemId)) .map(Booking::getStart) .filter(start -> !start.isAfter(now)) .max(LocalDateTime::compareTo) .orElse(null); } - private LocalDateTime getNextBookingDate(@NonNull List bookings) { + private LocalDateTime getNextBookingDate(@NonNull List bookings, Long itemId) { LocalDateTime now = LocalDateTime.now(); return bookings.stream() + .filter(booking -> booking.getItem().getId().equals(itemId)) .map(Booking::getStart) .filter(start -> start.isAfter(now)) .min(LocalDateTime::compareTo) diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java new file mode 100644 index 0000000..7520470 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -0,0 +1,40 @@ +package ru.practicum.shareit.request; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.dto.ItemRequestBigDto; +import ru.practicum.shareit.request.service.ItemRequestService; + +import java.util.List; + +@RestController +@RequestMapping(path = "/requests") +@RequiredArgsConstructor +public class ItemRequestController { + + final ItemRequestService requestService; + + @PostMapping + public ItemRequestDto addNewRequest( + @RequestHeader("X-Sharer-User-Id") long userId, + @RequestBody ItemRequestDto requestDto + ) { + return requestService.addNewRequest(userId, requestDto); + } + + @GetMapping + public List getRequestListByUser(@RequestHeader("X-Sharer-User-Id") long requestorId) { + return requestService.getRequestListByUser(requestorId); + } + + @GetMapping("/all") + public List getAllRequests(@RequestHeader("X-Sharer-User-Id") long userId) { + return requestService.getAllRequests(userId); + } + + @GetMapping("/{requestId}") + public ItemRequestBigDto getRequestById(@PathVariable long requestId) { + return requestService.getRequestById(requestId); + } +} diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestBigDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestBigDto.java new file mode 100644 index 0000000..5093566 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestBigDto.java @@ -0,0 +1,29 @@ +package ru.practicum.shareit.request.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; +import ru.practicum.shareit.item.dto.ItemLowDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@Builder +@FieldDefaults(makeFinal = true) +public class ItemRequestBigDto { + + Long id; + + String description; + + UserDto requestor; + + @JsonProperty("items") + List itemDtos; + + @JsonProperty("created") + LocalDateTime createdDate; +} diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java new file mode 100644 index 0000000..e8063b0 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java @@ -0,0 +1,24 @@ +package ru.practicum.shareit.request.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; + +@Data +@Builder +@FieldDefaults(makeFinal = true) +public class ItemRequestDto { + + Long id; + + String description; + + UserDto requestor; + + @JsonProperty("created") + LocalDateTime createdDate; +} diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestMapper.java b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestMapper.java new file mode 100644 index 0000000..70db9d4 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestMapper.java @@ -0,0 +1,44 @@ +package ru.practicum.shareit.request.dto; + +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import ru.practicum.shareit.item.dto.ItemMapper; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.dto.UserMapper; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; +import java.util.List; + +@Component +public class ItemRequestMapper { + + public static ItemRequestDto toDto(@NonNull ItemRequest itemRequest) { + return ItemRequestDto.builder() + .id(itemRequest.getId()) + .description(itemRequest.getDescription()) + .requestor(UserMapper.toUserDto(itemRequest.getRequestor())) + .createdDate(itemRequest.getCreatedDate()) + .build(); + } + + public static ItemRequestBigDto toBigDto(@NonNull ItemRequest itemRequest, @NonNull List items) { + return ItemRequestBigDto.builder() + .id(itemRequest.getId()) + .description(itemRequest.getDescription()) + .requestor(UserMapper.toUserDto(itemRequest.getRequestor())) + .itemDtos(items.stream().map(ItemMapper::toLowDto).toList()) + .createdDate(itemRequest.getCreatedDate()) + .build(); + } + + public static ItemRequest toEntity(@NonNull ItemRequestDto requestDto, User requestor) { + return new ItemRequest() + .setID(requestDto.getId()) + .setDescription(requestDto.getDescription()) + .setRequestor(requestor) + .setCreatedDate(requestDto.getCreatedDate() == null ? + LocalDateTime.now() : requestDto.getCreatedDate()); + } +} diff --git a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java b/server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java similarity index 53% rename from src/main/java/ru/practicum/shareit/request/model/ItemRequest.java rename to server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java index 2bb0df7..e6971d4 100644 --- a/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java +++ b/server/src/main/java/ru/practicum/shareit/request/model/ItemRequest.java @@ -3,26 +3,18 @@ import jakarta.persistence.*; import lombok.*; import lombok.experimental.Accessors; -import lombok.experimental.ExtensionMethod; import ru.practicum.shareit.user.model.User; -import ru.practicum.shareit.util.hibernate.HibernateEqualsAndHashCode; +import ru.practicum.shareit.util.entity.BaseEntity; import java.time.LocalDateTime; -/** - * TODO Sprint add-item-requests. - */ -@Data @Entity @Table(name = "requests") +@Getter +@Setter @NoArgsConstructor @Accessors(chain = true) -@ExtensionMethod({HibernateEqualsAndHashCode.class}) -public class ItemRequest { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - long id; +public class ItemRequest extends BaseEntity { String description; @@ -30,6 +22,11 @@ public class ItemRequest { @JoinColumn(name = "requestor_id", nullable = false) User requestor; - @Column(nullable = false) - LocalDateTime created; + @Column(name = "created", nullable = false) + LocalDateTime createdDate; + + public ItemRequest setID(Long id) { + this.id = id; + return this; + } } diff --git a/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java b/server/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java similarity index 74% rename from src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java rename to server/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java index 6d136f3..79e35a4 100644 --- a/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java +++ b/server/src/main/java/ru/practicum/shareit/request/repository/ItemRequestRepository.java @@ -3,5 +3,9 @@ import org.springframework.data.jpa.repository.JpaRepository; import ru.practicum.shareit.request.model.ItemRequest; +import java.util.List; + public interface ItemRequestRepository extends JpaRepository { + + List findAllByRequestor_Id(Long requestorId); } diff --git a/server/src/main/java/ru/practicum/shareit/request/repository/QueryDslRepository.java b/server/src/main/java/ru/practicum/shareit/request/repository/QueryDslRepository.java new file mode 100644 index 0000000..c9fb7e7 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/repository/QueryDslRepository.java @@ -0,0 +1,43 @@ +package ru.practicum.shareit.request.repository; + +import com.querydsl.jpa.JPQLQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.model.QItem; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.model.QItemRequest; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Repository +@RequiredArgsConstructor +public class QueryDslRepository { + + final JPQLQueryFactory queryFactory; + + final QItem qItem = QItem.item; + final QItemRequest qItemRequest = QItemRequest.itemRequest; + + public Map> getMapOfItemsByRequestId(List requestIds) { + return queryFactory + .selectFrom(qItem) + .where(qItem.request.id.in(requestIds)) + .fetch() + .stream() + .collect(Collectors.groupingBy(item -> item.getRequest().getId())); + } + + public List getAllRequestsWithoutThisUserRequests(Long userId) { + return queryFactory.selectFrom(qItemRequest) + .where(qItemRequest.requestor.id.notIn(userId)) + .orderBy(qItemRequest.createdDate.desc()) + .fetch(); + } + + public List getAllItemsByRequest(Long requestId) { + return queryFactory.selectFrom(qItem).where(qItem.request.id.eq(requestId)).fetch(); + } +} diff --git a/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java new file mode 100644 index 0000000..9959772 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java @@ -0,0 +1,24 @@ +package ru.practicum.shareit.request.service; + +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.dto.ItemRequestBigDto; + +import java.util.List; + +@Transactional(readOnly = true) +public interface ItemRequestService { + + /// Добавить новый запрос вещи + @Transactional + ItemRequestDto addNewRequest(long userId, ItemRequestDto requestDto); + + /// Получить список своих запросов вместе с данными об ответах на них + List getRequestListByUser(long userId); + + /// Получить список запросов, созданных другими пользователями + List getAllRequests(long userId); + + /// Получить данные об одном конкретном запросе вместе с данными об ответах на него + ItemRequestBigDto getRequestById(long requestId); +} diff --git a/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java new file mode 100644 index 0000000..644db68 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java @@ -0,0 +1,74 @@ +package ru.practicum.shareit.request.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.dto.ItemRequestBigDto; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.dto.ItemRequestMapper; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.repository.QueryDslRepository; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.util.UtilService; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class ItemRequestServiceImpl implements ItemRequestService { + + final ItemRequestRepository itemRequestRepository; + final QueryDslRepository queryDslRepository; + final UtilService utilService; + + @Override + public ItemRequestDto addNewRequest(long userId, ItemRequestDto requestDto) { + User user = utilService.getUser(userId); + ItemRequest itemRequest = ItemRequestMapper.toEntity(requestDto, user); + ItemRequest resultRequest = itemRequestRepository.save(itemRequest); + return ItemRequestMapper.toDto(resultRequest); + } + + @Override + public List getRequestListByUser(long userId) { + utilService.checkUser(userId); + + List itemRequests = itemRequestRepository.findAllByRequestor_Id(userId); + if (itemRequests.isEmpty()) { + return Collections.emptyList(); + } + + List requestIds = itemRequests.stream() + .map(ItemRequest::getId) + .toList(); + + Map> itemsByRequestId = queryDslRepository.getMapOfItemsByRequestId(requestIds); + + return itemRequests.stream() + .map(itemRequest -> ItemRequestMapper.toBigDto( + itemRequest, + itemsByRequestId.getOrDefault(itemRequest.getId(), Collections.emptyList()) + )) + .sorted(Comparator.comparing(ItemRequestBigDto::getCreatedDate).reversed()) + .toList(); + } + + @Override + public List getAllRequests(long userId) { + utilService.checkUser(userId); + return queryDslRepository.getAllRequestsWithoutThisUserRequests(userId).stream() + .map(ItemRequestMapper::toDto) + .toList(); + } + + @Override + public ItemRequestBigDto getRequestById(long requestId) { + List items = queryDslRepository.getAllItemsByRequest(requestId); + ItemRequest request = utilService.getItemRequest(requestId); + return ItemRequestMapper.toBigDto(request, items); + } +} 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 8dec375..02e9274 100644 --- a/src/main/java/ru/practicum/shareit/user/UserController.java +++ b/server/src/main/java/ru/practicum/shareit/user/UserController.java @@ -1,18 +1,13 @@ package ru.practicum.shareit.user; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import ru.practicum.shareit.user.dto.UserDto; import ru.practicum.shareit.user.service.UserService; -import ru.practicum.shareit.util.Marker; import java.util.List; -/** - * TODO Sprint add-controllers. - */ @RestController @Validated @RequestMapping(path = "/users") @@ -32,15 +27,14 @@ public List getAllUsers() { } @PostMapping - @Validated(Marker.OnCreate.class) - public UserDto addNewUser(@Valid @RequestBody UserDto userDto) { + public UserDto addNewUser(@RequestBody UserDto userDto) { return userService.saveUser(userDto); } @PatchMapping("/{userId}") public UserDto updateUser( @PathVariable long userId, - @Valid @RequestBody UserDto userDto + @RequestBody UserDto userDto ) { return userService.updateUser(userId, userDto); } diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java similarity index 50% rename from src/main/java/ru/practicum/shareit/user/dto/UserDto.java rename to server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java index d538b0b..f378fa0 100644 --- a/src/main/java/ru/practicum/shareit/user/dto/UserDto.java +++ b/server/src/main/java/ru/practicum/shareit/user/dto/UserDto.java @@ -1,25 +1,17 @@ package ru.practicum.shareit.user.dto; -import jakarta.validation.constraints.*; import lombok.Builder; import lombok.Data; import lombok.experimental.FieldDefaults; -import ru.practicum.shareit.util.Marker; @Data -@NotNull @Builder @FieldDefaults(makeFinal = true) public class UserDto { Long id; - @NotBlank(groups = Marker.OnCreate.class) - @Size(min = 1, max = 255) String name; - @NotBlank(groups = Marker.OnCreate.class) - @Size(min = 1, max = 512) - @Email String email; } diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserMapper.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserMapper.java similarity index 82% rename from src/main/java/ru/practicum/shareit/user/dto/UserMapper.java rename to server/src/main/java/ru/practicum/shareit/user/dto/UserMapper.java index 3a47070..b4597f7 100644 --- a/src/main/java/ru/practicum/shareit/user/dto/UserMapper.java +++ b/server/src/main/java/ru/practicum/shareit/user/dto/UserMapper.java @@ -1,7 +1,9 @@ package ru.practicum.shareit.user.dto; +import org.springframework.stereotype.Component; import ru.practicum.shareit.user.model.User; +@Component public class UserMapper { public static UserDto toUserDto(User user) { @@ -14,7 +16,7 @@ public static UserDto toUserDto(User user) { public static User toUser(UserDto userDto) { return new User() - .setId(userDto.getId()) + .setID(userDto.getId()) .setName(userDto.getName()) .setEmail(userDto.getEmail()); } diff --git a/server/src/main/java/ru/practicum/shareit/user/model/User.java b/server/src/main/java/ru/practicum/shareit/user/model/User.java new file mode 100644 index 0000000..ea54134 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/user/model/User.java @@ -0,0 +1,27 @@ +package ru.practicum.shareit.user.model; + +import jakarta.persistence.*; +import lombok.*; +import lombok.experimental.Accessors; + +import ru.practicum.shareit.util.entity.BaseEntity; + +@Entity +@Table(name = "users") +@Getter +@Setter +@NoArgsConstructor +@Accessors(chain = true) +public class User extends BaseEntity { + + @Column(name = "user_name", nullable = false) + private String name; + + @Column(nullable = false, unique = true, length = 512) + private String email; + + public User setID(Long id) { + this.id = id; + return this; + } +} diff --git a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java b/server/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java similarity index 99% rename from src/main/java/ru/practicum/shareit/user/repository/UserRepository.java rename to server/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java index cedef42..fb20957 100644 --- a/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java +++ b/server/src/main/java/ru/practicum/shareit/user/repository/UserRepository.java @@ -8,6 +8,7 @@ @Repository public interface UserRepository extends JpaRepository { + boolean existsByEmail(String email); @Modifying diff --git a/src/main/java/ru/practicum/shareit/user/service/UserService.java b/server/src/main/java/ru/practicum/shareit/user/service/UserService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/service/UserService.java rename to server/src/main/java/ru/practicum/shareit/user/service/UserService.java diff --git a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java b/server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java similarity index 82% rename from src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java index 6bfc640..b48e52c 100644 --- a/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/user/service/UserServiceImpl.java @@ -6,6 +6,7 @@ import ru.practicum.shareit.user.dto.UserDto; import ru.practicum.shareit.user.dto.UserMapper; import ru.practicum.shareit.user.repository.UserRepository; +import ru.practicum.shareit.util.UtilService; import ru.practicum.shareit.util.exception.DuplicatedDataException; import ru.practicum.shareit.util.exception.NotFoundException; @@ -13,13 +14,14 @@ @Service @RequiredArgsConstructor -class UserServiceImpl implements UserService { +public class UserServiceImpl implements UserService { final UserRepository userRepository; + final UtilService utilService; @Override public UserDto getUser(long userId) { - return UserMapper.toUserDto(getUserWithCheckPresent(userId)); + return UserMapper.toUserDto(utilService.getUser(userId)); } @Override @@ -37,7 +39,7 @@ public UserDto saveUser(UserDto userDto) { @Override public UserDto updateUser(long userId, UserDto userDto) { - User user = getUserWithCheckPresent(userId); + User user = utilService.getUser(userId); if (userDto.getName() != null) { user.setName(userDto.getName()); } @@ -63,10 +65,4 @@ private void checkEmail(String email) { throw new DuplicatedDataException("Пользователь с такой почтой " + email + " уже есть."); } } - - private User getUserWithCheckPresent(long userId) { - return userRepository.findById(userId).orElseThrow(() -> - new NotFoundException("Пользователь с id=" + userId + " не найден.") - ); - } } diff --git a/server/src/main/java/ru/practicum/shareit/util/UtilService.java b/server/src/main/java/ru/practicum/shareit/util/UtilService.java new file mode 100644 index 0000000..fd1287c --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/util/UtilService.java @@ -0,0 +1,48 @@ +package ru.practicum.shareit.util; + +import lombok.RequiredArgsConstructor; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; +import ru.practicum.shareit.util.exception.ForbiddenException; +import ru.practicum.shareit.util.exception.NotFoundException; + +@Service +@RequiredArgsConstructor +public class UtilService { + final UserRepository userRepository; + final ItemRepository itemRepository; + final ItemRequestRepository itemRequestRepository; + + public void checkUser(Long userId) { + if (!userRepository.existsById(userId)) { + throw new ForbiddenException("Пользователь с id=" + userId + " не найден."); + } + } + + @NonNull + public User getUser(long userId) { + return userRepository.findById(userId).orElseThrow(() -> + new NotFoundException("Пользователь с id=" + userId + " не найден.") + ); + } + + @NonNull + public Item getItem(long itemId) { + return itemRepository.findById(itemId).orElseThrow(() -> + new NotFoundException("Вещь с id=" + itemId + " не найдена.") + ); + } + + @NonNull + public ItemRequest getItemRequest(long requestId) { + return itemRequestRepository.findById(requestId).orElseThrow( + () -> new NotFoundException("Запрос с id=" + requestId + " на добавление вещи не найден.") + ); + } +} diff --git a/server/src/main/java/ru/practicum/shareit/util/config/ShateItConfiguration.java b/server/src/main/java/ru/practicum/shareit/util/config/ShateItConfiguration.java new file mode 100644 index 0000000..0337307 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/util/config/ShateItConfiguration.java @@ -0,0 +1,39 @@ +package ru.practicum.shareit.util.config; + +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; + +import java.time.format.DateTimeFormatter; + +@SpringBootConfiguration +public class ShateItConfiguration { + + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory queryFactory() { + return new JPAQueryFactory(entityManager); + } + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { + String pattern = "yyyy-MM-dd'T'HH:mm:ss"; + return builder -> { + builder.simpleDateFormat(pattern); + builder.serializers(new LocalDateTimeSerializer( + DateTimeFormatter.ofPattern(pattern) + )); + builder.deserializers(new LocalDateTimeDeserializer( + DateTimeFormatter.ofPattern(pattern) + )); + }; + } + +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/util/entity/BaseEntity.java b/server/src/main/java/ru/practicum/shareit/util/entity/BaseEntity.java new file mode 100644 index 0000000..5cdad2f --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/util/entity/BaseEntity.java @@ -0,0 +1,38 @@ +package ru.practicum.shareit.util.entity; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@MappedSuperclass +public abstract class BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + protected Long id; + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + + if (org.hibernate.Hibernate.getClass(this) + != org.hibernate.Hibernate.getClass(o)) { + return false; + } + + BaseEntity other = (BaseEntity) o; + + return id != null && id.equals(other.id); + } + + @Override + public final int hashCode() { + return org.hibernate.Hibernate.getClass(this).hashCode(); + } +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/util/error/ErrorHandler.java b/server/src/main/java/ru/practicum/shareit/util/error/ErrorHandler.java new file mode 100644 index 0000000..a8565d2 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/util/error/ErrorHandler.java @@ -0,0 +1,61 @@ +package ru.practicum.shareit.util.error; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import ru.practicum.shareit.util.exception.*; + +import java.util.UUID; + +@RestControllerAdvice +@Slf4j +public class ErrorHandler { + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler + public ErrorResponse handleNotFound(final NotFoundException e) { + return new ErrorResponse(e.getMessage()); + } + + @ResponseStatus(HttpStatus.FORBIDDEN) + @ExceptionHandler + public ErrorResponse handleSharingForbidden(final ForbiddenException e) { + return new ErrorResponse(e.getMessage()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler + public ErrorResponse handleParameterNotValid(final ParameterNotValidException e) { + return new ErrorResponse("Некорректное значение параметра " + + e.getParameter() + ": " + e.getReason() + ); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler + public ErrorResponse handleBookingStatusException(final BookingStatusException e) { + return new ErrorResponse(e.getMessage()); + } + + @ResponseStatus(HttpStatus.CONFLICT) + @ExceptionHandler + public ErrorResponse handleDuplicatedData(final DuplicatedDataException e) { + return new ErrorResponse(e.getMessage()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler + public ErrorResponse handleValidationException(final ValidationException e) { + return new ErrorResponse(e.getMessage()); + } + + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler + public ErrorResponse handleAnyThrowable(final Throwable e) { + UUID uuid = UUID.randomUUID(); + log.error("UUID={} Message=\"{}.\"", uuid, e.getMessage()); + return new ErrorResponse("Произошла непредвиденная ошибка. UUID=" + uuid); + } +} diff --git a/src/main/java/ru/practicum/shareit/util/error/ErrorResponse.java b/server/src/main/java/ru/practicum/shareit/util/error/ErrorResponse.java similarity index 100% rename from src/main/java/ru/practicum/shareit/util/error/ErrorResponse.java rename to server/src/main/java/ru/practicum/shareit/util/error/ErrorResponse.java diff --git a/src/main/java/ru/practicum/shareit/util/exception/BookingStatusException.java b/server/src/main/java/ru/practicum/shareit/util/exception/BookingStatusException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/util/exception/BookingStatusException.java rename to server/src/main/java/ru/practicum/shareit/util/exception/BookingStatusException.java diff --git a/src/main/java/ru/practicum/shareit/util/exception/DuplicatedDataException.java b/server/src/main/java/ru/practicum/shareit/util/exception/DuplicatedDataException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/util/exception/DuplicatedDataException.java rename to server/src/main/java/ru/practicum/shareit/util/exception/DuplicatedDataException.java diff --git a/src/main/java/ru/practicum/shareit/util/exception/ForbiddenException.java b/server/src/main/java/ru/practicum/shareit/util/exception/ForbiddenException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/util/exception/ForbiddenException.java rename to server/src/main/java/ru/practicum/shareit/util/exception/ForbiddenException.java diff --git a/src/main/java/ru/practicum/shareit/util/exception/NotFoundException.java b/server/src/main/java/ru/practicum/shareit/util/exception/NotFoundException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/util/exception/NotFoundException.java rename to server/src/main/java/ru/practicum/shareit/util/exception/NotFoundException.java diff --git a/src/main/java/ru/practicum/shareit/util/exception/ParameterNotValidException.java b/server/src/main/java/ru/practicum/shareit/util/exception/ParameterNotValidException.java similarity index 86% rename from src/main/java/ru/practicum/shareit/util/exception/ParameterNotValidException.java rename to server/src/main/java/ru/practicum/shareit/util/exception/ParameterNotValidException.java index 9c3dc73..109e986 100644 --- a/src/main/java/ru/practicum/shareit/util/exception/ParameterNotValidException.java +++ b/server/src/main/java/ru/practicum/shareit/util/exception/ParameterNotValidException.java @@ -4,8 +4,8 @@ @Getter public class ParameterNotValidException extends IllegalArgumentException { - String parameter; - String reason; + final String parameter; + final String reason; public ParameterNotValidException(String parameter, String reason) { super(reason); diff --git a/server/src/main/java/ru/practicum/shareit/util/exception/ValidationException.java b/server/src/main/java/ru/practicum/shareit/util/exception/ValidationException.java new file mode 100644 index 0000000..74e5d38 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/util/exception/ValidationException.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.util.exception; + +public class ValidationException extends RuntimeException { + public ValidationException(String message) { + super(message); + } +} diff --git a/server/src/main/resources/application-test.yml b/server/src/main/resources/application-test.yml new file mode 100644 index 0000000..5035758 --- /dev/null +++ b/server/src/main/resources/application-test.yml @@ -0,0 +1,4 @@ +spring: + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:shareit \ No newline at end of file diff --git a/server/src/main/resources/application.yaml b/server/src/main/resources/application.yaml new file mode 100644 index 0000000..82242f3 --- /dev/null +++ b/server/src/main/resources/application.yaml @@ -0,0 +1,50 @@ +server: + port: 9090 + +spring: + main: + banner-mode: OFF + sql: + init: + mode: ALWAYS + jpa: + hibernate: + ddl-auto: validate + properties: + hibernate: + format_sql: true + jackson: + serialization: + INDENT_OUTPUT: true + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://localhost:5432/shareit + username: shareit + password: shareit + +hibernate: +# jdbc: +# time-zone: UTC + default-schema: public + show-sql: true + format-sql: true + +# logging: +# level: +# root: error +# org: +# springframework: boot.web=info +# hibernate: +# sql: DEBUG +# orm: +# jdbc: +# bind: trace +# zalando: +# logbook: TRACE + +logging: + level: + root: info + org: + zalando: + logbook: trace diff --git a/src/main/resources/schema.sql b/server/src/main/resources/schema.sql similarity index 86% rename from src/main/resources/schema.sql rename to server/src/main/resources/schema.sql index 2763290..0c949b2 100644 --- a/src/main/resources/schema.sql +++ b/server/src/main/resources/schema.sql @@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS public.requests ( id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL, description varchar(255) NULL, requestor_id int8 NOT NULL, - created timestamp without time zone NOT NULL, + created timestamp(3) without time zone NOT NULL, CONSTRAINT pk_requests PRIMARY KEY (id), CONSTRAINT fk_requests_users_id FOREIGN KEY (requestor_id) REFERENCES public.users(id) ON DELETE CASCADE ); @@ -31,8 +31,8 @@ CREATE INDEX IF NOT EXISTS idx_items_description ON items(description); CREATE TABLE IF NOT EXISTS public.bookings ( id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL, - start_date timestamp without time zone NOT NULL NOT NULL, - end_date timestamp without time zone NOT NULL, + start_date timestamp(3) without time zone NOT NULL NOT NULL, + end_date timestamp(3) without time zone NOT NULL, item_id bigint NOT NULL, booker_id bigint NOT NULL, status varchar(255) check (status in ('WAITING','APPROVED','REJECTED','CANCELED')), @@ -46,9 +46,9 @@ CREATE TABLE IF NOT EXISTS public.comments ( id bigint GENERATED BY DEFAULT AS IDENTITY NOT NULL, item_id bigint NOT NULL, author_id bigint NOT NULL, - created_date timestamp without time zone NOT NULL, + created_date timestamp(3) without time zone NOT NULL, comment_text VARCHAR(255) NOT NULL, CONSTRAINT pk_comments PRIMARY KEY (id), CONSTRAINT fk_comments_items_id FOREIGN KEY (item_id) REFERENCES public.items(id) ON DELETE CASCADE, - CONSTRAINT fk_comments_users_id FOREIGN KEY (item_id) REFERENCES public.users(id) ON DELETE CASCADE + CONSTRAINT fk_comments_users_id FOREIGN KEY (author_id) REFERENCES public.users(id) ON DELETE CASCADE ); diff --git a/server/src/test/java/ru/practicum/shareit/TestUtils.java b/server/src/test/java/ru/practicum/shareit/TestUtils.java new file mode 100644 index 0000000..b1fcb8d --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/TestUtils.java @@ -0,0 +1,243 @@ +package ru.practicum.shareit; + +import org.hamcrest.Matcher; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.model.BookingStatus; +import ru.practicum.shareit.item.model.Comment; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasProperty; + +public class TestUtils { + + public final LocalDateTime pastDate = LocalDateTime.parse("2023-01-01T00:00:01"); + public final LocalDateTime futureDate = LocalDateTime.parse("2028-01-01T00:00:01"); + + public String generateRandomText(int length, String addAfterStr, Character... addSynbols) { + java.util.List symbols = new ArrayList<>(List.of( + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z' + )); + symbols.addAll(Arrays.asList(addSynbols)); + Random random = new Random(); + StringBuilder sb = new StringBuilder(length); + + for (int i = 0; i < length; i++) { + int index = random.nextInt(symbols.size()); + sb.append(symbols.get(index)); + } + + return sb.append(addAfterStr).toString(); + } + + public User makeNewUser(int id) { + return new User() + .setID((long) id) + .setName(generateRandomText(10, "", ' ')) + .setEmail(generateRandomText(10, "@mail.ru")); + } + + public User makeNewUser(long id) { + return new User() + .setID(id) + .setName(generateRandomText(10, "", ' ')) + .setEmail(generateRandomText(10, "@mail.ru")); + } + + public User makeNewUser() { + return new User() + .setName(generateRandomText(10, "", ' ')) + .setEmail(generateRandomText(10, "@mail.ru")); + } + + public User getCopyOfUser(User user) { + return new User() + .setID(user.getId()) + .setName(user.getName()) + .setEmail(user.getEmail()); + } + + public Matcher matchesUserDto(User user) { + return allOf( + hasProperty("id", notNullValue()), + hasProperty("name", equalTo(user.getName())), + hasProperty("email", equalTo(user.getEmail())) + ); + } + + public Item makeNewItem(int id, User owner, ItemRequest request) { + return new Item() + .setID((long) id) + .setName(generateRandomText(25, "", ' ', ' ')) + .setDescription(generateRandomText(50, "", ' ', ' ', ' ', ' ')) + .setAvailable(true) + .setOwner(owner) + .setRequest(request); + } + + public Item makeNewItem(long id, User owner, ItemRequest request) { + return new Item() + .setID(id) + .setName(generateRandomText(25, "", ' ', ' ')) + .setDescription(generateRandomText(50, "", ' ', ' ', ' ', ' ')) + .setAvailable(true) + .setOwner(owner) + .setRequest(request); + } + + public Item makeNewItem(User owner, ItemRequest request) { + return new Item() + .setName(generateRandomText(25, "", ' ', ' ')) + .setDescription(generateRandomText(50, "", ' ', ' ', ' ', ' ')) + .setAvailable(true) + .setOwner(owner) + .setRequest(request); + } + + public Item makeNewFastItem(int id) { + return makeNewItem(id, makeNewUser(id + 10), makeNewItemRequest(id + 20, makeNewUser(id + 11))); + } + + public ItemRequest makeNewItemRequest(int id, User requestor) { + return new ItemRequest() + .setID((long) id) + .setDescription(generateRandomText(50, "", ' ', ' ', ' ', ' ')) + .setCreatedDate(LocalDateTime.now().minusHours(1)) + .setRequestor(requestor); + } + + public ItemRequest makeNewItemRequest(int id, User requestor, LocalDateTime createdDate) { + return new ItemRequest() + .setID((long) id) + .setDescription(generateRandomText(50, "", ' ', ' ', ' ', ' ')) + .setCreatedDate(createdDate) + .setRequestor(requestor); + } + + public ItemRequest makeNewItemRequest(User requestor, LocalDateTime createdDate) { + return new ItemRequest() + .setDescription(generateRandomText(50, "", ' ', ' ', ' ', ' ')) + .setCreatedDate(createdDate) + .setRequestor(requestor); + } + + public Comment makeNewComment(int id, Item item, User author, LocalDateTime date) { + return new Comment() + .setID((long) id) + .setText(generateRandomText(50, "", ' ', ' ', ' ', ' ')) + .setItem(item) + .setAuthor(author) + .setCreatedDate(date); + } + + public Booking makeNewAnyFullFastBooking(int id, LocalDateTime date, BookingStatus status) { + User owner = makeNewUser(id + 10); + User booker = makeNewUser(id + 11); + Item item = makeNewItem(id + 20, owner, makeNewItemRequest(id + 30, booker, date.plusHours(11))); + return new Booking() + .setID((long) id) + .setItem(item) + .setBooker(booker) + .setStart(date) + .setEnd(date.plusHours(10)) + .setStatus(status); + } + + public Booking makeNewAnyFullFastBooking(int id, LocalDateTime date, BookingStatus status, ItemRequest request) { + User owner = makeNewUser(id + 10); + User booker = makeNewUser(id + 11); + Item item = makeNewItem(id + 20, owner, request); + return new Booking() + .setID((long) id) + .setItem(item) + .setBooker(booker) + .setStart(date) + .setEnd(date.plusHours(10)) + .setStatus(status); + } + + public Booking makeNewAnyFullFastBooking(LocalDateTime date, BookingStatus status, ItemRequest request) { + User owner = makeNewUser(); + User booker = makeNewUser(); + Item item = makeNewItem(owner, request); + return new Booking() + .setItem(item) + .setBooker(booker) + .setStart(date) + .setEnd(date.plusHours(10)) + .setStatus(status); + } + + public Booking makeNewAnyFullFastBooking(long id, LocalDateTime date, BookingStatus status, ItemRequest request) { + User owner = makeNewUser(id + 10); + User booker = makeNewUser(id + 11); + Item item = makeNewItem(id + 20, owner, request); + return new Booking() + .setID(id) + .setItem(item) + .setBooker(booker) + .setStart(date) + .setEnd(date.plusHours(10)) + .setStatus(status); + } + + public Booking makeNewAnyFullBooking(int id, User booker, User owner, LocalDateTime date, BookingStatus status) { + Item item = makeNewItem(id + 20, owner, makeNewItemRequest(id + 30, booker)); + return new Booking() + .setID((long) id) + .setItem(item) + .setBooker(booker) + .setStart(date) + .setEnd(date.plusHours(10)) + .setStatus(status); + } + + public Item makeCopyOfItem(Item item) { + return new Item() + .setID(item.getId()) + .setName(item.getName()) + .setDescription(item.getDescription()) + .setAvailable(item.isAvailable()) + .setOwner(item.getOwner()) + .setRequest(item.getRequest()); + } + + public Booking makeCopyOfBooking(Booking booking) { + return new Booking() + .setID(booking.getId()) + .setItem(booking.getItem()) + .setBooker(booking.getBooker()) + .setStart(booking.getStart()) + .setEnd(booking.getEnd()) + .setStatus(booking.getStatus()); + } + + public ItemRequest makeCopyOfRequest(ItemRequest request) { + return new ItemRequest() + .setID(request.getId()) + .setDescription(request.getDescription()) + .setCreatedDate(request.getCreatedDate()) + .setRequestor(request.getRequestor()); + } + + public Comment makeCopyOfComment(Comment comment) { + return new Comment() + .setID(comment.getId()) + .setText(comment.getText()) + .setItem(comment.getItem()) + .setAuthor(comment.getAuthor()) + .setCreatedDate(comment.getCreatedDate()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/BookingContextTest.java b/server/src/test/java/ru/practicum/shareit/booking/BookingContextTest.java new file mode 100644 index 0000000..2b9b9a3 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingContextTest.java @@ -0,0 +1,166 @@ +package ru.practicum.shareit.booking; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.EntityManagerFactory; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import ru.practicum.shareit.TestUtils; +import ru.practicum.shareit.booking.dto.IncomingBookingDto; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.model.BookingStatus; +import ru.practicum.shareit.booking.repository.BookingRepository; +import ru.practicum.shareit.item.repository.CommentRepository; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.request.repository.QueryDslRepository; +import ru.practicum.shareit.user.repository.UserRepository; +import ru.practicum.shareit.util.error.ErrorHandler; +import ru.practicum.shareit.util.error.ErrorResponse; + +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@EnableAutoConfiguration(exclude = { + DataSourceAutoConfiguration.class, + HibernateJpaAutoConfiguration.class +}) +public class BookingContextTest { + + @Autowired + ObjectMapper mapper; + + @Autowired + private MockMvc mvc; + + @Autowired + BookingController controller; + + // region mocks + @MockitoBean + UserRepository userRepository; + + @MockitoBean + ItemRepository itemRepository; + + @MockitoBean + ItemRequestRepository itemRequestRepository; + + @MockitoBean + BookingRepository bookingRepository; + + @MockitoBean + CommentRepository commentRepository; + + @MockitoBean + QueryDslRepository queryDslRepository; + + @MockitoBean + EntityManagerFactory entityManagerFactory; + // endregion mocks + + final TestUtils testUtils = new TestUtils(); + + @BeforeEach + void setUp() { + mvc = MockMvcBuilders + .standaloneSetup(controller) + .setControllerAdvice(new ErrorHandler()) + .setMessageConverters(new MappingJackson2HttpMessageConverter(mapper)) + .build(); + } + + @Test + @SneakyThrows + void approveBooking_whenBookingWasCanseled_thenReturnErrorResponse() { + // region setup + long bookingId = 1L; + Booking approovedBooking = testUtils.makeNewAnyFullFastBooking( + bookingId, + testUtils.futureDate, + BookingStatus.APPROVED, + null + ); + Long ownerId = approovedBooking.getItem().getOwner().getId(); + Booking inBaseBooking = testUtils.makeCopyOfBooking(approovedBooking).setStatus(BookingStatus.CANCELED); + // endregion setup + + when(userRepository.existsById(ownerId)).thenReturn(true); + when(bookingRepository.findById(bookingId)).thenReturn(Optional.of(inBaseBooking)); + + String result = mvc.perform( + patch("/bookings/{bookingId}?approved={approved}", bookingId, true) + .header("X-Sharer-User-Id", ownerId)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isBadRequest()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertThat(result, is( + mapper.writeValueAsString(new ErrorResponse("Бронирование уже было отменено создателем")) + )); + + verify(bookingRepository, never()).save(any(Booking.class)); + } + + @Test + @SneakyThrows + void addNewBooking_whenItenIsNotApprouved_thenReturnErrorResponse() { + Booking booking = testUtils.makeNewAnyFullFastBooking( + 1, + testUtils.futureDate, + BookingStatus.WAITING, + null + ); + booking.getItem().setAvailable(false); + IncomingBookingDto incomingBookingDto = new IncomingBookingDto( + booking.getItem().getId(), booking.getStart(), booking.getEnd()); + + when(userRepository.findById(booking.getBooker().getId())).thenReturn(Optional.of(booking.getBooker())); + when(itemRepository.findById(booking.getItem().getId())).thenReturn(Optional.of(booking.getItem())); + + String result = mvc.perform( + post("/bookings") + .header("X-Sharer-User-Id", booking.getBooker().getId()) + .content(mapper.writeValueAsString(incomingBookingDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isBadRequest()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertThat(result, is( + mapper.writeValueAsString( + new ErrorResponse("Вещь с id=" + booking.getItem().getId() + " не доступна для аренды")) + )); + + verify(bookingRepository, never()).save(any(Booking.class)); + } +} 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..29df280 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/BookingControllerTest.java @@ -0,0 +1,195 @@ +package ru.practicum.shareit.booking; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import ru.practicum.shareit.TestUtils; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.dto.BookingMapper; +import ru.practicum.shareit.booking.dto.IncomingBookingDto; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.model.BookingStatus; +import ru.practicum.shareit.booking.model.StateOfBooking; +import ru.practicum.shareit.booking.service.BookingService; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.util.UtilService; +import ru.practicum.shareit.util.error.ErrorHandler; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = BookingController.class) +public class BookingControllerTest { + + @Autowired + ObjectMapper mapper; + + @Autowired + MockMvc mvc; + + @Autowired + BookingController bookingController; + + @MockitoBean + BookingService bookingService; + + @MockitoBean + UtilService utilService; + + final TestUtils testUtils = new TestUtils(); + + @BeforeEach + void setUp() { + mvc = MockMvcBuilders + .standaloneSetup(bookingController) + .setControllerAdvice(new ErrorHandler()) + .setMessageConverters(new MappingJackson2HttpMessageConverter(mapper)) + .build(); + } + + @Test + @SneakyThrows + void addNewBooking_whenBookingIsValid() { + Booking booking = testUtils.makeNewAnyFullFastBooking(1, testUtils.pastDate, BookingStatus.APPROVED); + BookingDto bookingDto = BookingMapper.toBookingDto(booking); + IncomingBookingDto incomingBookingDto = new IncomingBookingDto( + booking.getItem().getId(), booking.getStart(), booking.getEnd()); + + when(bookingService.addNewBooking(booking.getBooker().getId(), incomingBookingDto)).thenReturn(bookingDto); + + String result = mvc.perform(post("/bookings") + .header("X-Sharer-User-Id", booking.getBooker().getId()) + .content(mapper.writeValueAsString(incomingBookingDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertThat(result, is(mapper.writeValueAsString(bookingDto))); + } + + @Test + @SneakyThrows + void getBooking_whenBookingIsValid() { + Booking booking = testUtils.makeNewAnyFullFastBooking(1, testUtils.pastDate, BookingStatus.APPROVED); + BookingDto bookingDto = BookingMapper.toBookingDto(booking); + + when(bookingService.getBooking(booking.getBooker().getId(), booking.getId())).thenReturn(bookingDto); + + String result = mvc.perform(get("/bookings/{bookingId}", booking.getId()) + .header("X-Sharer-User-Id", booking.getBooker().getId())) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertThat(result, is(mapper.writeValueAsString(bookingDto))); + } + + @Test + @SneakyThrows + void getAllBookingsByBooker_whenBookingIsValid() { + Booking booking1 = testUtils.makeNewAnyFullFastBooking(1, testUtils.pastDate, BookingStatus.APPROVED); + Booking booking2 = testUtils.makeNewAnyFullFastBooking(2, testUtils.pastDate, BookingStatus.APPROVED); + booking2.setBooker(booking1.getBooker()); + List bookingDtos = List.of( + BookingMapper.toBookingDto(booking1), + BookingMapper.toBookingDto(booking2) + ); + StateOfBooking state = StateOfBooking.ALL; + + when(bookingService.getAllBookingsByBooker(booking1.getBooker().getId(), String.valueOf(state))) + .thenReturn(bookingDtos); + + String result = mvc.perform(get("/bookings?state={state}", state) + .header("X-Sharer-User-Id", booking1.getBooker().getId())) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertThat(result, is(mapper.writeValueAsString(bookingDtos))); + } + + @Test + @SneakyThrows + void getAllBookingsByOwner_whenBookingIsValid() { + User owner = testUtils.makeNewUser(1); + Booking booking1 = testUtils.makeNewAnyFullBooking( + 10, + testUtils.makeNewUser(2), + owner, + testUtils.pastDate, + BookingStatus.APPROVED + ); + Booking booking2 = testUtils.makeNewAnyFullBooking( + 11, + testUtils.makeNewUser(3), + owner, + testUtils.futureDate, + BookingStatus.APPROVED + ); + List bookingDtos = List.of( + BookingMapper.toBookingDto(booking1), + BookingMapper.toBookingDto(booking2) + ); + StateOfBooking state = StateOfBooking.ALL; + + when(bookingService.getAllBookingsByOwner(owner.getId(), String.valueOf(state))) + .thenReturn(bookingDtos); + + String result = mvc.perform(get("/bookings/owner?state={state}", state) + .header("X-Sharer-User-Id", owner.getId())) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertThat(result, is(mapper.writeValueAsString(bookingDtos))); + } + + @Test + @SneakyThrows + void approveBooking_whenBookingIsValid() { + Booking booking = testUtils.makeNewAnyFullFastBooking(1, testUtils.pastDate, BookingStatus.APPROVED); + BookingDto bookingDto = BookingMapper.toBookingDto(booking); + + + when(bookingService.approveBooking(booking.getItem().getOwner().getId(), booking.getId(), true)) + .thenReturn(bookingDto); + + String result = mvc.perform( + patch("/bookings/{bookingId}?approved={approved}", booking.getId(), true) + .header("X-Sharer-User-Id", booking.getItem().getOwner().getId())) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertThat(result, is(mapper.writeValueAsString(bookingDto))); + } + +} diff --git a/server/src/test/java/ru/practicum/shareit/booking/repository/BookingRepositoryTest.java b/server/src/test/java/ru/practicum/shareit/booking/repository/BookingRepositoryTest.java new file mode 100644 index 0000000..c7dd49e --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/repository/BookingRepositoryTest.java @@ -0,0 +1,369 @@ +package ru.practicum.shareit.booking.repository; + +import com.sun.istack.NotNull; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import ru.practicum.shareit.TestUtils; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.model.BookingStatus; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Set; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +@DataJpaTest +@Transactional +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class BookingRepositoryTest { + + final TestUtils testUtils = new TestUtils(); + + final EntityManager entityManager; + + final UserRepository userRepository; + final ItemRequestRepository requestRepository; + final ItemRepository itemRepository; + final BookingRepository bookingRepository; + + private User getCheckedUser(@NotNull User user) { + return user.getId() == null ? + userRepository.save(user) : + entityManager.merge(user); + } + + private Item getCheckedItem(@NotNull Item item) { + return item.getId() == null ? + magicSaveItem(item) : + entityManager.merge(item); + } + + private ItemRequest getCheckedRequest(ItemRequest request) { + return request.getId() == null ? + magicSaveRequest(request) : + entityManager.merge(request); + } + + private Booking magicSaveBooking(Booking booking) { + return bookingRepository.save(new Booking() + .setBooker(getCheckedUser(booking.getBooker())) + .setItem(getCheckedItem(booking.getItem())) + .setStart(booking.getStart()) + .setEnd(booking.getEnd()) + .setStatus(booking.getStatus()) + ); + } + + private Item magicSaveItem(Item item) { + return itemRepository.save( + new Item() + .setName(item.getName()) + .setDescription(item.getDescription()) + .setAvailable(item.isAvailable()) + .setOwner(getCheckedUser(item.getOwner())) + .setRequest(item.getRequest() == null ? null : getCheckedRequest(item.getRequest())) + ); + } + + private ItemRequest magicSaveRequest(ItemRequest request) { + return requestRepository.save( + new ItemRequest() + .setRequestor(getCheckedUser(request.getRequestor())) + .setDescription(request.getDescription()) + .setCreatedDate(request.getCreatedDate()) + ); + } + + Booking saveNewMagicBooking(LocalDateTime date, BookingStatus status) { + return magicSaveBooking( + testUtils.makeNewAnyFullFastBooking(date, status, null)); + } + + private Matcher matchesBooking(Booking booking) { + return allOf( + hasProperty("id", notNullValue()), + hasProperty("booker", equalTo(booking.getBooker())), + hasProperty("item", equalTo(booking.getItem())), + hasProperty("start", equalTo(booking.getStart())), + hasProperty("end", equalTo(booking.getEnd())), + hasProperty("status", equalTo(booking.getStatus())) + ); + } + + @Test + void findAllByOwner() { + // region setup + List bookings = List.of( + saveNewMagicBooking(LocalDateTime.now(), BookingStatus.WAITING), + saveNewMagicBooking(LocalDateTime.now(), BookingStatus.WAITING), + saveNewMagicBooking(LocalDateTime.now(), BookingStatus.WAITING) + ); + User owner = bookings.get(0).getItem().getOwner(); + entityManager.merge(bookings.get(1).getItem().setOwner(owner)); + // endregion setup + + List bookingList = bookingRepository.findAllByOwner(owner.getId()); + + assertThat(bookingList, hasSize(2)); + assertThat(bookingList, contains(matchesBooking(bookings.get(1)), matchesBooking(bookings.get(0)))); + } + + @Test + void findCurrentByOwner() { + // region setup + List bookings = List.of( + saveNewMagicBooking(LocalDateTime.now().plusDays(1), BookingStatus.WAITING), // future + saveNewMagicBooking(LocalDateTime.now().minusHours(5), BookingStatus.WAITING), // current + saveNewMagicBooking(LocalDateTime.now().minusHours(5), BookingStatus.WAITING) // current + ); + User owner = bookings.get(0).getItem().getOwner(); + entityManager.merge(bookings.get(1).getItem().setOwner(owner)); + // endregion setup + + List bookingList = bookingRepository.findCurrentByOwner(owner.getId()); + + assertThat(bookingList, hasSize(1)); + assertThat(bookingList, contains(matchesBooking(bookings.get(1)))); + } + + @Test + void findPastByOwner() { + // region setup + List bookings = List.of( + saveNewMagicBooking(LocalDateTime.now().plusDays(1), BookingStatus.WAITING), // future + saveNewMagicBooking(LocalDateTime.now().minusDays(5), BookingStatus.WAITING), // past + saveNewMagicBooking(LocalDateTime.now().minusDays(5), BookingStatus.WAITING) // past + ); + User owner = bookings.get(0).getItem().getOwner(); + entityManager.merge(bookings.get(1).getItem().setOwner(owner)); + // endregion setup + + List bookingList = bookingRepository.findPastByOwner(owner.getId()); + + assertThat(bookingList, hasSize(1)); + assertThat(bookingList, contains(matchesBooking(bookings.get(1)))); + } + + @Test + void findFutureByOwner() { + // region setup + List bookings = List.of( + saveNewMagicBooking(LocalDateTime.now().plusDays(1), BookingStatus.WAITING), // future + saveNewMagicBooking(LocalDateTime.now().minusDays(5), BookingStatus.WAITING), // past + saveNewMagicBooking(LocalDateTime.now().minusDays(5), BookingStatus.WAITING) // past + ); + User owner = bookings.get(0).getItem().getOwner(); + entityManager.merge(bookings.get(1).getItem().setOwner(owner)); + // endregion setup + + List bookingList = bookingRepository.findFutureByOwner(owner.getId()); + + assertThat(bookingList, hasSize(1)); + assertThat(bookingList, contains(matchesBooking(bookings.getFirst()))); + } + + @Test + void findAllByOwnerAndStatusOrderByStartDesc() { + // region setup + List bookings = List.of( + saveNewMagicBooking(LocalDateTime.now().plusDays(1), BookingStatus.WAITING), // future + saveNewMagicBooking(LocalDateTime.now().minusDays(5), BookingStatus.WAITING), // past + saveNewMagicBooking(LocalDateTime.now().minusDays(5), BookingStatus.REJECTED) // past + ); + User owner = bookings.get(0).getItem().getOwner(); + entityManager.merge(bookings.get(1).getItem().setOwner(owner)); + // endregion setup + + List bookingList = bookingRepository + .findAllByOwnerAndStatusOrderByStartDesc(owner.getId(), BookingStatus.WAITING); + + assertThat(bookingList, hasSize(2)); + assertThat(bookingList, contains(matchesBooking(bookings.get(0)), matchesBooking(bookings.get(1)))); + } + + @Test + void findCurrentByBooker() { + // region setup + List bookings = List.of( + saveNewMagicBooking(LocalDateTime.now().plusDays(1), BookingStatus.WAITING), // future + saveNewMagicBooking(LocalDateTime.now().minusHours(5), BookingStatus.WAITING), // current + saveNewMagicBooking(LocalDateTime.now().minusHours(5), BookingStatus.WAITING) // current + ); + User booker = bookings.get(0).getBooker(); + entityManager.merge(bookings.get(1).setBooker(booker)); + // endregion setup + + List bookingList = bookingRepository.findCurrentByBooker(booker.getId()); + + assertThat(bookingList, hasSize(1)); + assertThat(bookingList, contains(matchesBooking(bookings.get(1)))); + } + + @Test + void findPastByBooker() { + // region setup + List bookings = List.of( + saveNewMagicBooking(LocalDateTime.now().plusDays(1), BookingStatus.WAITING), // future + saveNewMagicBooking(LocalDateTime.now().minusDays(5), BookingStatus.WAITING), // past + saveNewMagicBooking(LocalDateTime.now().minusDays(5), BookingStatus.WAITING) // past + ); + User booker = bookings.get(0).getBooker(); + entityManager.merge(bookings.get(1).setBooker(booker)); + // endregion setup + + List bookingList = bookingRepository.findPastByBooker(booker.getId()); + + assertThat(bookingList, hasSize(1)); + assertThat(bookingList, contains(matchesBooking(bookings.get(1)))); + } + + @Test + void findFutureByBooker() { + // region setup + List bookings = List.of( + saveNewMagicBooking(LocalDateTime.now().plusDays(1), BookingStatus.WAITING), // future + saveNewMagicBooking(LocalDateTime.now().minusDays(5), BookingStatus.WAITING), // past + saveNewMagicBooking(LocalDateTime.now().minusDays(5), BookingStatus.WAITING) // past + ); + User booker = bookings.get(0).getBooker(); + entityManager.merge(bookings.get(1).setBooker(booker)); + // endregion setup + + List bookingList = bookingRepository.findFutureByBooker(booker.getId()); + + assertThat(bookingList, hasSize(1)); + assertThat(bookingList, contains(matchesBooking(bookings.getFirst()))); + } + + @Test + void findLastBookingDate() { + // region setup + List bookings = List.of( + saveNewMagicBooking(LocalDateTime.now().plusDays(1), BookingStatus.WAITING), // future + saveNewMagicBooking(LocalDateTime.now().minusDays(5), BookingStatus.WAITING), // past + saveNewMagicBooking(LocalDateTime.now().minusDays(6), BookingStatus.WAITING) // past + ); + Item item = bookings.get(0).getItem(); + entityManager.merge(bookings.get(1).setItem(item)); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); + // endregion setup + LocalDateTime date = bookingRepository.findLastBookingDate(item.getId()); + + assertThat(date.truncatedTo(ChronoUnit.SECONDS).toString(), + equalTo(bookings.get(1).getStart().truncatedTo(ChronoUnit.SECONDS).format(formatter)) + ); + } + + @Test + void findNextBookingDate() { + // region setup + List bookings = List.of( + saveNewMagicBooking(LocalDateTime.now().minusDays(1), BookingStatus.WAITING), // past + saveNewMagicBooking(LocalDateTime.now().plusDays(5), BookingStatus.WAITING), // future + saveNewMagicBooking(LocalDateTime.now().plusDays(4), BookingStatus.WAITING) // future + ); + Item item = bookings.get(0).getItem(); + entityManager.merge(bookings.get(1).setItem(item)); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); + // endregion setup + LocalDateTime date = bookingRepository.findNextBookingDate(item.getId()); + + assertThat(date.truncatedTo(ChronoUnit.SECONDS).toString(), + equalTo(bookings.get(1).getStart().truncatedTo(ChronoUnit.SECONDS).format(formatter)) + ); + } + + @Test + void findAllByBookerIdOrderByStartDesc() { + // region setup + List bookings = List.of( + saveNewMagicBooking(LocalDateTime.now().minusDays(1), BookingStatus.WAITING), // past + saveNewMagicBooking(LocalDateTime.now().plusDays(5), BookingStatus.WAITING), // future + saveNewMagicBooking(LocalDateTime.now().plusDays(4), BookingStatus.WAITING) // future + ); + User booker = bookings.get(0).getBooker(); + entityManager.merge(bookings.get(1).setBooker(booker)); + // endregion setup + + List bookingList = bookingRepository.findAllByBookerIdOrderByStartDesc(booker.getId()); + + assertThat(bookingList, hasSize(2)); + assertThat(bookingList, contains(matchesBooking(bookings.get(1)), matchesBooking(bookings.get(0)))); + } + + @Test + void findAllByBooker_IdAndStatusOrderByStartDesc() { + // region setup + List bookings = List.of( + saveNewMagicBooking(LocalDateTime.now().plusDays(1), BookingStatus.WAITING), // future + saveNewMagicBooking(LocalDateTime.now().minusDays(5), BookingStatus.WAITING), // past + saveNewMagicBooking(LocalDateTime.now().minusDays(5), BookingStatus.REJECTED) // past + ); + User booker = bookings.get(0).getBooker(); + entityManager.merge(bookings.get(1).setBooker(booker)); + // endregion setup + + List bookingList = bookingRepository + .findAllByBooker_IdAndStatusOrderByStartDesc(booker.getId(), BookingStatus.WAITING); + + assertThat(bookingList, hasSize(2)); + assertThat(bookingList, contains(matchesBooking(bookings.get(0)), matchesBooking(bookings.get(1)))); + } + + @Test + void findAllBookingByItem_Id() { + // region setup + List bookings = List.of( + saveNewMagicBooking(LocalDateTime.now().plusDays(1), BookingStatus.WAITING), // future + saveNewMagicBooking(LocalDateTime.now().minusDays(5), BookingStatus.WAITING), // past + saveNewMagicBooking(LocalDateTime.now().minusDays(6), BookingStatus.WAITING) // past + ); + Item item = bookings.get(0).getItem(); + entityManager.merge(bookings.get(1).setItem(item)); + // endregion setup + + List bookingList = bookingRepository.findAllBookingByItem_Id(item.getId()); + + assertThat(bookingList, hasSize(2)); + assertThat(bookingList, contains(matchesBooking(bookings.get(0)), matchesBooking(bookings.get(1)))); + } + + @Test + void findAllBookingByItemIdIn() { + // region setup + List bookings = List.of( + saveNewMagicBooking(LocalDateTime.now().plusDays(1), BookingStatus.WAITING), // future + saveNewMagicBooking(LocalDateTime.now().minusDays(5), BookingStatus.WAITING), // past + saveNewMagicBooking(LocalDateTime.now().minusDays(6), BookingStatus.WAITING), // past + saveNewMagicBooking(LocalDateTime.now().minusDays(6), BookingStatus.WAITING) // past + ); + Item item1 = bookings.get(0).getItem(); + entityManager.merge(bookings.get(1).setItem(item1)); + Item item2 = bookings.get(2).getItem(); + // endregion setup + + List bookingList = bookingRepository.findAllBookingByItemIdIn(Set.of(item1.getId(), item2.getId())); + + assertThat(bookingList, hasSize(3)); + assertThat(bookingList, contains( + matchesBooking(bookings.get(0)), + matchesBooking(bookings.get(1)), + matchesBooking(bookings.get(2)) + )); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java b/server/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java new file mode 100644 index 0000000..f1627dd --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/booking/service/BookingServiceImplTest.java @@ -0,0 +1,943 @@ +package ru.practicum.shareit.booking.service; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import ru.practicum.shareit.TestUtils; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.dto.BookingMapper; +import ru.practicum.shareit.booking.dto.IncomingBookingDto; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.model.BookingStatus; +import ru.practicum.shareit.booking.repository.BookingRepository; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.util.UtilService; +import ru.practicum.shareit.util.exception.*; + +import java.time.LocalDateTime; +import java.util.*; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class BookingServiceImplTest { + + @Mock + UtilService utilService; + + @Mock + BookingRepository bookingRepository; + + @InjectMocks + BookingServiceImpl bookingService; + + final TestUtils testUtils = new TestUtils(); + + @Nested + class AddNewBooking { + + @Test + void whenAllIsOk_thenReturnActualBookingDto() { + // region setup + Booking booking = testUtils.makeNewAnyFullFastBooking( + 1, + testUtils.futureDate, + BookingStatus.WAITING, + null + ); + BookingDto expectedDto = BookingMapper.toBookingDto(booking); + IncomingBookingDto incomingBookingDto = new IncomingBookingDto( + booking.getItem().getId(), + booking.getStart(), + booking.getEnd() + ); + // endregion setup + + when(utilService.getUser(booking.getBooker().getId())).thenReturn(booking.getBooker()); + when(utilService.getItem(booking.getItem().getId())).thenReturn(booking.getItem()); + when(bookingRepository.save(any(Booking.class))).thenReturn(booking); + ArgumentCaptor captor = ArgumentCaptor.forClass(Booking.class); + + BookingDto resultDto = bookingService.addNewBooking( + booking.getBooker().getId(), + incomingBookingDto + ); + + verify(bookingRepository).save(captor.capture()); + Booking captured = captor.getValue(); + + assertEquals(booking.getStart(), captured.getStart()); + assertEquals(booking.getEnd(), captured.getEnd()); + assertEquals(booking.getItem(), captured.getItem()); + assertEquals(booking.getBooker(), captured.getBooker()); + assertEquals(BookingStatus.WAITING, captured.getStatus()); + assertThat(resultDto, equalTo(expectedDto)); + } + + @Test + void whenBookerIsEqualOwner_thenReturnValidationException() { + // region setup + Booking booking = testUtils.makeNewAnyFullFastBooking( + 1, + testUtils.futureDate, + BookingStatus.WAITING, + null + ); + + // Breaking it here + booking.setBooker(booking.getItem().getOwner()); + + IncomingBookingDto incomingBookingDto = new IncomingBookingDto( + booking.getItem().getId(), booking.getStart(), booking.getEnd()); + // endregion setup + + when(utilService.getUser(booking.getBooker().getId())).thenReturn(booking.getBooker()); + when(utilService.getItem(booking.getItem().getId())).thenReturn(booking.getItem()); + + assertThrows(ValidationException.class, () -> + bookingService.addNewBooking(booking.getBooker().getId(), incomingBookingDto) + ); + + verify(bookingRepository, never()).save(any(Booking.class)); + } + + @Test + void whenItemIsNotAvaialble_thenReturnValidationException() { + // region setup + Booking booking = testUtils.makeNewAnyFullFastBooking( + 1, + testUtils.futureDate, + BookingStatus.WAITING, + null + ); + + // Breaking it here + booking.setItem(booking.getItem().setAvailable(false)); + + IncomingBookingDto incomingBookingDto = new IncomingBookingDto( + booking.getItem().getId(), booking.getStart(), booking.getEnd()); + // endregion setup + + when(utilService.getUser(booking.getBooker().getId())).thenReturn(booking.getBooker()); + when(utilService.getItem(booking.getItem().getId())).thenReturn(booking.getItem()); + + assertThrows(ValidationException.class, () -> + bookingService.addNewBooking(booking.getBooker().getId(), incomingBookingDto) + ); + + verify(bookingRepository, never()).save(any(Booking.class)); + } + } + + @Nested + class ApproveBooking { + + @Test + void whenApproveIsTrue_andUserIsOwner_thenReturnApprovedBookingDto() { + // region setup + long bookingId = 1L; + Booking approovedBooking = testUtils.makeNewAnyFullFastBooking( + bookingId, + testUtils.futureDate, + BookingStatus.APPROVED, + null + ); + Long ownerId = approovedBooking.getItem().getOwner().getId(); + Booking inBaseBooking = testUtils.makeCopyOfBooking(approovedBooking).setStatus(BookingStatus.WAITING); + BookingDto bookingDto = BookingMapper.toBookingDto(approovedBooking); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findById(bookingId)).thenReturn(Optional.of(inBaseBooking)); + when(bookingRepository.save(inBaseBooking)).thenReturn(approovedBooking); + + BookingDto resultDto = bookingService + .approveBooking(ownerId, bookingId, true); + + assertThat(resultDto, equalTo(bookingDto)); + } + + @Test + void whenApproveIsTrue_andUserIsOwner_andBookingAlredyApprouved_thenReturnApprovedBookingDto() { + // region setup + long bookingId = 1L; + Booking approovedBooking = testUtils.makeNewAnyFullFastBooking( + bookingId, + testUtils.futureDate, + BookingStatus.APPROVED, + null + ); + Long ownerId = approovedBooking.getItem().getOwner().getId(); + Booking inBaseBooking = testUtils.makeCopyOfBooking(approovedBooking).setStatus(BookingStatus.APPROVED); + BookingDto bookingDto = BookingMapper.toBookingDto(approovedBooking); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findById(bookingId)).thenReturn(Optional.of(inBaseBooking)); + when(bookingRepository.save(inBaseBooking)).thenReturn(approovedBooking); + + BookingDto resultDto = bookingService + .approveBooking(ownerId, bookingId, true); + + assertThat(resultDto, equalTo(bookingDto)); + } + + @Test + void whenApproveIsFalse_andUserIsOwner_thenReturnRejectedBookingDto() { + // region setup + long bookingId = 1L; + Booking approovedBooking = testUtils.makeNewAnyFullFastBooking( + bookingId, + testUtils.futureDate, + BookingStatus.REJECTED, + null + ); + Long ownerId = approovedBooking.getItem().getOwner().getId(); + Booking inBaseBooking = testUtils.makeCopyOfBooking(approovedBooking).setStatus(BookingStatus.WAITING); + BookingDto bookingDto = BookingMapper.toBookingDto(approovedBooking); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findById(bookingId)).thenReturn(Optional.of(inBaseBooking)); + when(bookingRepository.save(inBaseBooking)).thenReturn(approovedBooking); + + BookingDto resultDto = bookingService + .approveBooking(ownerId, bookingId, false); + + assertThat(resultDto, equalTo(bookingDto)); + } + + @Test + void whenApproveIsTrue_butBookingWasRejected_thenStatusWillChanged_andReturnApprovedBookingDto() { + // region setup + long bookingId = 1L; + Booking approovedBooking = testUtils.makeNewAnyFullFastBooking( + bookingId, + testUtils.futureDate, + BookingStatus.APPROVED, + null + ); + Long ownerId = approovedBooking.getItem().getOwner().getId(); + Booking inBaseBooking = testUtils.makeCopyOfBooking(approovedBooking).setStatus(BookingStatus.REJECTED); + BookingDto bookingDto = BookingMapper.toBookingDto(approovedBooking); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findById(bookingId)).thenReturn(Optional.of(inBaseBooking)); + when(bookingRepository.save(inBaseBooking)).thenReturn(approovedBooking); + + BookingDto resultDto = bookingService + .approveBooking(ownerId, bookingId, true); + + assertThat(resultDto, equalTo(bookingDto)); + } + + @Test + void whenApproveIsFalse_andBookingWasRejected_thenStatusWillChanged_andReturnRejectedBookingDto() { + // region setup + long bookingId = 1L; + Booking approovedBooking = testUtils.makeNewAnyFullFastBooking( + bookingId, + testUtils.futureDate, + BookingStatus.REJECTED, + null + ); + Long ownerId = approovedBooking.getItem().getOwner().getId(); + Booking inBaseBooking = testUtils.makeCopyOfBooking(approovedBooking).setStatus(BookingStatus.REJECTED); + BookingDto bookingDto = BookingMapper.toBookingDto(approovedBooking); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findById(bookingId)).thenReturn(Optional.of(inBaseBooking)); + when(bookingRepository.save(inBaseBooking)).thenReturn(approovedBooking); + + BookingDto resultDto = bookingService + .approveBooking(ownerId, bookingId, false); + + assertThat(resultDto, equalTo(bookingDto)); + } + + @Test + void whenApproveIsTrue_butBookingIsCanceled_thenReturnBookingStatusException() { + // region setup + long bookingId = 1L; + Booking approovedBooking = testUtils.makeNewAnyFullFastBooking( + bookingId, + testUtils.futureDate, + BookingStatus.APPROVED, + null + ); + Long ownerId = approovedBooking.getItem().getOwner().getId(); + Booking inBaseBooking = testUtils.makeCopyOfBooking(approovedBooking).setStatus(BookingStatus.CANCELED); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findById(bookingId)).thenReturn(Optional.of(inBaseBooking)); + + assertThrows(BookingStatusException.class, () -> bookingService + .approveBooking(ownerId, bookingId, true)); + + verify(bookingRepository, never()).save(any(Booking.class)); + } + + @Test + void whenUserIsNotAnOwnerAndNotABooker_thenReturnForbiddenException() { + // region setup + long bookingId = 1L; + Booking approovedBooking = testUtils.makeNewAnyFullFastBooking( + bookingId, + testUtils.futureDate, + BookingStatus.APPROVED, + null + ); + Long ownerId = approovedBooking.getItem().getOwner().getId(); + approovedBooking.setItem(approovedBooking.getItem().setOwner(testUtils.makeNewUser(500))); + approovedBooking.setBooker(testUtils.makeNewUser(501)); + Booking inBaseBooking = testUtils.makeCopyOfBooking(approovedBooking).setStatus(BookingStatus.WAITING); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findById(bookingId)).thenReturn(Optional.of(inBaseBooking)); + + assertThrows(ForbiddenException.class, () -> bookingService + .approveBooking(ownerId, bookingId, true)); + + verify(bookingRepository, never()).save(any(Booking.class)); + } + + @Test + void whenBookingIsLost_thenReturnNotFoundException() { + // region setup + long bookingId = 1L; + Booking approovedBooking = testUtils.makeNewAnyFullFastBooking( + bookingId, + testUtils.futureDate, + BookingStatus.APPROVED, + null + ); + Long ownerId = approovedBooking.getItem().getOwner().getId(); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findById(bookingId)).thenReturn(Optional.empty()); + + assertThrows(NotFoundException.class, () -> bookingService + .approveBooking(ownerId, bookingId, true)); + + verify(bookingRepository, never()).save(any(Booking.class)); + } + + } + + @Nested + class GetBooking { + + @Test + void whenUserIsOwner_thenReturnActualBookingDto() { + // region setup + long bookingId = 1L; + Booking booking = testUtils.makeNewAnyFullFastBooking( + bookingId, + testUtils.futureDate, + BookingStatus.APPROVED, + null + ); + Long ownerId = booking.getItem().getOwner().getId(); + BookingDto bookingDto = BookingMapper.toBookingDto(booking); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findById(bookingId)).thenReturn(Optional.of(booking)); + + BookingDto resultDto = bookingService.getBooking(ownerId, bookingId); + + assertThat(resultDto, equalTo(bookingDto)); + } + + @Test + void whenUserIsBooker_thenReturnActualBookingDto() { + // region setup + long bookingId = 1L; + Booking booking = testUtils.makeNewAnyFullFastBooking( + bookingId, + testUtils.futureDate, + BookingStatus.APPROVED, + null + ); + Long bookerId = booking.getBooker().getId(); + BookingDto bookingDto = BookingMapper.toBookingDto(booking); + // endregion setup + + doNothing().when(utilService).checkUser(bookerId); + when(bookingRepository.findById(bookingId)).thenReturn(Optional.of(booking)); + + BookingDto resultDto = bookingService.getBooking(bookerId, bookingId); + + assertThat(resultDto, equalTo(bookingDto)); + } + + @Test + void whenUserIsNotBooker_andUserIsNotOwner_thenReturnForbiddenException() { + // region setup + long bookingId = 1L; + Booking booking = testUtils.makeNewAnyFullFastBooking( + bookingId, + testUtils.futureDate, + BookingStatus.APPROVED, + null + ); + Long userId = 500L; + // endregion setup + + doNothing().when(utilService).checkUser(userId); + when(bookingRepository.findById(bookingId)).thenReturn(Optional.of(booking)); + + assertThrows(ForbiddenException.class, () -> bookingService.getBooking(userId, bookingId)); + } + } + + @Nested + class GetAllBookingsByBooker { + + @Test + void whenUserIsBooker_andStateIsAll_thenReturnListOfAllBookingDtosOfBooker() { + // region setup + User booker = testUtils.makeNewUser(1); + long bookerId = booker.getId(); + LocalDateTime now = LocalDateTime.now(); + List bookings = List.of( + // future + testUtils.makeNewAnyFullFastBooking( + 53, testUtils.futureDate, BookingStatus.WAITING, null), + // current + testUtils.makeNewAnyFullFastBooking( + 52, now.minusHours(5), BookingStatus.APPROVED, null), + // rejected + testUtils.makeNewAnyFullFastBooking( + 51, testUtils.pastDate.plusDays(30), BookingStatus.REJECTED, null), + // past + testUtils.makeNewAnyFullFastBooking( + 50, testUtils.pastDate, BookingStatus.APPROVED) + ); + bookings.forEach(booking -> booking.setBooker(booker)); + List bookingDtos = bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); + // endregion setup + + doNothing().when(utilService).checkUser(bookerId); + when(bookingRepository.findAllByBookerIdOrderByStartDesc(bookerId)).thenReturn(bookings); + + List resultDtoList = bookingService.getAllBookingsByBooker(bookerId, "all"); + + assertThat(resultDtoList, equalTo(bookingDtos)); + } + + @Test + void whenUserIsBooker_andStateIsNull_thenReturnListOfAllBookingDtosOfBooker() { + // region setup + User booker = testUtils.makeNewUser(1); + long bookerId = booker.getId(); + LocalDateTime now = LocalDateTime.now(); + List bookings = List.of( + // future + testUtils.makeNewAnyFullFastBooking( + 53, testUtils.futureDate, BookingStatus.WAITING, null), + // current + testUtils.makeNewAnyFullFastBooking( + 52, now.minusHours(5), BookingStatus.APPROVED, null), + // rejected + testUtils.makeNewAnyFullFastBooking( + 51, testUtils.pastDate.plusDays(30), BookingStatus.REJECTED, null), + // past + testUtils.makeNewAnyFullFastBooking( + 50, testUtils.pastDate, BookingStatus.APPROVED) + ); + bookings.forEach(booking -> booking.setBooker(booker)); + List bookingDtos = bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); + // endregion setup + + doNothing().when(utilService).checkUser(bookerId); + when(bookingRepository.findAllByBookerIdOrderByStartDesc(bookerId)).thenReturn(bookings); + + List resultDtoList = bookingService.getAllBookingsByBooker(bookerId, null); + + assertThat(resultDtoList, equalTo(bookingDtos)); + } + + @Test + void whenUserIsBooker_andStateIsCurrent_thenReturnListOfCurrentBookingDtosOfBooker() { + // region setup + User booker = testUtils.makeNewUser(1); + long bookerId = booker.getId(); + LocalDateTime now = LocalDateTime.now(); + List bookings = List.of( + // future + testUtils.makeNewAnyFullFastBooking( + 53, testUtils.futureDate, BookingStatus.WAITING, null), + // current + testUtils.makeNewAnyFullFastBooking( + 52, now.minusHours(5), BookingStatus.APPROVED, null), + // rejected + testUtils.makeNewAnyFullFastBooking( + 51, testUtils.pastDate.plusDays(30), BookingStatus.REJECTED, null), + // past + testUtils.makeNewAnyFullFastBooking( + 50, testUtils.pastDate, BookingStatus.APPROVED) + ); + bookings.forEach(booking -> booking.setBooker(booker)); + bookings = bookings.stream() + .filter(booking -> booking.getStart().isBefore(now) && booking.getEnd().isAfter(now)) + .toList(); + List bookingDtos = bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); + // endregion setup + + doNothing().when(utilService).checkUser(bookerId); + when(bookingRepository.findCurrentByBooker(bookerId)).thenReturn(bookings); + + List resultDtoList = bookingService.getAllBookingsByBooker(bookerId, "CURRENT"); + + assertThat(resultDtoList, equalTo(bookingDtos)); + } + + @Test + void whenUserIsBooker_andStateIsPast_thenReturnListOfPastBookingDtosOfBooker() { + // region setup + User booker = testUtils.makeNewUser(1); + long bookerId = booker.getId(); + LocalDateTime now = LocalDateTime.now(); + List bookings = List.of( + // future + testUtils.makeNewAnyFullFastBooking( + 53, testUtils.futureDate, BookingStatus.WAITING, null), + // current + testUtils.makeNewAnyFullFastBooking( + 52, now.minusHours(5), BookingStatus.APPROVED, null), + // rejected + testUtils.makeNewAnyFullFastBooking( + 51, testUtils.pastDate.plusDays(30), BookingStatus.REJECTED, null), + // past + testUtils.makeNewAnyFullFastBooking( + 50, testUtils.pastDate, BookingStatus.APPROVED) + ); + bookings.forEach(booking -> booking.setBooker(booker)); + bookings = bookings.stream() + .filter(booking -> booking.getEnd().isBefore(now)) + .toList(); + List bookingDtos = bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); + // endregion setup + + doNothing().when(utilService).checkUser(bookerId); + when(bookingRepository.findPastByBooker(bookerId)).thenReturn(bookings); + + List resultDtoList = bookingService.getAllBookingsByBooker(bookerId, "past"); + + assertThat(resultDtoList, equalTo(bookingDtos)); + } + + @Test + void whenUserIsBooker_andStateIsFuture_thenReturnListOfFutureBookingDtosOfBooker() { + // region setup + User booker = testUtils.makeNewUser(1); + long bookerId = booker.getId(); + LocalDateTime now = LocalDateTime.now(); + List bookings = List.of( + // future + testUtils.makeNewAnyFullFastBooking( + 53, testUtils.futureDate, BookingStatus.WAITING, null), + // current + testUtils.makeNewAnyFullFastBooking( + 52, now.minusHours(5), BookingStatus.APPROVED, null), + // rejected + testUtils.makeNewAnyFullFastBooking( + 51, testUtils.pastDate.plusDays(30), BookingStatus.REJECTED, null), + // past + testUtils.makeNewAnyFullFastBooking( + 50, testUtils.pastDate, BookingStatus.APPROVED) + ); + bookings.forEach(booking -> booking.setBooker(booker)); + bookings = bookings.stream() + .filter(booking -> booking.getStart().isAfter(now)) + .toList(); + List bookingDtos = bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); + // endregion setup + + doNothing().when(utilService).checkUser(bookerId); + when(bookingRepository.findFutureByBooker(bookerId)).thenReturn(bookings); + + List resultDtoList = bookingService.getAllBookingsByBooker(bookerId, "Future"); + + assertThat(resultDtoList, equalTo(bookingDtos)); + } + + @Test + void whenUserIsBooker_andStatusIsWaiting_thenReturnListOfWaitingBookingDtosOfBooker() { + // region setup + User booker = testUtils.makeNewUser(1); + long bookerId = booker.getId(); + LocalDateTime now = LocalDateTime.now(); + List bookings = List.of( + // future + testUtils.makeNewAnyFullFastBooking( + 53, testUtils.futureDate, BookingStatus.WAITING, null), + // current + testUtils.makeNewAnyFullFastBooking( + 52, now.minusHours(5), BookingStatus.APPROVED, null), + // rejected + testUtils.makeNewAnyFullFastBooking( + 51, testUtils.pastDate.plusDays(30), BookingStatus.REJECTED, null), + // past + testUtils.makeNewAnyFullFastBooking( + 50, testUtils.pastDate, BookingStatus.APPROVED) + ); + bookings.forEach(booking -> booking.setBooker(booker)); + bookings = bookings.stream() + .filter(booking -> booking.getStatus().equals(BookingStatus.WAITING)) + .toList(); + List bookingDtos = bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); + // endregion setup + + doNothing().when(utilService).checkUser(bookerId); + when(bookingRepository.findAllByBooker_IdAndStatusOrderByStartDesc(bookerId, BookingStatus.WAITING)) + .thenReturn(bookings); + + List resultDtoList = bookingService.getAllBookingsByBooker(bookerId, "waItIng"); + + assertThat(resultDtoList, equalTo(bookingDtos)); + } + + @Test + void whenUserIsBooker_andStatusIsRejected_thenReturnListOfRejectedBookingDtosOfBooker() { + // region setup + User booker = testUtils.makeNewUser(1); + long bookerId = booker.getId(); + LocalDateTime now = LocalDateTime.now(); + List bookings = List.of( + // future + testUtils.makeNewAnyFullFastBooking( + 53, testUtils.futureDate, BookingStatus.WAITING, null), + // current + testUtils.makeNewAnyFullFastBooking( + 52, now.minusHours(5), BookingStatus.APPROVED, null), + // rejected + testUtils.makeNewAnyFullFastBooking( + 51, testUtils.pastDate.plusDays(30), BookingStatus.REJECTED, null), + // past + testUtils.makeNewAnyFullFastBooking( + 50, testUtils.pastDate, BookingStatus.APPROVED) + ); + bookings.forEach(booking -> booking.setBooker(booker)); + bookings = bookings.stream() + .filter(booking -> booking.getStatus().equals(BookingStatus.REJECTED)) + .toList(); + List bookingDtos = bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); + // endregion setup + + doNothing().when(utilService).checkUser(bookerId); + when(bookingRepository.findAllByBooker_IdAndStatusOrderByStartDesc(bookerId, BookingStatus.REJECTED)) + .thenReturn(bookings); + + List resultDtoList = bookingService.getAllBookingsByBooker(bookerId, "rejected"); + + assertThat(resultDtoList, equalTo(bookingDtos)); + } + + @Test + void whenUserIsBooker_andStatusIsUndefined_thenReturnParameterNotValidException() { + long bookerId = 1L; + + doNothing().when(utilService).checkUser(bookerId); + + assertThrows(ParameterNotValidException.class, () -> + bookingService.getAllBookingsByBooker(bookerId, "XXX")); + } + } + + @Nested + class GetAllBookingsByOwner { + + @Test + void whenUserIsBooker_andStateIsAll_thenReturnListOfAllBookingDtosOfBooker() { + // region setup + User owner = testUtils.makeNewUser(1); + long ownerId = owner.getId(); + LocalDateTime now = LocalDateTime.now(); + List bookings = List.of( + // future + testUtils.makeNewAnyFullFastBooking( + 53, testUtils.futureDate, BookingStatus.WAITING, null), + // current + testUtils.makeNewAnyFullFastBooking( + 52, now.minusHours(5), BookingStatus.APPROVED, null), + // rejected + testUtils.makeNewAnyFullFastBooking( + 51, testUtils.pastDate.plusDays(30), BookingStatus.REJECTED, null), + // past + testUtils.makeNewAnyFullFastBooking( + 50, testUtils.pastDate, BookingStatus.APPROVED) + ); + bookings.forEach(booking -> booking.getItem().setOwner(owner)); + List bookingDtos = bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findAllByOwner(ownerId)).thenReturn(bookings); + + List resultDtoList = bookingService.getAllBookingsByOwner(ownerId, "all"); + + assertThat(resultDtoList, equalTo(bookingDtos)); + } + + @Test + void whenUserIsBooker_andStateIsNull_thenReturnListOfAllBookingDtosOfBooker() { + // region setup + User owner = testUtils.makeNewUser(1); + long ownerId = owner.getId(); + LocalDateTime now = LocalDateTime.now(); + List bookings = List.of( + // future + testUtils.makeNewAnyFullFastBooking( + 53, testUtils.futureDate, BookingStatus.WAITING, null), + // current + testUtils.makeNewAnyFullFastBooking( + 52, now.minusHours(5), BookingStatus.APPROVED, null), + // rejected + testUtils.makeNewAnyFullFastBooking( + 51, testUtils.pastDate.plusDays(30), BookingStatus.REJECTED, null), + // past + testUtils.makeNewAnyFullFastBooking( + 50, testUtils.pastDate, BookingStatus.APPROVED) + ); + bookings.forEach(booking -> booking.getItem().setOwner(owner)); + List bookingDtos = bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findAllByOwner(ownerId)).thenReturn(bookings); + + List resultDtoList = bookingService.getAllBookingsByOwner(ownerId, null); + + assertThat(resultDtoList, equalTo(bookingDtos)); + } + + @Test + void whenUserIsBooker_andStateIsCurrent_thenReturnListOfCurrentBookingDtosOfBooker() { + // region setup + User owner = testUtils.makeNewUser(1); + long ownerId = owner.getId(); + LocalDateTime now = LocalDateTime.now(); + List bookings = List.of( + // future + testUtils.makeNewAnyFullFastBooking( + 53, testUtils.futureDate, BookingStatus.WAITING, null), + // current + testUtils.makeNewAnyFullFastBooking( + 52, now.minusHours(5), BookingStatus.APPROVED, null), + // rejected + testUtils.makeNewAnyFullFastBooking( + 51, testUtils.pastDate.plusDays(30), BookingStatus.REJECTED, null), + // past + testUtils.makeNewAnyFullFastBooking( + 50, testUtils.pastDate, BookingStatus.APPROVED) + ); + bookings.forEach(booking -> booking.getItem().setOwner(owner)); + bookings = bookings.stream() + .filter(booking -> booking.getStart().isBefore(now) && booking.getEnd().isAfter(now)) + .toList(); + List bookingDtos = bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findCurrentByOwner(ownerId)).thenReturn(bookings); + + List resultDtoList = bookingService.getAllBookingsByOwner(ownerId, "CURRENT"); + + assertThat(resultDtoList, equalTo(bookingDtos)); + } + + @Test + void whenUserIsBooker_andStateIsPast_thenReturnListOfPastBookingDtosOfBooker() { + // region setup + User owner = testUtils.makeNewUser(1); + long ownerId = owner.getId(); + LocalDateTime now = LocalDateTime.now(); + List bookings = List.of( + // future + testUtils.makeNewAnyFullFastBooking( + 53, testUtils.futureDate, BookingStatus.WAITING, null), + // current + testUtils.makeNewAnyFullFastBooking( + 52, now.minusHours(5), BookingStatus.APPROVED, null), + // rejected + testUtils.makeNewAnyFullFastBooking( + 51, testUtils.pastDate.plusDays(30), BookingStatus.REJECTED, null), + // past + testUtils.makeNewAnyFullFastBooking( + 50, testUtils.pastDate, BookingStatus.APPROVED) + ); + bookings.forEach(booking -> booking.getItem().setOwner(owner)); + bookings = bookings.stream() + .filter(booking -> booking.getEnd().isBefore(now)) + .toList(); + List bookingDtos = bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findPastByOwner(ownerId)).thenReturn(bookings); + + List resultDtoList = bookingService.getAllBookingsByOwner(ownerId, "past"); + + assertThat(resultDtoList, equalTo(bookingDtos)); + } + + @Test + void whenUserIsBooker_andStateIsFuture_thenReturnListOfFutureBookingDtosOfBooker() { + // region setup + User owner = testUtils.makeNewUser(1); + long ownerId = owner.getId(); + LocalDateTime now = LocalDateTime.now(); + List bookings = List.of( + // future + testUtils.makeNewAnyFullFastBooking( + 53, testUtils.futureDate, BookingStatus.WAITING, null), + // current + testUtils.makeNewAnyFullFastBooking( + 52, now.minusHours(5), BookingStatus.APPROVED, null), + // rejected + testUtils.makeNewAnyFullFastBooking( + 51, testUtils.pastDate.plusDays(30), BookingStatus.REJECTED, null), + // past + testUtils.makeNewAnyFullFastBooking( + 50, testUtils.pastDate, BookingStatus.APPROVED) + ); + bookings.forEach(booking -> booking.getItem().setOwner(owner)); + bookings = bookings.stream() + .filter(booking -> booking.getStart().isAfter(now)) + .toList(); + List bookingDtos = bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findFutureByOwner(ownerId)).thenReturn(bookings); + + List resultDtoList = bookingService.getAllBookingsByOwner(ownerId, "Future"); + + assertThat(resultDtoList, equalTo(bookingDtos)); + } + + @Test + void whenUserIsBooker_andStatusIsWaiting_thenReturnListOfWaitingBookingDtosOfBooker() { + // region setup + User owner = testUtils.makeNewUser(1); + long ownerId = owner.getId(); + LocalDateTime now = LocalDateTime.now(); + List bookings = List.of( + // future + testUtils.makeNewAnyFullFastBooking( + 53, testUtils.futureDate, BookingStatus.WAITING, null), + // current + testUtils.makeNewAnyFullFastBooking( + 52, now.minusHours(5), BookingStatus.APPROVED, null), + // rejected + testUtils.makeNewAnyFullFastBooking( + 51, testUtils.pastDate.plusDays(30), BookingStatus.REJECTED, null), + // past + testUtils.makeNewAnyFullFastBooking( + 50, testUtils.pastDate, BookingStatus.APPROVED) + ); + bookings.forEach(booking -> booking.getItem().setOwner(owner)); + bookings = bookings.stream() + .filter(booking -> booking.getStatus().equals(BookingStatus.WAITING)) + .toList(); + List bookingDtos = bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findAllByOwnerAndStatusOrderByStartDesc(ownerId, BookingStatus.WAITING)) + .thenReturn(bookings); + + List resultDtoList = bookingService.getAllBookingsByOwner(ownerId, "waItIng"); + + assertThat(resultDtoList, equalTo(bookingDtos)); + } + + @Test + void whenUserIsBooker_andStatusIsRejected_thenReturnListOfRejectedBookingDtosOfBooker() { + // region setup + User owner = testUtils.makeNewUser(1); + long ownerId = owner.getId(); + LocalDateTime now = LocalDateTime.now(); + List bookings = List.of( + // future + testUtils.makeNewAnyFullFastBooking( + 53, testUtils.futureDate, BookingStatus.WAITING, null), + // current + testUtils.makeNewAnyFullFastBooking( + 52, now.minusHours(5), BookingStatus.APPROVED, null), + // rejected + testUtils.makeNewAnyFullFastBooking( + 51, testUtils.pastDate.plusDays(30), BookingStatus.REJECTED, null), + // past + testUtils.makeNewAnyFullFastBooking( + 50, testUtils.pastDate, BookingStatus.APPROVED) + ); + bookings.forEach(booking -> booking.getItem().setOwner(owner)); + bookings = bookings.stream() + .filter(booking -> booking.getStatus().equals(BookingStatus.REJECTED)) + .toList(); + List bookingDtos = bookings.stream() + .map(BookingMapper::toBookingDto) + .toList(); + // endregion setup + + doNothing().when(utilService).checkUser(ownerId); + when(bookingRepository.findAllByOwnerAndStatusOrderByStartDesc(ownerId, BookingStatus.REJECTED)) + .thenReturn(bookings); + + List resultDtoList = bookingService.getAllBookingsByOwner(ownerId, "rejected"); + + assertThat(resultDtoList, equalTo(bookingDtos)); + } + + @Test + void whenUserIsBooker_andStatusIsUndefined_thenReturnParameterNotValidException() { + long ownerId = 1L; + + doNothing().when(utilService).checkUser(ownerId); + + assertThrows(ParameterNotValidException.class, () -> + bookingService.getAllBookingsByOwner(ownerId, "XXX")); + } + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/item/ItemContextTest.java b/server/src/test/java/ru/practicum/shareit/item/ItemContextTest.java new file mode 100644 index 0000000..52202a8 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/ItemContextTest.java @@ -0,0 +1,632 @@ +package ru.practicum.shareit.item; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.EntityManagerFactory; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import ru.practicum.shareit.TestUtils; +import ru.practicum.shareit.booking.model.Booking; +import ru.practicum.shareit.booking.model.BookingStatus; +import ru.practicum.shareit.booking.repository.BookingRepository; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.CommentMapper; +import ru.practicum.shareit.item.dto.ItemDto; +import ru.practicum.shareit.item.dto.ItemMapper; +import ru.practicum.shareit.item.model.Comment; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.repository.CommentRepository; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.request.repository.QueryDslRepository; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; +import ru.practicum.shareit.util.error.ErrorHandler; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.*; +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 +@AutoConfigureMockMvc +@EnableAutoConfiguration(exclude = { + DataSourceAutoConfiguration.class, + HibernateJpaAutoConfiguration.class +}) +public class ItemContextTest { + + final TestUtils testUtils = new TestUtils(); + + @Autowired + ObjectMapper mapper; + + @Autowired + private MockMvc mvc; + + @Autowired + ItemController itemController; + + // region mocks + @MockitoBean + UserRepository userRepository; + + @MockitoBean + ItemRepository itemRepository; + + @MockitoBean + ItemRequestRepository itemRequestRepository; + + @MockitoBean + BookingRepository bookingRepository; + + @MockitoBean + CommentRepository commentRepository; + + @MockitoBean + QueryDslRepository queryDslRepository; + + @MockitoBean + EntityManagerFactory entityManagerFactory; + // endregion mocks + + @BeforeEach + void setUp() { + mvc = MockMvcBuilders + .standaloneSetup(itemController) + .setControllerAdvice(new ErrorHandler()) + .setMessageConverters(new MappingJackson2HttpMessageConverter(mapper)) + .build(); + } + + @Nested + class GetItem { + + @Test + @SneakyThrows + void whenUserIdIsEqualsOwnerId_htenReturnsItemWithBookingDates() { + User owner = testUtils.makeNewUser(1); + Item item = testUtils.makeNewItem(100, owner, null); + + when(itemRepository.findById(item.getId())).thenReturn(Optional.of(item)); + when(bookingRepository.findLastBookingDate(item.getId())).thenReturn(testUtils.pastDate); + when(bookingRepository.findNextBookingDate(item.getId())).thenReturn(testUtils.futureDate); + + mvc.perform(get("/items/{itemid}", item.getId()) + .header("X-Sharer-User-Id", owner.getId())) + .andExpect(status().isOk()) + .andDo(MockMvcResultHandlers.print()) + // region .andExpect item + .andExpect(jsonPath("$.id", is(item.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(item.getName()), String.class)) + .andExpect(jsonPath("$.description", is(item.getDescription()), String.class)) + .andExpect(jsonPath("$.available", is(item.isAvailable()), Boolean.class)) + .andExpect(jsonPath("$.request", nullValue())) + .andExpect(jsonPath("$.lastBooking", is(testUtils.pastDate.toString()), String.class)) + .andExpect(jsonPath("$.nextBooking", is(testUtils.futureDate.toString()), String.class)) + .andExpect(jsonPath("$.comments", empty())); + // endregion .andExpect item + } + + @Test + @SneakyThrows + void whenUserIdIsNotEqualsOwnerId_htenReturnsItemWithNullableBookingDates() { + User owner = testUtils.makeNewUser(1); + Item item = testUtils.makeNewItem(100, owner, null); + + when(itemRepository.findById(item.getId())).thenReturn(Optional.of(item)); + + mvc.perform(get("/items/{itemid}", item.getId()) + .header("X-Sharer-User-Id", item.getId())) + .andExpect(status().isOk()) + .andDo(MockMvcResultHandlers.print()) + // region .andExpect item + .andExpect(jsonPath("$.id", is(item.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(item.getName()), String.class)) + .andExpect(jsonPath("$.description", is(item.getDescription()), String.class)) + .andExpect(jsonPath("$.available", is(item.isAvailable()), Boolean.class)) + .andExpect(jsonPath("$.request", nullValue())) + .andExpect(jsonPath("$.lastBooking", nullValue())) + .andExpect(jsonPath("$.nextBooking", nullValue())) + .andExpect(jsonPath("$.comments", empty())); + // endregion .andExpect item + + verify(bookingRepository, never()).findLastBookingDate(item.getId()); + verify(bookingRepository, never()).findNextBookingDate(item.getId()); + } + } + + @Nested + class GetAllItemsOfUser { + + @Test + @SneakyThrows + void returnResponseWithStatusOkAndActualItems() { + Booking booking1 = testUtils.makeNewAnyFullFastBooking( + 10, + testUtils.pastDate, + BookingStatus.APPROVED + ); + + User booker = booking1.getBooker(); + User owner = booking1.getItem().getOwner(); + + Booking booking2 = testUtils.makeNewAnyFullBooking( + 11, + booker, + owner, + testUtils.futureDate, + BookingStatus.WAITING + ); + + Item item1 = booking1.getItem(); + Item item2 = booking2.getItem(); + + ItemRequest request1 = item1.getRequest(); + ItemRequest request2 = item2.getRequest(); + + Comment comment = testUtils.makeNewComment( + 90, + booking1.getItem(), + booker, + testUtils.pastDate.plusHours(11) + ); + + when(userRepository.existsById(owner.getId())).thenReturn(true); + when(itemRepository.findAllByOwner_Id(owner.getId())).thenReturn(List.of(item1, item2)); + when(bookingRepository.findAllBookingByItemIdIn(Set.of(item1.getId(), item2.getId()))) + .thenReturn(List.of(booking1, booking2)); + when(commentRepository.findAllByItemIdIn(Set.of(item1.getId(), item2.getId()))) + .thenReturn(List.of(comment)); + + mvc.perform(get("/items").header("X-Sharer-User-Id", owner.getId())) + .andExpect(status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andExpect(jsonPath("$", hasSize(2))) + // region .andExpect item 1 + .andExpect(jsonPath("$[0].id", is(item1.getId()), Long.class)) + .andExpect(jsonPath("$[0].name", is(item1.getName()))) + .andExpect(jsonPath("$[0].description", is(item1.getDescription()))) + .andExpect(jsonPath("$[0].available", is(item1.isAvailable()))) + .andExpect(jsonPath("$[0].request", is(request1.getId()), Long.class)) + .andExpect(jsonPath("$[0].lastBooking", is(booking1.getStart().toString()))) + .andExpect(jsonPath("$[0].nextBooking", nullValue())) + .andExpect(jsonPath("$[0].comments[0].id", is(comment.getId()), Long.class)) + .andExpect(jsonPath("$[0].comments[0].text", is(comment.getText()))) + .andExpect(jsonPath("$[0].comments[0].itemId", is(comment.getItem().getId()), Long.class)) + .andExpect(jsonPath("$[0].comments[0].created", is(comment.getCreatedDate().toString()))) + .andExpect(jsonPath("$[0].comments[0].authorName", is(comment.getAuthor().getName()))) + // endregion .andExpect item 1 + // region .andExpect item 2 + .andExpect(jsonPath("$[1].id", is(item2.getId()), Long.class)) + .andExpect(jsonPath("$[1].name", is(item2.getName()))) + .andExpect(jsonPath("$[1].description", is(item2.getDescription()))) + .andExpect(jsonPath("$[1].available", is(item2.isAvailable()))) + .andExpect(jsonPath("$[1].request", is(request2.getId()), Long.class)) + .andExpect(jsonPath("$[1].lastBooking", nullValue())) + .andExpect(jsonPath("$[1].nextBooking", is(booking2.getStart().toString()))) + .andExpect(jsonPath("$[1].comments", empty())); + // endregion .andExpect item 2 + } + } + + @Nested + class Search { + + @Test + @SneakyThrows + void whenTextIsValid_returnsListWithActualItems() { + Item item1 = testUtils.makeNewFastItem(10); + Item item2 = testUtils.makeNewFastItem(11); + Item item3 = testUtils.makeNewFastItem(12); + String text = item1.getName(); + item2.setDescription(text + item2.getName()); + item3.setName("hghgh" + text); + Long seacherId = item3.getOwner().getId(); + + when(userRepository.existsById(seacherId)).thenReturn(true); + when(itemRepository.search(item1.getName())).thenReturn(List.of(item1, item2, item3)); + + // region mvc test + mvc.perform(get("/items/search?text={text}", item1.getName()) + .header("X-Sharer-User-Id", seacherId)) + .andExpect(status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andExpect(jsonPath("$", hasSize(2))); + // endregion mvc test + } + + @Test + @SneakyThrows + void whenUserIsNotFound_returnsResponseStatusForbidden() { + when(userRepository.existsById(1L)).thenReturn(false); + + // region mvc test + mvc.perform(get("/items/search?text={text}", "") + .header("X-Sharer-User-Id", 1)) + .andExpect(status().isForbidden()) + .andDo(MockMvcResultHandlers.print()); + // endregion mvc test + } + } + + @Nested + class AddNewItem { + + @Test + @SneakyThrows + void whenRequestIsNotNull_returnItemWithRequestId() { + Item item = testUtils.makeNewFastItem(10); + + when(userRepository.findById(item.getOwner().getId())).thenReturn(Optional.of(item.getOwner())); + when(itemRequestRepository.findById(item.getRequest().getId())) + .thenReturn(Optional.ofNullable(item.getRequest())); + when(itemRepository.save(item)).thenReturn(item); + + // region mvc test + mvc.perform(post("/items") + .header("X-Sharer-User-Id", item.getOwner().getId()) + .content(mapper.writeValueAsString(ItemMapper.toDto(item))) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andExpect(jsonPath("$.id", is(item.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(item.getName()), String.class)) + .andExpect(jsonPath("$.description", is(item.getDescription()), String.class)) + .andExpect(jsonPath("$.available", is(item.isAvailable()), Boolean.class)) + .andExpect(jsonPath("$.requestId", is(item.getRequest().getId()), Long.class)); + // endregion mvc test + } + + @Test + @SneakyThrows + void whenRequestIsNull_returnItemWithNullableRequestId() { + Item item = testUtils.makeNewFastItem(10); + item.setRequest(null); + + when(userRepository.findById(item.getOwner().getId())).thenReturn(Optional.of(item.getOwner())); + when(itemRepository.save(item)).thenReturn(item); + + // region mvc test + mvc.perform(post("/items") + .header("X-Sharer-User-Id", item.getOwner().getId()) + .content(mapper.writeValueAsString(ItemMapper.toDto(item))) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andExpect(jsonPath("$.id", is(item.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(item.getName()), String.class)) + .andExpect(jsonPath("$.description", is(item.getDescription()), String.class)) + .andExpect(jsonPath("$.available", is(item.isAvailable()), Boolean.class)) + .andExpect(jsonPath("$.requestId", nullValue())); + // endregion mvc test + + verify(itemRequestRepository, never()).findById(anyLong()); + } + } + + @Nested + class UpdateItem { + + @Test + @SneakyThrows + void updateAllfields_whenUserIsOwnerOfItem_thenReturnsUpdatableItem() { + // region setup + Item item = testUtils.makeNewFastItem(10); + ItemDto itemDto = ItemDto.builder() + .name("this is new name") + .description("this is new description") + .available(false) + .requestId(100L) + .build(); + ItemRequest newRequest = testUtils.makeNewItemRequest(100, testUtils.makeNewUser(200)); + Item newItem = testUtils.makeCopyOfItem(item) + .setName(itemDto.getName()) + .setDescription(itemDto.getDescription()) + .setAvailable(itemDto.getAvailable()) + .setRequest(newRequest); + // endregion setup + + when(userRepository.existsById(item.getOwner().getId())).thenReturn(true); + when(itemRepository.findById(item.getId())).thenReturn(Optional.of(item)); + when(userRepository.findById(item.getOwner().getId())).thenReturn(Optional.of(item.getOwner())); + when(itemRequestRepository.findById(item.getRequest().getId())) + .thenReturn(Optional.ofNullable(item.getRequest())); + when(itemRequestRepository.findById(newItem.getRequest().getId())).thenReturn(Optional.of(newRequest)); + when(itemRepository.save(newItem)).thenReturn(newItem); + + // region mvc test + mvc.perform(patch("/items/{itemId}", item.getId()) + .header("X-Sharer-User-Id", item.getOwner().getId()) + .content(mapper.writeValueAsString(itemDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andExpect(jsonPath("$.id", is(newItem.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(newItem.getName()), String.class)) + .andExpect(jsonPath("$.description", is(newItem.getDescription()), String.class)) + .andExpect(jsonPath("$.available", is(newItem.isAvailable()), Boolean.class)) + .andExpect(jsonPath("$.requestId", is(newItem.getRequest().getId()), Long.class)); + // endregion mvc test + } + + @Test + @SneakyThrows + void updateNullfields_whenUserIsOwnerOfItem_thenReturnsUpdatableItem() { + // region setup + Item item = testUtils.makeNewFastItem(10); + ItemDto itemDto = ItemDto.builder().build(); + Item newItem = testUtils.makeCopyOfItem(item); + // endregion setup + + when(userRepository.existsById(item.getOwner().getId())).thenReturn(true); + when(itemRepository.findById(item.getId())).thenReturn(Optional.of(item)); + when(userRepository.findById(item.getOwner().getId())).thenReturn(Optional.of(item.getOwner())); + when(itemRequestRepository.findById(item.getRequest().getId())) + .thenReturn(Optional.ofNullable(item.getRequest())); + when(itemRepository.save(newItem)).thenReturn(newItem); + + // region mvc test + mvc.perform(patch("/items/{itemId}", item.getId()) + .header("X-Sharer-User-Id", item.getOwner().getId()) + .content(mapper.writeValueAsString(itemDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andExpect(jsonPath("$.id", is(newItem.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(newItem.getName()), String.class)) + .andExpect(jsonPath("$.description", is(newItem.getDescription()), String.class)) + .andExpect(jsonPath("$.available", is(newItem.isAvailable()), Boolean.class)) + .andExpect(jsonPath("$.requestId", is(newItem.getRequest().getId()), Long.class)); + // endregion mvc test + } + + @Test + @SneakyThrows + void whenUserIsNotOwnerOfItem_thenReturnsResponseWithStatuNotFound() { + Item item = testUtils.makeNewFastItem(10); + ItemDto itemDto = ItemDto.builder().name("this is new name").build(); + Item newItem = testUtils.makeCopyOfItem(item).setName(itemDto.getName()); + + when(userRepository.existsById(item.getOwner().getId() + 1)).thenReturn(true); + when(itemRepository.findById(item.getId())).thenReturn(Optional.of(item)); + + // region mvc test + mvc.perform(patch("/items/{itemId}", item.getId()) + .header("X-Sharer-User-Id", item.getOwner().getId() + 1) + .content(mapper.writeValueAsString(itemDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andDo(MockMvcResultHandlers.print()); + // endregion mvc test + + verify(userRepository, never()).findById(item.getOwner().getId() + 1); + verify(itemRequestRepository, never()).findById(item.getRequest().getId()); + verify(itemRepository, never()).save(newItem); + } + } + + @Nested + class DeleteItem { + + @Test + @SneakyThrows + void wnenUserIsOwnerOfItem_thenReturnsResponseWithStatusOk() { + Item item = testUtils.makeNewFastItem(10); + when(userRepository.existsById(item.getOwner().getId())).thenReturn(true); + when(itemRepository.findById(item.getId())).thenReturn(Optional.of(item)); + doNothing().when(itemRepository).deleteById(item.getId()); + + // region mvc test + mvc.perform(delete("/items/{itemId}", item.getId()) + .header("X-Sharer-User-Id", item.getOwner().getId()) + ) + .andExpect(status().isOk()) + .andDo(MockMvcResultHandlers.print()); + // endregion mvc test + } + + @Test + @SneakyThrows + void wnenUserIsNotOwnerOfItem_thenReturnsResponseWithStatuNotFound() { + Item item = testUtils.makeNewFastItem(10); + when(userRepository.existsById(item.getOwner().getId() + 1)).thenReturn(true); + when(itemRepository.findById(item.getId())).thenReturn(Optional.of(item)); + + // region mvc test + mvc.perform(delete("/items/{itemId}", item.getId()) + .header("X-Sharer-User-Id", item.getOwner().getId() + 1) + ) + .andExpect(status().isNotFound()) + .andDo(MockMvcResultHandlers.print()); + // endregion mvc test + + verify(itemRepository, never()).deleteById(item.getId()); + } + } + + @Nested + class AddComment { + + @Test + @SneakyThrows + void whenUserIsNotOwnerOfItem_thenReturnsResponseWithStatusOkAndBodyOfActualComment() { + Booking booking1 = testUtils.makeNewAnyFullFastBooking(10, testUtils.pastDate, BookingStatus.APPROVED); + Booking booking2 = testUtils.makeNewAnyFullFastBooking(11, testUtils.pastDate, BookingStatus.APPROVED); + booking2.setItem(booking1.getItem()); + Item item = booking1.getItem(); + User booker = booking1.getBooker(); + String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")); + Comment comment = testUtils.makeNewComment(100, item, booker, LocalDateTime.parse(date)); + CommentDto commentDto = CommentMapper.toDto(comment); + + when(userRepository.findById(booker.getId())).thenReturn(Optional.of(booker)); + when(itemRepository.findById(item.getId())).thenReturn(Optional.of(item)); + when(bookingRepository.findAllBookingByItem_Id(item.getId())) + .thenReturn(List.of(booking1, booking2)); + when(commentRepository.save(comment)).thenReturn(comment); + + // region mvc test + mvc.perform(post("/items/{itemId}/comment", item.getId()) + .header("X-Sharer-User-Id", booker.getId()) + .content(mapper.writeValueAsString(commentDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andExpect(jsonPath("$.id", is(comment.getId()), Long.class)) + .andExpect(jsonPath("$.itemId", is(item.getId()), Long.class)) + .andExpect(jsonPath("$.authorName", is(booker.getName()))) + .andExpect(jsonPath("$.created", is(comment.getCreatedDate().toString()))) + .andExpect(jsonPath("$.text", is(comment.getText()))); + // endregion mvc test + + verify(commentRepository).save(any()); + } + + @Test + @SneakyThrows + void whenUserIsNotOwnerOfItem_andDateIsNull_thenReturnsResponseWithStatusOkAndBodyOfActualComment() { + Booking booking1 = testUtils.makeNewAnyFullFastBooking(10, testUtils.pastDate, BookingStatus.APPROVED); + Booking booking2 = testUtils.makeNewAnyFullFastBooking(11, testUtils.pastDate, BookingStatus.APPROVED); + booking2.setItem(booking1.getItem()); + Item item = booking1.getItem(); + User booker = booking1.getBooker(); + Comment comment = testUtils.makeNewComment(100, item, booker, null); + CommentDto commentDto = CommentMapper.toDto(comment); + + when(userRepository.findById(booker.getId())).thenReturn(Optional.of(booker)); + when(itemRepository.findById(item.getId())).thenReturn(Optional.of(item)); + when(bookingRepository.findAllBookingByItem_Id(item.getId())) + .thenReturn(List.of(booking1, booking2)); + when(commentRepository.save(comment)).thenReturn( + testUtils.makeCopyOfComment(comment).setCreatedDate(LocalDateTime.now())); + + // region mvc test + mvc.perform(post("/items/{itemId}/comment", item.getId()) + .header("X-Sharer-User-Id", booker.getId()) + .content(mapper.writeValueAsString(commentDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andExpect(jsonPath("$.id", is(comment.getId()), Long.class)) + .andExpect(jsonPath("$.itemId", is(item.getId()), Long.class)) + .andExpect(jsonPath("$.authorName", is(booker.getName()))) + .andExpect(jsonPath("$.text", is(comment.getText()))); + // endregion mvc test + } + + @Test + @SneakyThrows + void whenUserIsOwnerOfItem_thenReturnsResponseWithStatusForbidden() { + User owner = testUtils.makeNewUser(10); + Item item = testUtils.makeNewItem(50, owner, null); + CommentDto commentDto = CommentDto.builder().text("this is comment").build(); + + when(userRepository.findById(owner.getId())).thenReturn(Optional.of(owner)); + when(itemRepository.findById(item.getId())).thenReturn(Optional.of(item)); + + // region mvc test + mvc.perform(post("/items/{itemId}/comment", item.getId()) + .header("X-Sharer-User-Id", owner.getId()) + .content(mapper.writeValueAsString(commentDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); + // endregion mvc test + + verify(bookingRepository, never()).findAllBookingByItem_Id(item.getId()); + verify(commentRepository, never()).save(any()); + } + + @Test + @SneakyThrows + void whenBookingIsNotCompleted_thenReturnsResponseWithStatusBadRequest() { + Booking booking = testUtils.makeNewAnyFullFastBooking(10, testUtils.pastDate, BookingStatus.APPROVED); + booking.setEnd(testUtils.futureDate); + Item item = booking.getItem(); + User booker = booking.getBooker(); + CommentDto commentDto = CommentDto.builder().text("this is comment").build(); + + when(userRepository.findById(booker.getId())).thenReturn(Optional.of(booker)); + when(itemRepository.findById(item.getId())).thenReturn(Optional.of(item)); + when(bookingRepository.findAllBookingByItem_Id(item.getId())) + .thenReturn(Collections.singletonList(booking)); + + // region mvc test + mvc.perform(post("/items/{itemId}/comment", item.getId()) + .header("X-Sharer-User-Id", booker.getId()) + .content(mapper.writeValueAsString(commentDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + // endregion mvc test + + verify(commentRepository, never()).save(any()); + } + + @Test + @SneakyThrows + void whenBookingIsLost_thenReturnsResponseWithStatusBadRequest() { + User booker = testUtils.makeNewUser(10); + User owner = testUtils.makeNewUser(11); + Item item = testUtils.makeNewItem(50, owner, null); + CommentDto commentDto = CommentDto.builder().text("this is comment").build(); + + when(userRepository.findById(booker.getId())).thenReturn(Optional.of(booker)); + when(itemRepository.findById(item.getId())).thenReturn(Optional.of(item)); + when(bookingRepository.findAllBookingByItem_Id(item.getId())) + .thenReturn(Collections.emptyList()); + + // region mvc test + mvc.perform(post("/items/{itemId}/comment", item.getId()) + .header("X-Sharer-User-Id", booker.getId()) + .content(mapper.writeValueAsString(commentDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + // endregion mvc test + + verify(commentRepository, never()).save(any()); + } + } +} diff --git a/server/src/test/java/ru/practicum/shareit/item/dto/ItemBigDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/item/dto/ItemBigDtoJsonTest.java new file mode 100644 index 0000000..46e5f96 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/item/dto/ItemBigDtoJsonTest.java @@ -0,0 +1,116 @@ +package ru.practicum.shareit.item.dto; + +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonContent; +import org.springframework.test.context.ContextConfiguration; +import ru.practicum.shareit.ShareItServer; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +@ContextConfiguration(classes = ShareItServer.class) +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class ItemBigDtoJsonTest { + + final JacksonTester itemBigDtoJson; + + @Test + @SneakyThrows + void testItemBigDto() { + ItemBigDto itemBigDto = ItemBigDto.builder() + .id(1L) + .name("this is item name") + .description("this is description") + .available(true) + .requestId(123L) // request + .lastBookingDate(LocalDateTime.parse("2027-01-01T00:00:01")) // lastBooking + .nextBookingDate(LocalDateTime.parse("2027-01-02T01:02:01")) // nextBooking + .comments(List.of( + CommentDto.builder() + .id(6L) + .itemId(1L) + .authorName("this is author name 1") + .createdDate(LocalDateTime.parse("2027-01-01T03:20:01")) // created + .text("this is text 1") + .build(), + CommentDto.builder() + .id(7L) + .itemId(1L) + .authorName("this is author name 2") + .createdDate(LocalDateTime.parse("2027-03-01T07:21:01")) // created + .text("this is text 2") + .build() + )) + .build(); + + JsonContent result = itemBigDtoJson.write(itemBigDto); + + // region itemBigDto + assertThat(result) + .extractingJsonPathNumberValue("$.id") + .isEqualTo(1); + assertThat(result) + .extractingJsonPathStringValue("$.name") + .isEqualTo("this is item name"); + assertThat(result) + .extractingJsonPathStringValue("$.description") + .isEqualTo("this is description"); + assertThat(result) + .extractingJsonPathBooleanValue("$.available") + .isEqualTo(true); + assertThat(result) + .extractingJsonPathNumberValue("$.request") + .isEqualTo(123); + assertThat(result) + .extractingJsonPathStringValue("$.lastBooking") + .isEqualTo("2027-01-01T00:00:01"); + assertThat(result) + .extractingJsonPathStringValue("$.nextBooking") + .isEqualTo("2027-01-02T01:02:01"); + // endregion itemBigDto + + // region comments[0] + assertThat(result) + .extractingJsonPathNumberValue("$.comments[0].id") + .isEqualTo(6); + assertThat(result) + .extractingJsonPathNumberValue("$.comments[0].itemId") + .isEqualTo(1); + assertThat(result) + .extractingJsonPathStringValue("$.comments[0].authorName") + .isEqualTo("this is author name 1"); + assertThat(result) + .extractingJsonPathStringValue("$.comments[0].created") + .isEqualTo("2027-01-01T03:20:01"); + assertThat(result) + .extractingJsonPathStringValue("$.comments[0].text") + .isEqualTo("this is text 1"); + // endregion comments[0] + + // region comments[1] + assertThat(result) + .extractingJsonPathNumberValue("$.comments[1].id") + .isEqualTo(7); + assertThat(result) + .extractingJsonPathNumberValue("$.comments[1].itemId") + .isEqualTo(1); + assertThat(result) + .extractingJsonPathStringValue("$.comments[1].authorName") + .isEqualTo("this is author name 2"); + assertThat(result) + .extractingJsonPathStringValue("$.comments[1].created") + .isEqualTo("2027-03-01T07:21:01"); + assertThat(result) + .extractingJsonPathStringValue("$.comments[1].text") + .isEqualTo("this is text 2"); + // endregion comments[1] + } +} diff --git a/server/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java b/server/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java new file mode 100644 index 0000000..0fd6cf1 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/ItemRequestControllerTest.java @@ -0,0 +1,164 @@ +package ru.practicum.shareit.request; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import ru.practicum.shareit.TestUtils; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.dto.ItemRequestBigDto; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.dto.ItemRequestMapper; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.service.ItemRequestService; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.util.UtilService; +import ru.practicum.shareit.util.error.ErrorHandler; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(ItemRequestController.class) +class ItemRequestControllerTest { + + @Autowired + ObjectMapper mapper; + + @Autowired + MockMvc mvc; + + @Autowired + ItemRequestController controller; + + @MockitoBean + ItemRequestService requestService; + + @MockitoBean + UtilService utilService; + + final TestUtils testUtils = new TestUtils(); + + @BeforeEach + void setUp() { + mvc = MockMvcBuilders + .standaloneSetup(controller) + .setControllerAdvice(new ErrorHandler()) + .setMessageConverters(new MappingJackson2HttpMessageConverter(mapper)) + .build(); + } + + @Test + @SneakyThrows + void addNewRequest() { + User requestor = testUtils.makeNewUser(1); + ItemRequest itemRequest = testUtils.makeNewItemRequest(50, requestor); + ItemRequestDto requestDto = ItemRequestDto.builder() + .description(itemRequest.getDescription()) + .createdDate(itemRequest.getCreatedDate()) + .build(); + ItemRequestDto resultRequestDto = ItemRequestMapper.toDto(itemRequest); + + when(requestService.addNewRequest(requestor.getId(), requestDto)).thenReturn(resultRequestDto); + + String result = mvc.perform(post("/requests") + .header("X-Sharer-User-Id", requestor.getId()) + .content(mapper.writeValueAsString(requestDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertEquals(result, mapper.writeValueAsString(ItemRequestMapper.toDto(itemRequest))); + } + + @Test + @SneakyThrows + void getRequestListByUser() { + User requestor = testUtils.makeNewUser(5); + Item item1 = testUtils.makeNewFastItem(40); + Item item2 = testUtils.makeNewFastItem(41); + Item item3 = testUtils.makeNewFastItem(42); + List bigDtos = List.of( + ItemRequestMapper + .toBigDto(testUtils.makeNewItemRequest(70, requestor), List.of(item1, item3)), + ItemRequestMapper + .toBigDto(testUtils.makeNewItemRequest(71, requestor), List.of(item1, item2)) + ); + + when(requestService.getRequestListByUser(requestor.getId())).thenReturn(bigDtos); + + String result = mvc.perform(get("/requests") + .header("X-Sharer-User-Id", requestor.getId())) + .andExpect(status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertEquals(result, mapper.writeValueAsString(bigDtos)); + } + + @Test + @SneakyThrows + void getAllRequests() { + User user = testUtils.makeNewUser(1); + List requests = List.of( + testUtils.makeNewItemRequest(10, testUtils.makeNewUser(2)), + testUtils.makeNewItemRequest(11, testUtils.makeNewUser(3)) + ); + List requestDtos = requests.stream() + .map(ItemRequestMapper::toDto) + .toList(); + + when(requestService.getAllRequests(user.getId())).thenReturn(requestDtos); + + String result = mvc.perform(get("/requests/all") + .header("X-Sharer-User-Id", user.getId())) + .andExpect(status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertEquals(result, mapper.writeValueAsString(requestDtos)); + } + + @Test + @SneakyThrows + void getRequestById() { + ItemRequest itemRequest = testUtils.makeNewItemRequest(50, testUtils.makeNewUser(1)); + Item item = testUtils.makeNewFastItem(30); + ItemRequestBigDto bigDto = ItemRequestMapper.toBigDto(itemRequest, List.of(item)); + + when(requestService.getRequestById(itemRequest.getId())).thenReturn(bigDto); + + String result = mvc.perform(get("/requests/{requestId}", itemRequest.getId()) + .header("X-Sharer-User-Id", anyLong())) + .andExpect(status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertEquals(result, mapper.writeValueAsString(bigDto)); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/request/dto/ItemRequestBigDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/request/dto/ItemRequestBigDtoJsonTest.java new file mode 100644 index 0000000..f5a64b2 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/dto/ItemRequestBigDtoJsonTest.java @@ -0,0 +1,93 @@ +package ru.practicum.shareit.request.dto; + +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonContent; +import org.springframework.test.context.ContextConfiguration; +import ru.practicum.shareit.ShareItServer; +import ru.practicum.shareit.item.dto.ItemLowDto; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +@ContextConfiguration(classes = ShareItServer.class) +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class ItemRequestBigDtoJsonTest { + + final JacksonTester requestBigDtoJson; + + @Test + @SneakyThrows + void testItemRequestBigDto() { + ItemRequestBigDto requestBigDto = ItemRequestBigDto.builder() + .id(1L) + .description("this is description") + .createdDate(LocalDateTime.parse("2027-01-01T00:00:01")) + .requestor(UserDto.builder() + .id(5L) + .name("John") + .email("john.doe@mail.com") + .build() + ) + .itemDtos(List.of( + new ItemLowDto(7L, "first item dto", 14L), + new ItemLowDto(8L, "second item dto", 15L) + )) + .build(); + + JsonContent result = requestBigDtoJson.write(requestBigDto); + + // region ItemRequestBigDto + assertThat(result) + .extractingJsonPathNumberValue("$.id") + .isEqualTo(1); + assertThat(result) + .extractingJsonPathStringValue("$.description") + .isEqualTo("this is description"); + assertThat(result) + .extractingJsonPathStringValue("$.created") + .isEqualTo("2027-01-01T00:00:01"); + assertThat(result) + .extractingJsonPathNumberValue("$.requestor.id") + .isEqualTo(5); + assertThat(result) + .extractingJsonPathStringValue("$.requestor.name") + .isEqualTo("John"); + assertThat(result) + .extractingJsonPathStringValue("$.requestor.email") + .isEqualTo("john.doe@mail.com"); + // endregion ItemRequestBigDto + + // region items[0] + assertThat(result) + .extractingJsonPathNumberValue("$.items[0].id") + .isEqualTo(7); + assertThat(result) + .extractingJsonPathStringValue("$.items[0].name") + .isEqualTo("first item dto"); + assertThat(result) + .extractingJsonPathNumberValue("$.items[0].ownerId") + .isEqualTo(14); + // endregion items[0] + + // region items[1] + assertThat(result) + .extractingJsonPathNumberValue("$.items[1].id") + .isEqualTo(8); + assertThat(result) + .extractingJsonPathStringValue("$.items[1].name") + .isEqualTo("second item dto"); + assertThat(result) + .extractingJsonPathNumberValue("$.items[1].ownerId") + .isEqualTo(15); + // endregion items[1] + } +} diff --git a/server/src/test/java/ru/practicum/shareit/request/dto/ItemRequestDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/request/dto/ItemRequestDtoJsonTest.java new file mode 100644 index 0000000..6115bea --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/dto/ItemRequestDtoJsonTest.java @@ -0,0 +1,61 @@ +package ru.practicum.shareit.request.dto; + +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonContent; +import org.springframework.test.context.ContextConfiguration; +import ru.practicum.shareit.ShareItServer; +import ru.practicum.shareit.user.dto.UserDto; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +@ContextConfiguration(classes = ShareItServer.class) +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class ItemRequestDtoJsonTest { + + final JacksonTester requestDtoJson; + + @Test + @SneakyThrows + void testItemRequestDto() { + ItemRequestDto requestDto = ItemRequestDto.builder() + .id(1L) + .description("this is description") + .createdDate(LocalDateTime.parse("2027-01-01T00:00:01")) + .requestor(UserDto.builder() + .id(5L) + .name("John") + .email("john.doe@mail.com") + .build() + ) + .build(); + + JsonContent result = requestDtoJson.write(requestDto); + + assertThat(result) + .extractingJsonPathNumberValue("$.id") + .isEqualTo(1); + assertThat(result) + .extractingJsonPathStringValue("$.description") + .isEqualTo("this is description"); + assertThat(result) + .extractingJsonPathStringValue("$.created") + .isEqualTo("2027-01-01T00:00:01"); + assertThat(result) + .extractingJsonPathNumberValue("$.requestor.id") + .isEqualTo(5); + assertThat(result) + .extractingJsonPathStringValue("$.requestor.name") + .isEqualTo("John"); + assertThat(result) + .extractingJsonPathStringValue("$.requestor.email") + .isEqualTo("john.doe@mail.com"); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/request/repository/QueryDslRepositoryTest.java b/server/src/test/java/ru/practicum/shareit/request/repository/QueryDslRepositoryTest.java new file mode 100644 index 0000000..b3f999a --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/repository/QueryDslRepositoryTest.java @@ -0,0 +1,151 @@ +package ru.practicum.shareit.request.repository; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.transaction.Transactional; +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.Import; +import ru.practicum.shareit.TestUtils; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +@DataJpaTest +@Transactional +@Import({QuerydslTestConfig.class, QueryDslRepository.class}) +public class QueryDslRepositoryTest { + + @PersistenceContext + EntityManager entityManager; + + @Autowired + QueryDslRepository queryDslRepository; + + final TestUtils testUtils = new TestUtils(); + + private User addNewUser() { + User user = testUtils.makeNewUser(); + entityManager.persist(user); + return user; + } + + private ItemRequest addNewItemRequest(User requestor) { + ItemRequest itemRequest = testUtils.makeNewItemRequest(requestor, LocalDateTime.now()); + entityManager.persist(itemRequest); + return itemRequest; + } + + private Item addNewItem(User owner, ItemRequest request) { + Item item = testUtils.makeNewItem(owner, request); + entityManager.persist(item); + return item; + } + + @Test + void getMapOfItemsByRequestId() { + List users = List.of( + addNewUser(), + addNewUser(), + addNewUser(), + addNewUser(), + addNewUser(), + addNewUser() + ); + + List itemRequests = List.of( + addNewItemRequest(users.get(0)), + addNewItemRequest(users.get(1)), + addNewItemRequest(users.get(2)) + ); + + List items = List.of( + addNewItem(users.get(0), itemRequests.get(2)), + addNewItem(users.get(1), itemRequests.get(0)), + addNewItem(users.get(2), itemRequests.get(1)), + addNewItem(users.get(3), itemRequests.get(0)), + addNewItem(users.get(4), itemRequests.get(1)), + addNewItem(users.get(5), itemRequests.get(2)) + ); + + Map> localResult = new HashMap<>(); + for (Item item : items) { + localResult.computeIfAbsent( + item.getRequest().getId(), k -> new ArrayList<>()) + .add(item); + } + + List itemIds = localResult.keySet().stream().toList(); + + Map> repositoryResult = queryDslRepository.getMapOfItemsByRequestId(itemIds); + + assertThat(localResult, is(repositoryResult)); + } + + @Test + void getAllRequestsWithoutByUser() { + List users = List.of( + addNewUser(), + addNewUser(), + addNewUser() + ); + + List itemRequests = List.of( + addNewItemRequest(users.get(0)), + addNewItemRequest(users.get(1)), + addNewItemRequest(users.get(2)) + ); + + List result = queryDslRepository.getAllRequestsWithoutThisUserRequests(users.getFirst().getId()); + + assertThat(result.getFirst().getId(), is(itemRequests.getLast().getId())); + } + + @Test + void getAllItemsByRequest() { + List users = List.of( + addNewUser(), + addNewUser(), + addNewUser() + ); + + List itemRequests = List.of( + addNewItemRequest(users.get(0)), + addNewItemRequest(users.get(1)) + ); + + List items = List.of( + addNewItem(users.get(0), itemRequests.getLast()), + addNewItem(users.get(1), itemRequests.getFirst()), + addNewItem(users.get(2), itemRequests.getLast()), + addNewItem(users.get(0), itemRequests.getFirst()), + addNewItem(users.get(1), itemRequests.getLast()), + addNewItem(users.get(2), itemRequests.getLast()) + ); + + List first = items.stream() + .filter(item -> item.getRequest().equals(itemRequests.getFirst())) + .toList(); + + List last = items.stream() + .filter(item -> item.getRequest().equals(itemRequests.getLast())) + .toList(); + + List resultFirst = queryDslRepository.getAllItemsByRequest(itemRequests.getFirst().getId()); + List resultLast = queryDslRepository.getAllItemsByRequest(itemRequests.getLast().getId()); + + assertThat(first, is(resultFirst)); + assertThat(last, is(resultLast)); + } +} + diff --git a/server/src/test/java/ru/practicum/shareit/request/repository/QuerydslTestConfig.java b/server/src/test/java/ru/practicum/shareit/request/repository/QuerydslTestConfig.java new file mode 100644 index 0000000..a02e83d --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/repository/QuerydslTestConfig.java @@ -0,0 +1,15 @@ +package ru.practicum.shareit.request.repository; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; + +@TestConfiguration +public class QuerydslTestConfig { + + @Bean + public JPAQueryFactory queryFactory(EntityManager em) { + return new JPAQueryFactory(em); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/request/service/ItemRequestServiceImplTest.java b/server/src/test/java/ru/practicum/shareit/request/service/ItemRequestServiceImplTest.java new file mode 100644 index 0000000..74d0e71 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/request/service/ItemRequestServiceImplTest.java @@ -0,0 +1,203 @@ +package ru.practicum.shareit.request.service; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import ru.practicum.shareit.TestUtils; +import ru.practicum.shareit.item.dto.ItemLowDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.dto.ItemRequestBigDto; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.dto.ItemRequestMapper; +import ru.practicum.shareit.request.model.ItemRequest; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.request.repository.QueryDslRepository; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.util.UtilService; + +import java.util.*; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ItemRequestServiceImplTest { + + @Mock + UtilService utilService; + + @Mock + ItemRequestRepository itemRequestRepository; + + @Mock + QueryDslRepository queryDslRepository; + + @InjectMocks + ItemRequestServiceImpl requestService; + + final TestUtils testUtils = new TestUtils(); + + @Nested + class AddNewRequest { + + @Test + void whenCreatedDateIsNotNull() { + // region setup + ItemRequest request = testUtils.makeNewItemRequest(1, testUtils.makeNewUser(10)); + User requestor = request.getRequestor(); + ItemRequestDto dtoIn = ItemRequestDto.builder() + .description(request.getDescription()) + .createdDate(request.getCreatedDate()) + .build(); + ItemRequestDto dtoOut = ItemRequestMapper.toDto(request); + // endregion setup + + when(utilService.getUser(requestor.getId())).thenReturn(requestor); + when(itemRequestRepository.save(any(ItemRequest.class))).thenReturn(request); + ArgumentCaptor captor = ArgumentCaptor.forClass(ItemRequest.class); + + ItemRequestDto resultDto = requestService.addNewRequest(requestor.getId(), dtoIn); + + verify(itemRequestRepository).save(captor.capture()); + ItemRequest captured = captor.getValue(); + assertEquals(request.getDescription(), captured.getDescription()); + assertThat(resultDto, equalTo(dtoOut)); + } + + @Test + void whenCreatedDateIsNull() { + // region setup + ItemRequest request = testUtils.makeNewItemRequest(1, testUtils.makeNewUser(10)); + User requestor = request.getRequestor(); + ItemRequestDto dtoIn = ItemRequestDto.builder() + .description(request.getDescription()) + .createdDate(null) + .build(); + ItemRequestDto dtoOut = ItemRequestMapper.toDto(request); + // endregion setup + + when(utilService.getUser(requestor.getId())).thenReturn(requestor); + when(itemRequestRepository.save(any(ItemRequest.class))).thenReturn(request); + ArgumentCaptor captor = ArgumentCaptor.forClass(ItemRequest.class); + + ItemRequestDto resultDto = requestService.addNewRequest(requestor.getId(), dtoIn); + + verify(itemRequestRepository).save(captor.capture()); + ItemRequest captured = captor.getValue(); + assertEquals(request.getDescription(), captured.getDescription()); + assertThat(resultDto, equalTo(dtoOut)); + } + } + + @Nested + class GetRequestListByUser { + + @Test + void getRequestListByUser() { + // region setup + User requestor = testUtils.makeNewUser(5); + List items = List.of( + testUtils.makeNewFastItem(40), + testUtils.makeNewFastItem(41), + testUtils.makeNewFastItem(42) + ); + + List itemRequests = List.of( + testUtils.makeNewItemRequest(70, requestor), + testUtils.makeNewItemRequest(71, requestor) + ); + List requestIds = itemRequests.stream().map(ItemRequest::getId).toList(); + List bigDtos = List.of( + ItemRequestMapper + .toBigDto(itemRequests.getLast(), List.of(items.get(0), items.get(2))), + ItemRequestMapper + .toBigDto(itemRequests.getFirst(), List.of(items.get(0), items.get(1))) + ); + Map> bigMap = new HashMap<>(); + for (ItemRequestBigDto bigDto : bigDtos) { + List itemIds = bigDto.getItemDtos().stream().map(ItemLowDto::id).toList(); + bigMap.put( + bigDto.getId(), + items.stream().filter(item -> itemIds.contains(item.getId())) + .toList() + ); + } + // endregion setup + + doNothing().when(utilService).checkUser(requestor.getId()); + when(itemRequestRepository.findAllByRequestor_Id(requestor.getId())).thenReturn(itemRequests); + when(queryDslRepository.getMapOfItemsByRequestId(requestIds)).thenReturn(bigMap); + + List resultDtos = requestService.getRequestListByUser(requestor.getId()); + + assertThat(resultDtos, equalTo(bigDtos)); + } + + @Test + void getRequestListByUser_thenReturnEmptyList() { + User requestor = testUtils.makeNewUser(5); + + doNothing().when(utilService).checkUser(requestor.getId()); + when(itemRequestRepository.findAllByRequestor_Id(requestor.getId())).thenReturn(Collections.emptyList()); + + List resultDtos = requestService.getRequestListByUser(requestor.getId()); + + assertThat(resultDtos, equalTo(Collections.emptyList())); + } + } + + @Nested + class GetAllRequests { + + @Test + void getAllRequests() { + // region setup + Long userId = 1L; + List requests = List.of( + testUtils.makeNewItemRequest(10, testUtils.makeNewUser()), + testUtils.makeNewItemRequest(11, testUtils.makeNewUser()), + testUtils.makeNewItemRequest(12, testUtils.makeNewUser()) + ); + List dtos = requests.stream().map(ItemRequestMapper::toDto).toList(); + // endregion setup + + doNothing().when(utilService).checkUser(userId); + when(queryDslRepository.getAllRequestsWithoutThisUserRequests(userId)).thenReturn(requests); + + List resultDtos = requestService.getAllRequests(userId); + + assertThat(resultDtos, equalTo(dtos)); + } + } + + @Nested + class GetRequestById { + + @Test + void getRequestById() { + // region setup + ItemRequest request = testUtils.makeNewItemRequest(1, testUtils.makeNewUser(10)); + List items = List.of( + testUtils.makeNewFastItem(40), + testUtils.makeNewFastItem(41), + testUtils.makeNewFastItem(42) + ); + ItemRequestBigDto bigDto = ItemRequestMapper.toBigDto(request, items); + // endregion setup + + when(queryDslRepository.getAllItemsByRequest(request.getId())).thenReturn(items); + when(utilService.getItemRequest(request.getId())).thenReturn(request); + + ItemRequestBigDto resultDto = requestService.getRequestById(request.getId()); + + assertThat(resultDto, equalTo(bigDto)); + } + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/user/UserContextTest.java b/server/src/test/java/ru/practicum/shareit/user/UserContextTest.java new file mode 100644 index 0000000..7da607d --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/UserContextTest.java @@ -0,0 +1,133 @@ +package ru.practicum.shareit.user; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.EntityManagerFactory; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import ru.practicum.shareit.TestUtils; +import ru.practicum.shareit.booking.repository.BookingRepository; +import ru.practicum.shareit.item.repository.CommentRepository; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.request.repository.QueryDslRepository; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.dto.UserMapper; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; +import ru.practicum.shareit.util.error.ErrorHandler; +import ru.practicum.shareit.util.error.ErrorResponse; + +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.never; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@EnableAutoConfiguration(exclude = { + DataSourceAutoConfiguration.class, + HibernateJpaAutoConfiguration.class +}) +public class UserContextTest { + + final TestUtils testUtils = new TestUtils(); + + @Autowired + ObjectMapper mapper; + + @Autowired + private MockMvc mvc; + + @Autowired + UserController controller; + + // region mocks + @MockitoBean + UserRepository userRepository; + + @MockitoBean + ItemRepository itemRepository; + + @MockitoBean + ItemRequestRepository itemRequestRepository; + + @MockitoBean + BookingRepository bookingRepository; + + @MockitoBean + CommentRepository commentRepository; + + @MockitoBean + QueryDslRepository queryDslRepository; + + @MockitoBean + EntityManagerFactory entityManagerFactory; + // endregion mocks + + @BeforeEach + void setUp() { + mvc = MockMvcBuilders + .standaloneSetup(controller) + .setControllerAdvice(new ErrorHandler()) + .setMessageConverters(new MappingJackson2HttpMessageConverter(mapper)) + .build(); + } + + @Test + @SneakyThrows + void saveUser_whenThisUserAlredyExists_thenReturnErrorResponse() { + User user = testUtils.makeNewUser(1); + UserDto userDto = UserMapper.toUserDto(user); + + when(userRepository.existsByEmail(user.getEmail())).thenReturn(true); + + String result = mvc.perform(post("/users") + .content(mapper.writeValueAsString(userDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isConflict()) + .andReturn() + .getResponse() + .getContentAsString(); + + verify(userRepository, never()).save(any()); + assertEquals(result, + mapper.writeValueAsString(new ErrorResponse( + "Пользователь с такой почтой " + user.getEmail() + " уже есть.")) + ); + } + + @Test + @SneakyThrows + void saveUser_whenRequestBosyIsBroken_thenReturnError500() { + mvc.perform(post("/users") + .content(mapper.writeValueAsString(Optional.empty())) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is5xxServerError()) + .andReturn() + .getResponse() + .getContentAsString(); + + verify(userRepository, never()).save(any()); + } +} 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..da83bd3 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/UserControllerTest.java @@ -0,0 +1,206 @@ +package ru.practicum.shareit.user; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.service.UserService; +import ru.practicum.shareit.util.error.ErrorHandler; +import ru.practicum.shareit.util.exception.NotFoundException; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +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; + +@ExtendWith(MockitoExtension.class) +class UserControllerTest { + + final ObjectMapper mapper = new ObjectMapper(); + + @InjectMocks + UserController userController; + + @Mock + UserService userService; + + MockMvc mvc; + + UserDto userDto; + + @BeforeEach + void setUp() { + mvc = MockMvcBuilders + .standaloneSetup(userController) + .setControllerAdvice(new ErrorHandler()) + .build(); + + userDto = UserDto.builder() + .id(1L) + .name("John") + .email("john.doe@mail.com") + .build(); + } + + @Nested + class GetUser { + + @Test + @SneakyThrows + void returnResponseWithStatusOkAndBodyOfActualUserDto() { + long userId = 1L; + when(userService.getUser(userId)).thenReturn(userDto); + + mvc.perform(get("/users/{userId}", userId) + .content(mapper.writeValueAsString(userDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(userDto.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(userDto.getName()))) + .andExpect(jsonPath("$.email", is(userDto.getEmail()))); + + verify(userService).getUser(userId); + } + + @Test + @SneakyThrows + void returnResponseWithStatus404AndBodyOfNotFoundException() { + long userId = anyLong(); + when(userService.getUser(userId)).thenThrow(NotFoundException.class); + + mvc.perform(get("/users/{userId}", userId) + .content(mapper.writeValueAsString(userDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().is(404)); + + verify(userService).getUser(userId); + } + } + + @Nested + class GetAllUsers { + + @Test + @SneakyThrows + void returnResponseWithStatusOkAndBodyOfListWithActualUserDto() { + when(userService.getAllUsers()).thenReturn(List.of(userDto)); + + mvc.perform(get("/users") + .content(mapper.writeValueAsString(userDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.[0].id", is(userDto.getId()), Long.class)) + .andExpect(jsonPath("$.[0].name", is(userDto.getName()), String.class)) + .andExpect(jsonPath("$.[0].email", is(userDto.getEmail()), String.class)); + + verify(userService).getAllUsers(); + } + + @Test + @SneakyThrows + void returnResponseWithStatusOkAndBodyOfEmptyList() { + when(userService.getAllUsers()).thenReturn(Collections.emptyList()); + + mvc.perform(get("/users") + .content(mapper.writeValueAsString(userDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", is(Collections.emptyList()))); + + verify(userService).getAllUsers(); + } + } + + @Nested + class AddNewUser { + + @Test + @SneakyThrows + void returnResponseWithStatusOkAndBodyOfActualUserDto() { + when(userService.saveUser(any())) + .thenReturn(userDto); + + String result = mvc.perform(post("/users") + .content(mapper.writeValueAsString(userDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + + verify(userService).saveUser(any()); + assertEquals(result, mapper.writeValueAsString(userDto)); + } + } + + @Nested + class UpdateUser { + + @Test + @SneakyThrows + void test() { + UserDto newUserDto = UserDto.builder().name("NewUser").build(); + UserDto resultUserDto = UserDto.builder() + .id(userDto.getId()) + .name(newUserDto.getName()) + .email(userDto.getEmail()) + .build(); + when(userService.updateUser(userDto.getId(), newUserDto)).thenReturn(resultUserDto); + + mvc.perform(patch("/users/{userId}", 1L) + .content(mapper.writeValueAsString(newUserDto)) + .characterEncoding(StandardCharsets.UTF_8) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(resultUserDto.getId()), Long.class)) + .andExpect(jsonPath("$.name", is(resultUserDto.getName()), String.class)) + .andExpect(jsonPath("$.email", is(resultUserDto.getEmail()), String.class)); + + verify(userService).updateUser(userDto.getId(), newUserDto); + } + } + + @Nested + class DeleteUser { + + @Test + @SneakyThrows + void test() { + mvc.perform(delete("/users/{userId}", 1L)) + .andExpect(status().isOk()); + + verify(userService).deleteUser(1L); + } + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/user/dto/UserDtoJsonTest.java b/server/src/test/java/ru/practicum/shareit/user/dto/UserDtoJsonTest.java new file mode 100644 index 0000000..6701a4c --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/dto/UserDtoJsonTest.java @@ -0,0 +1,37 @@ +package ru.practicum.shareit.user.dto; + +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonContent; +import org.springframework.test.context.ContextConfiguration; +import ru.practicum.shareit.ShareItServer; + +import static org.assertj.core.api.Assertions.assertThat; + +@JsonTest +@ContextConfiguration(classes = ShareItServer.class) +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class UserDtoJsonTest { + + final JacksonTester json; + + @Test + @SneakyThrows + void testUserDto() { + UserDto userDto = UserDto.builder() + .id(1L) + .name("John") + .email("john.doe@mail.com") + .build(); + + JsonContent result = json.write(userDto); + + assertThat(result).extractingJsonPathNumberValue("$.id").isEqualTo(1); + assertThat(result).extractingJsonPathStringValue("$.name").isEqualTo("John"); + assertThat(result).extractingJsonPathStringValue("$.email").isEqualTo("john.doe@mail.com"); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/user/repository/UserRepositoryTest.java b/server/src/test/java/ru/practicum/shareit/user/repository/UserRepositoryTest.java new file mode 100644 index 0000000..d87bbb6 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/repository/UserRepositoryTest.java @@ -0,0 +1,38 @@ +package ru.practicum.shareit.user.repository; + +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import ru.practicum.shareit.user.model.User; + +import static org.junit.jupiter.api.Assertions.*; + +@DataJpaTest +@Transactional +class UserRepositoryTest { + + @Autowired + UserRepository userRepository; + + private User addNewUser() { + return userRepository.save(new User() + .setName("username") + .setEmail("email@mail.ru") + ); + } + + @Test + void existsByEmail() { + addNewUser(); + assertTrue(userRepository.existsByEmail("email@mail.ru")); + assertFalse(userRepository.existsByEmail("username@mail.ru")); + } + + @Test + void deleteUserById() { + User user = addNewUser(); + int count = userRepository.deleteUserById(user.getId()); + assertEquals(1, count); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/user/service/UserServiceTest.java b/server/src/test/java/ru/practicum/shareit/user/service/UserServiceTest.java new file mode 100644 index 0000000..2577e9d --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/user/service/UserServiceTest.java @@ -0,0 +1,193 @@ +package ru.practicum.shareit.user.service; + +import lombok.SneakyThrows; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import ru.practicum.shareit.TestUtils; + +import ru.practicum.shareit.user.dto.UserDto; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.repository.UserRepository; +import ru.practicum.shareit.util.UtilService; +import ru.practicum.shareit.util.exception.NotFoundException; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class UserServiceTest { + + @Mock + UserRepository userRepository; + + @Mock + UtilService utilService; + + @InjectMocks + UserServiceImpl userService; + + final TestUtils testUtils = new TestUtils(); + + @Nested + class SaveUser { + + @Test + void returnActualUserDto() { + User user = testUtils.makeNewUser(1); + UserDto userDto = UserDto.builder() + .id(user.getId()) + .name(user.getName()) + .email(user.getEmail()) + .build(); + when(userRepository.save(user)).thenReturn(user); + + UserDto resultDto = userService.saveUser(userDto); + assertThat(resultDto, testUtils.matchesUserDto(user)); + } + } + + @Nested + class GetUser { + + @Test + void returnActualUserDto() { + User user = testUtils.makeNewUser(1); + when(utilService.getUser(user.getId())).thenReturn(user); + + UserDto resultDto = userService.getUser(user.getId()); + + assertThat(resultDto, testUtils.matchesUserDto(user)); + } + } + + @Nested + class GetAllUsers { + + @Test + public void returnListOfActualUserDtos() { + List users = List.of( + testUtils.makeNewUser(1), + testUtils.makeNewUser(2), + testUtils.makeNewUser(3) + ); + when(userRepository.findAll()).thenReturn(users); + + List userDtos = userService.getAllUsers(); + + assertThat(userDtos, contains( + testUtils.matchesUserDto(users.get(0)), + testUtils.matchesUserDto(users.get(1)), + testUtils.matchesUserDto(users.get(2)) + )); + } + } + + @Nested + class UpdateUser { + + @Test + void whenUserWasRenamed_returnActualUserDto() { + User oldUser = testUtils.makeNewUser(1); + UserDto requestDto = UserDto.builder() + .name("new name") + .build(); + User newUser = new User() + .setID(oldUser.getId()) + .setName(requestDto.getName()) + .setEmail(oldUser.getEmail()); + + when(utilService.getUser(oldUser.getId())).thenReturn(testUtils.getCopyOfUser(oldUser)); + when(userRepository.save(newUser)).thenReturn(newUser); + + UserDto resultDto = userService.updateUser(oldUser.getId(), requestDto); + + assertNotEquals(resultDto.getName(), oldUser.getName()); + assertThat(resultDto, testUtils.matchesUserDto(newUser)); + verify(userRepository).save(newUser); + } + + @Test + void whenUserEmailWasChanged_returnActualUserDto() { + User oldUser = testUtils.makeNewUser(1); + UserDto requestDto = UserDto.builder() + .email(testUtils.generateRandomText(10, "@mail.com")) + .build(); + User newUser = new User() + .setID(oldUser.getId()) + .setName(oldUser.getName()) + .setEmail(requestDto.getEmail()); + + when(utilService.getUser(oldUser.getId())).thenReturn(testUtils.getCopyOfUser(oldUser)); + when(userRepository.save(newUser)).thenReturn(newUser); + + UserDto resultDto = userService.updateUser(oldUser.getId(), requestDto); + + assertNotEquals(resultDto.getEmail(), oldUser.getEmail()); + assertThat(resultDto, testUtils.matchesUserDto(newUser)); + verify(userRepository).save(newUser); + } + + @Test + @SneakyThrows + void whenUserEmailIsConflicted_returnDuplicatedDataException() { + User oldUser = testUtils.makeNewUser(1); + + Method checkEmail = userService.getClass().getDeclaredMethod("checkEmail", String.class); + checkEmail.setAccessible(true); + when(userRepository.existsByEmail(oldUser.getEmail())).thenReturn(true); + + assertThrows(InvocationTargetException.class, + () -> checkEmail.invoke(userService, oldUser.getEmail())); + } + } + + @Nested + class DeleteUser { + + @Test + void whenUserWasDeleted_returnVoid() { + User user = testUtils.makeNewUser(1); + when(userRepository.existsById(user.getId())).thenReturn(true); + when(userRepository.deleteUserById(user.getId())).thenReturn(1); + + userService.deleteUser(user.getId()); + verify(userRepository).existsById(user.getId()); + verify(userRepository).deleteUserById(user.getId()); + } + + @Test + void whenUserWasNotDeleted_returnRuntimeException() { + User user = testUtils.makeNewUser(1); + when(userRepository.existsById(user.getId())).thenReturn(true); + when(userRepository.deleteUserById(user.getId())).thenReturn(0); + + assertThrows(RuntimeException.class, () -> userService.deleteUser(user.getId())); + + verify(userRepository).existsById(user.getId()); + verify(userRepository).deleteUserById(user.getId()); + } + + @Test + void whenUserIsNotFound_returnNotFoundException() { + User user = testUtils.makeNewUser(1); + when(userRepository.existsById(user.getId())).thenReturn(false); + + assertThrows(NotFoundException.class, + () -> userService.deleteUser(user.getId())); + + verify(userRepository).existsById(user.getId()); + verify(userRepository, never()).deleteUserById(user.getId()); + } + } +} diff --git a/server/src/test/java/ru/practicum/shareit/util/UtilServiceTest.java b/server/src/test/java/ru/practicum/shareit/util/UtilServiceTest.java new file mode 100644 index 0000000..933515e --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/util/UtilServiceTest.java @@ -0,0 +1,50 @@ +package ru.practicum.shareit.util; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import ru.practicum.shareit.item.repository.ItemRepository; +import ru.practicum.shareit.request.repository.ItemRequestRepository; +import ru.practicum.shareit.user.repository.UserRepository; +import ru.practicum.shareit.util.exception.NotFoundException; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class UtilServiceTest { + + @Mock + private UserRepository userRepository; + + @Mock + private ItemRepository itemRepository; + + @Mock + private ItemRequestRepository itemRequestRepository; + + @InjectMocks + private UtilService utilService; + + @Test + void getUser() { + when(userRepository.findById(anyLong())).thenReturn(Optional.empty()); + assertThrows(NotFoundException.class, () -> utilService.getUser(anyLong())); + } + + @Test + void getItem() { + when(itemRepository.findById(anyLong())).thenReturn(Optional.empty()); + assertThrows(NotFoundException.class, () -> utilService.getItem(anyLong())); + } + + @Test + void getItemRequest() { + when(itemRequestRepository.findById(anyLong())).thenReturn(Optional.empty()); + assertThrows(NotFoundException.class, () -> utilService.getItemRequest(anyLong())); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/util/entity/BaseEntityJpaTest.java b/server/src/test/java/ru/practicum/shareit/util/entity/BaseEntityJpaTest.java new file mode 100644 index 0000000..c95cc38 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/util/entity/BaseEntityJpaTest.java @@ -0,0 +1,31 @@ +package ru.practicum.shareit.util.entity; + +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.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import ru.practicum.shareit.user.model.User; + +import static org.junit.jupiter.api.Assertions.*; + +@DataJpaTest +class BaseEntityJpaTest { + @Autowired + private TestEntityManager em; + + @Test + void equals_shouldWorkWithHibernateProxy() { + User entity = new User().setName("name").setEmail("email"); + em.persist(entity); + em.flush(); + em.clear(); + + User proxy = em.getEntityManager() + .getReference(User.class, entity.getId()); + + User found = em.find(User.class, entity.getId()); + + assertEquals(proxy, found); + assertEquals(found, proxy); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/util/entity/BaseEntityTest.java b/server/src/test/java/ru/practicum/shareit/util/entity/BaseEntityTest.java new file mode 100644 index 0000000..f5ec3d2 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/util/entity/BaseEntityTest.java @@ -0,0 +1,63 @@ +package ru.practicum.shareit.util.entity; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.user.model.User; + +import static org.junit.jupiter.api.Assertions.*; + +class BaseEntityTest { + + @Test + void equals_shouldReturnTrue_whenSameInstance() { + User entity = new User().setName("name").setEmail("email"); + + assertEquals(entity, entity); + } + + @Test + void equals_shouldReturnFalse_whenNull() { + User entity = new User().setName("name").setEmail("email"); + + assertNotEquals(null, entity); + } + + @Test + void equals_shouldReturnFalse_whenDifferentClasses() { + User entity = new User().setName("name").setEmail("email"); + Object other = new Object(); + + assertNotEquals(entity, other); + } + + @Test + void equals_shouldReturnFalse_whenIdsAreNull() { + User e1 = new User().setName("name1").setEmail("email1"); + User e2 = new User().setName("name2").setEmail("email2"); + + assertNotEquals(e1, e2); + } + + @Test + void equals_shouldReturnTrue_whenSameId() throws Exception { + User e1 = new User().setID(1L).setName("name1").setEmail("email1"); + User e2 = new User().setID(1L).setName("name2").setEmail("email2"); + + assertEquals(e1, e2); + } + + @Test + void equals_shouldReturnFalse_whenDifferentIds() throws Exception { + User e1 = new User().setID(1L).setName("name1").setEmail("email1"); + User e2 = new User().setID(2L).setName("name2").setEmail("email2"); + + assertNotEquals(e1, e2); + } + + @Test + void hashCode_shouldBeSameForSameClass() { + User e1 = new User(); + User e2 = new User(); + + assertEquals(e1.hashCode(), e2.hashCode()); + } +} \ No newline at end of file diff --git a/server/suppressions.xml b/server/suppressions.xml new file mode 100644 index 0000000..b2c6822 --- /dev/null +++ b/server/suppressions.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/dto/IncomingBookingDto.java b/src/main/java/ru/practicum/shareit/booking/dto/IncomingBookingDto.java deleted file mode 100644 index 4c95ab3..0000000 --- a/src/main/java/ru/practicum/shareit/booking/dto/IncomingBookingDto.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.practicum.shareit.booking.dto; - -import jakarta.validation.constraints.*; -import lombok.Data; -import ru.practicum.shareit.util.Marker; - -import java.time.LocalDateTime; - -@Data -@NotNull -public class IncomingBookingDto { - - @Positive(message = "id вещи - обязательный параметр, должен быть положительным числом") - long itemId; - - @FutureOrPresent(message = "Дата начала не может быть в прошлом") - @NotNull(groups = Marker.OnCreate.class, message = "Дата начала бронирования - обязательна") - LocalDateTime start; - - @Future(message = "Дата окончания должна быть в будущем") - @NotNull(groups = Marker.OnCreate.class, message = "Дата окончания бронирования - обязательна") - LocalDateTime end; -} diff --git a/src/main/java/ru/practicum/shareit/booking/model/Booking.java b/src/main/java/ru/practicum/shareit/booking/model/Booking.java deleted file mode 100644 index b0d322f..0000000 --- a/src/main/java/ru/practicum/shareit/booking/model/Booking.java +++ /dev/null @@ -1,64 +0,0 @@ -package ru.practicum.shareit.booking.model; - -import jakarta.persistence.*; -import lombok.*; -import lombok.experimental.Accessors; -import lombok.experimental.ExtensionMethod; -import ru.practicum.shareit.item.model.Item; -import ru.practicum.shareit.user.model.User; -import ru.practicum.shareit.util.hibernate.HibernateEqualsAndHashCode; - -import java.time.LocalDateTime; -import java.util.Objects; - -import static ru.practicum.shareit.util.hibernate.HibernateEqualsAndHashCode.*; - -/** - * TODO Sprint add-bookings. - */ - -@Data -@Entity -@NoArgsConstructor -@Accessors(chain = true) -@Table(name = "bookings") -@ExtensionMethod({HibernateEqualsAndHashCode.class}) -public class Booking { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - Long id; - - @Column(name = "start_date", nullable = false) - LocalDateTime start; - - @Column(name = "end_date", nullable = false) - LocalDateTime end; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "item_id", nullable = false) - Item item; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "booker_id", nullable = false) - User booker; - - @Enumerated(EnumType.STRING) - BookingStatus status; - - //region equals and hashCode - @Override - public final boolean equals(Object object) { - return this == object - || object != null - && persistentClass(this) == object.persistentClass() - && object instanceof Booking booking - && Objects.equals(getId(), booking.getId()); - } - - @Override - public final int hashCode() { - return persistentClass(this).hashCode(); - } - //endregion -} diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java b/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java deleted file mode 100644 index 7db7208..0000000 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemDto.java +++ /dev/null @@ -1,34 +0,0 @@ -package ru.practicum.shareit.item.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; -import jakarta.validation.constraints.*; -import lombok.Builder; -import lombok.Data; -import lombok.experimental.FieldDefaults; -import ru.practicum.shareit.util.Marker; - -/** - * TODO Sprint add-controllers. - */ -@Data -@NotNull -@Builder -@FieldDefaults(makeFinal = true) -public class ItemDto { - - Long id; - - @NotBlank(groups = Marker.OnCreate.class) - @Size(min = 1, max = 255) - String name; - - @NotBlank(groups = Marker.OnCreate.class) - @Size(min = 1, max = 255) - String description; - - @NotNull(groups = Marker.OnCreate.class) - Boolean available; - - @JsonProperty("request") - Long requestId; -} diff --git a/src/main/java/ru/practicum/shareit/item/model/Comment.java b/src/main/java/ru/practicum/shareit/item/model/Comment.java deleted file mode 100644 index 6c965e9..0000000 --- a/src/main/java/ru/practicum/shareit/item/model/Comment.java +++ /dev/null @@ -1,55 +0,0 @@ -package ru.practicum.shareit.item.model; - -import jakarta.persistence.*; -import lombok.*; -import lombok.experimental.Accessors; -import lombok.experimental.ExtensionMethod; -import ru.practicum.shareit.user.model.User; -import ru.practicum.shareit.util.hibernate.HibernateEqualsAndHashCode; - -import java.time.LocalDateTime; -import java.util.Objects; - -import static ru.practicum.shareit.util.hibernate.HibernateEqualsAndHashCode.*; - -@Data -@Entity -@NoArgsConstructor -@Accessors(chain = true) -@Table(name = "comments") -@ExtensionMethod({HibernateEqualsAndHashCode.class}) -public class Comment { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "item_id") - Item item; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "author_id") - User author; - - LocalDateTime createdDate; - - @Column(name = "comment_text") - String text; - - //region equals and hashCode - @Override - public final boolean equals(Object object) { - return this == object - || object != null - && persistentClass(this) == object.persistentClass() - && object instanceof Comment comment - && Objects.equals(getId(), comment.getId()); - } - - @Override - public final int hashCode() { - return persistentClass(this).hashCode(); - } - //endregion -} diff --git a/src/main/java/ru/practicum/shareit/item/model/Item.java b/src/main/java/ru/practicum/shareit/item/model/Item.java deleted file mode 100644 index f17ad6b..0000000 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ /dev/null @@ -1,63 +0,0 @@ -package ru.practicum.shareit.item.model; - -import jakarta.persistence.*; -import lombok.*; -import lombok.experimental.Accessors; -import lombok.experimental.ExtensionMethod; -import ru.practicum.shareit.request.model.ItemRequest; -import ru.practicum.shareit.user.model.User; -import ru.practicum.shareit.util.hibernate.HibernateEqualsAndHashCode; - -import java.util.Objects; - -import static ru.practicum.shareit.util.hibernate.HibernateEqualsAndHashCode.*; - -/** - * TODO Sprint add-controllers. - */ -@Data -@Entity -@NoArgsConstructor -@Accessors(chain = true) -@Table(name = "items") -@ExtensionMethod({HibernateEqualsAndHashCode.class}) -public class Item { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - Long id; - - @Column(name = "item_name", nullable = false, length = 100) - String name; - - @Column - String description; - - /// статус о том, доступна или нет вещь для аренды - @Column(nullable = false) - boolean available; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "owner_id", nullable = false) - User owner; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "request_id") - ItemRequest request; - - //region equals and hashCode - @Override - public final boolean equals(Object object) { - return this == object - || object != null - && persistentClass(this) == object.persistentClass() - && object instanceof Item item - && Objects.equals(getId(), item.getId()); - } - - @Override - public final int hashCode() { - return persistentClass(this).hashCode(); - } - //endregion -} 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 { -} diff --git a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java deleted file mode 100644 index 7b3ed54..0000000 --- a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.request.dto; - -/** - * TODO Sprint add-item-requests. - */ -public class ItemRequestDto { -} diff --git a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestMapper.java b/src/main/java/ru/practicum/shareit/request/dto/ItemRequestMapper.java deleted file mode 100644 index 6d9d394..0000000 --- a/src/main/java/ru/practicum/shareit/request/dto/ItemRequestMapper.java +++ /dev/null @@ -1,4 +0,0 @@ -package ru.practicum.shareit.request.dto; - -public class ItemRequestMapper { -} diff --git a/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java b/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java deleted file mode 100644 index 4eae728..0000000 --- a/src/main/java/ru/practicum/shareit/request/service/ItemRequestService.java +++ /dev/null @@ -1,4 +0,0 @@ -package ru.practicum.shareit.request.service; - -public interface ItemRequestService { -} diff --git a/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java b/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java deleted file mode 100644 index 95db20a..0000000 --- a/src/main/java/ru/practicum/shareit/request/service/ItemRequestServiceImpl.java +++ /dev/null @@ -1,4 +0,0 @@ -package ru.practicum.shareit.request.service; - -public class ItemRequestServiceImpl implements ItemRequestService { -} diff --git a/src/main/java/ru/practicum/shareit/user/model/User.java b/src/main/java/ru/practicum/shareit/user/model/User.java deleted file mode 100644 index c719c35..0000000 --- a/src/main/java/ru/practicum/shareit/user/model/User.java +++ /dev/null @@ -1,46 +0,0 @@ -package ru.practicum.shareit.user.model; - -import jakarta.persistence.*; -import lombok.*; -import lombok.experimental.Accessors; -import lombok.experimental.ExtensionMethod; -import ru.practicum.shareit.util.hibernate.HibernateEqualsAndHashCode; - -import java.util.Objects; - -import static ru.practicum.shareit.util.hibernate.HibernateEqualsAndHashCode.*; - -@Data -@Entity -@Table(name = "users") -@NoArgsConstructor -@Accessors(chain = true) -@ExtensionMethod({HibernateEqualsAndHashCode.class}) -public class User { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - Long id; - - @Column(name = "user_name", nullable = false) - String name; - - @Column(nullable = false, unique = true, length = 512) - String email; - - //region equals and hashCode - @Override - public final boolean equals(Object object) { - return this == object - || object != null - && persistentClass(this) == object.persistentClass() - && object instanceof User user - && Objects.equals(getId(), user.getId()); - } - - @Override - public final int hashCode() { - return persistentClass(this).hashCode(); - } - //endregion -} diff --git a/src/main/java/ru/practicum/shareit/util/datetime/FutureOrPresentWithTolerance.java b/src/main/java/ru/practicum/shareit/util/datetime/FutureOrPresentWithTolerance.java deleted file mode 100644 index 79857d6..0000000 --- a/src/main/java/ru/practicum/shareit/util/datetime/FutureOrPresentWithTolerance.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.practicum.shareit.util.datetime; - -import jakarta.validation.Constraint; -import jakarta.validation.Payload; - -import java.lang.annotation.*; - -/// Generated by GPT -@Documented -@Constraint(validatedBy = FutureOrPresentWithToleranceValidator.class) -@Target({ ElementType.FIELD, ElementType.PARAMETER }) -@Retention(RetentionPolicy.RUNTIME) -public @interface FutureOrPresentWithTolerance { - - String message() default "must be in the present or future (with tolerance)"; - - Class[] groups() default {}; - - Class[] payload() default {}; - - long toleranceMillis() default 0; - - String zone() default ""; - - TimeRoundingStrategy rounding() default TimeRoundingStrategy.NONE; -} diff --git a/src/main/java/ru/practicum/shareit/util/datetime/FutureOrPresentWithToleranceValidator.java b/src/main/java/ru/practicum/shareit/util/datetime/FutureOrPresentWithToleranceValidator.java deleted file mode 100644 index e4147a6..0000000 --- a/src/main/java/ru/practicum/shareit/util/datetime/FutureOrPresentWithToleranceValidator.java +++ /dev/null @@ -1,96 +0,0 @@ -package ru.practicum.shareit.util.datetime; - -import jakarta.validation.ConstraintValidator; -import jakarta.validation.ConstraintValidatorContext; - -import java.time.*; - -/// Generated by GPT -public class FutureOrPresentWithToleranceValidator - implements ConstraintValidator { - - long toleranceMillis; - String zoneIdStr; - TimeRoundingStrategy roundingStrategy; - - @Override - public void initialize(FutureOrPresentWithTolerance constraintAnnotation) { - this.toleranceMillis = constraintAnnotation.toleranceMillis(); - this.zoneIdStr = constraintAnnotation.zone(); - this.roundingStrategy = constraintAnnotation.rounding(); - } - - @Override - public boolean isValid(Object value, ConstraintValidatorContext context) { - if (value == null) { - return true; - } - - Clock clock = context.getClockProvider().getClock(); - - ZoneId zone = resolveZone(clock); - - Instant now = applyRounding(clock.instant()); - Instant valueInstant = toInstant(value, zone); - - if (valueInstant == null) { - return false; - } - - valueInstant = applyRounding(valueInstant); - - return valueInstant.toEpochMilli() >= (now.toEpochMilli() - toleranceMillis); - } - - private ZoneId resolveZone(Clock clock) { - if (zoneIdStr == null || zoneIdStr.isBlank()) { - return clock.getZone(); - } - return ZoneId.of(zoneIdStr); - } - - private Instant toInstant(Object value, ZoneId zone) { - - if (value instanceof Instant) { - return (Instant) value; - } - - if (value instanceof OffsetDateTime) { - return ((OffsetDateTime) value).toInstant(); - } - - if (value instanceof ZonedDateTime) { - return ((ZonedDateTime) value).toInstant(); - } - - if (value instanceof LocalDateTime) { - return ((LocalDateTime) value).atZone(zone).toInstant(); - } - - if (value instanceof LocalDate) { - return ((LocalDate) value).atStartOfDay(zone).toInstant(); - } - - return null; - } - - private Instant applyRounding(Instant instant) { - - switch (roundingStrategy) { - - case TRUNCATE_TO_SECONDS: - return instant.truncatedTo(java.time.temporal.ChronoUnit.SECONDS); - - case TRUNCATE_TO_MINUTES: - return instant.truncatedTo(java.time.temporal.ChronoUnit.MINUTES); - - case ROUND_TO_SECONDS: - long millis = instant.toEpochMilli(); - long rounded = Math.round(millis / 1000.0) * 1000; - return Instant.ofEpochMilli(rounded); - - default: - return instant; - } - } -} diff --git a/src/main/java/ru/practicum/shareit/util/datetime/TimeRoundingStrategy.java b/src/main/java/ru/practicum/shareit/util/datetime/TimeRoundingStrategy.java deleted file mode 100644 index 6ef8f41..0000000 --- a/src/main/java/ru/practicum/shareit/util/datetime/TimeRoundingStrategy.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.practicum.shareit.util.datetime; - -public enum TimeRoundingStrategy { - - NONE, // без округления - TRUNCATE_TO_SECONDS, // обрезать до секунд - TRUNCATE_TO_MINUTES, // обрезать до минут - ROUND_TO_SECONDS // математическое округление -} diff --git a/src/main/java/ru/practicum/shareit/util/error/ErrorHandler.java b/src/main/java/ru/practicum/shareit/util/error/ErrorHandler.java deleted file mode 100644 index 6b9f82b..0000000 --- a/src/main/java/ru/practicum/shareit/util/error/ErrorHandler.java +++ /dev/null @@ -1,117 +0,0 @@ -package ru.practicum.shareit.util.error; - -import jakarta.validation.ValidationException; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.validation.FieldError; -import org.springframework.validation.method.MethodValidationException; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.MissingRequestHeaderException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; -import ru.practicum.shareit.util.exception.*; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -@RestControllerAdvice -@Slf4j -public class ErrorHandler { - - @ResponseStatus(HttpStatus.NOT_IMPLEMENTED) - @ExceptionHandler - public ErrorResponse handleMethodNotImplemented(final MethodNotImplementedException e) { - return new ErrorResponse(e.getMessage()); - } - - @ResponseStatus(HttpStatus.NOT_FOUND) - @ExceptionHandler - public ErrorResponse handleNotFound(final NotFoundException e) { - return new ErrorResponse(e.getMessage()); - } - - @ResponseStatus(HttpStatus.FORBIDDEN) - @ExceptionHandler - public ErrorResponse handleSharingForbidden(final ForbiddenException e) { - return new ErrorResponse(e.getMessage()); - } - - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler - public ErrorResponse handleParameterNotValid(final ParameterNotValidException e) { - return new ErrorResponse("Некорректное значение параметра " + - e.getParameter() + ": " + e.getReason() - ); - } - - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler - public ErrorResponse handleBookingStatusException(final BookingStatusException e) { - return new ErrorResponse(e.getMessage()); - } - - @ResponseStatus(HttpStatus.CONFLICT) - @ExceptionHandler - public ErrorResponse handleDuplicatedData(final DuplicatedDataException e) { - return new ErrorResponse(e.getMessage()); - } - - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler - public ErrorResponse handleMethodValidationException(final MethodValidationException e) { - return new ErrorResponse(e.getAllErrors().getFirst().getDefaultMessage()); - } - - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(MethodArgumentTypeMismatchException.class) - public ErrorResponse handleMethodArgumentTypeMismatchException(final MethodArgumentTypeMismatchException e) { - return new ErrorResponse("Не верно указан параметр запроса: " + e.getPropertyName() + "=" + e.getValue()); - } - - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler - public ErrorResponse handleValidationException(final ValidationException e) { - return new ErrorResponse(e.getMessage()); - } - - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(MissingRequestHeaderException.class) - public ErrorResponse handleMissingRequestHeaderException(final MissingRequestHeaderException e) { - return new ErrorResponse("Отсутствует заголовок в запросе: " + e.getHeaderName()); - } - - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(HttpMessageNotReadableException.class) - public ErrorResponse handleEmptyBody() { - return new ErrorResponse("Тело запроса отсутствует или некорректно"); - } - - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity> handleMethodArgumentException(final MethodArgumentNotValidException e) { - Map errors = new HashMap<>(); - - e.getBindingResult().getAllErrors().forEach((error) -> { - String fieldName = ((FieldError) error).getField(); - String errorMessage = error.getDefaultMessage(); - errors.put(fieldName, errorMessage); - }); - - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .contentType(MediaType.APPLICATION_JSON) - .body(errors); - } - - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - @ExceptionHandler - public ErrorResponse handleAnyThrowable(final Throwable e) { - UUID uuid = UUID.randomUUID(); - log.error("UUID={} Message=\"{}.\"", uuid, e.getMessage()); - return new ErrorResponse("Произошла непредвиденная ошибка. UUID=" + uuid); - } -} diff --git a/src/main/java/ru/practicum/shareit/util/exception/ConditionsNotMetException.java b/src/main/java/ru/practicum/shareit/util/exception/ConditionsNotMetException.java deleted file mode 100644 index a49d237..0000000 --- a/src/main/java/ru/practicum/shareit/util/exception/ConditionsNotMetException.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.util.exception; - -public class ConditionsNotMetException extends RuntimeException { - public ConditionsNotMetException(String message) { - super(message); - } -} diff --git a/src/main/java/ru/practicum/shareit/util/exception/MethodNotImplementedException.java b/src/main/java/ru/practicum/shareit/util/exception/MethodNotImplementedException.java deleted file mode 100644 index f2dbc41..0000000 --- a/src/main/java/ru/practicum/shareit/util/exception/MethodNotImplementedException.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.util.exception; - -public class MethodNotImplementedException extends RuntimeException { - public MethodNotImplementedException(String className, String methodName) { - super("Method not implemented " + className + "." + methodName); - } -} diff --git a/src/main/java/ru/practicum/shareit/util/hibernate/HibernateEqualsAndHashCode.java b/src/main/java/ru/practicum/shareit/util/hibernate/HibernateEqualsAndHashCode.java deleted file mode 100644 index 298c12d..0000000 --- a/src/main/java/ru/practicum/shareit/util/hibernate/HibernateEqualsAndHashCode.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.practicum.shareit.util.hibernate; - -import lombok.experimental.UtilityClass; -import org.hibernate.proxy.HibernateProxy; - -@UtilityClass -public class HibernateEqualsAndHashCode { - public Class persistentClass(Object object) { - return object instanceof HibernateProxy proxy ? - proxy.getHibernateLazyInitializer().getPersistentClass() - : object.getClass(); - } -} diff --git a/src/main/java/ru/practicum/shareit/util/stub/MethodNotImplemented.java b/src/main/java/ru/practicum/shareit/util/stub/MethodNotImplemented.java deleted file mode 100644 index 7f210ac..0000000 --- a/src/main/java/ru/practicum/shareit/util/stub/MethodNotImplemented.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.practicum.shareit.util.stub; - -import java.lang.annotation.*; - -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface MethodNotImplemented { -} diff --git a/src/main/java/ru/practicum/shareit/util/stub/MethodNotImplementedBeanPostProcessor.java b/src/main/java/ru/practicum/shareit/util/stub/MethodNotImplementedBeanPostProcessor.java deleted file mode 100644 index 064d30c..0000000 --- a/src/main/java/ru/practicum/shareit/util/stub/MethodNotImplementedBeanPostProcessor.java +++ /dev/null @@ -1,54 +0,0 @@ -package ru.practicum.shareit.util.stub; - -import lombok.NonNull; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.stereotype.Component; -import ru.practicum.shareit.util.exception.MethodNotImplementedException; - -import java.lang.reflect.Method; - -@Component -public class MethodNotImplementedBeanPostProcessor implements BeanPostProcessor { - - @Override - public Object postProcessAfterInitialization(Object bean, @NonNull String beanName) - throws BeansException { - - Class targetClass = bean.getClass(); - - boolean hasAnnotatedMethod = false; - for (Method method : targetClass.getMethods()) { - if (method.isAnnotationPresent(MethodNotImplemented.class)) { - hasAnnotatedMethod = true; - break; - } - } - - if (!hasAnnotatedMethod) { - return bean; - } - - ProxyFactory proxyFactory = new ProxyFactory(bean); - - proxyFactory.addAdvice((org.aopalliance.intercept.MethodInterceptor) invocation -> { - Method method = invocation.getMethod(); - Method implMethod = targetClass.getMethod( - method.getName(), - method.getParameterTypes() - ); - - if (implMethod.isAnnotationPresent(MethodNotImplemented.class)) { - throw new MethodNotImplementedException( - targetClass.getSimpleName(), - method.getName() - ); - } - - return invocation.proceed(); - }); - - return proxyFactory.getProxy(); - } -} diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties deleted file mode 100644 index 7095f54..0000000 --- a/src/main/resources/application-test.properties +++ /dev/null @@ -1,12 +0,0 @@ -spring.jpa.hibernate.ddl-auto=none -spring.jpa.properties.hibernate.format_sql=true -spring.sql.init.mode=always -logging.level.org.springframework.orm.jpa=INFO -logging.level.org.springframework.transaction=INFO -logging.level.org.springframework.transaction.interceptor=TRACE -logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG - -spring.datasource.driver-class-name=org.h2.Driver -spring.datasource.url=jdbc:h2:file:./db/filmorate -spring.datasource.username=password -spring.datasource.password=sa diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 46af283..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,19 +0,0 @@ -spring.main.log-startup-info=false -spring.jackson.serialization.indent_output=true -spring.sql.init.mode=always -spring.jpa.hibernate.ddl-auto=validate -spring.jpa.properties.hibernate.format_sql=true - -spring.datasource.driver-class-name=org.postgresql.Driver -spring.datasource.url=jdbc:postgresql://localhost:5432/shareit -spring.datasource.username=dbuser -spring.datasource.password=12345 - -hibernate.jdbc.time-zone=UTC -hibernate.default-schema=public -hibernate.show-sql=true -hibernate.format-sql=true - -logging.level.org.hibernate.SQL=DEBUG -logging.level.org.hibernate.orm.jdbc.bind=trace -logging.level.org.zalando.logbook=TRACE diff --git a/src/test/java/ru/practicum/shareit/ShareItTests.java b/src/test/java/ru/practicum/shareit/ShareItTests.java deleted file mode 100644 index 4d79052..0000000 --- a/src/test/java/ru/practicum/shareit/ShareItTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.practicum.shareit; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class ShareItTests { - - @Test - void contextLoads() { - } - -}