diff --git a/.gitignore b/.gitignore index 549e00a..5eac309 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,4 @@ build/ !**/src/test/**/build/ ### VS Code ### -.vscode/ +.vscode/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 0d97b31..0000000 --- a/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM openjdk:21-jdk-slim -WORKDIR /app -COPY target/shareit-0.0.1-SNAPSHOT.jar app.jar -EXPOSE 8080 -ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..4f62d02 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,55 @@ +version: '3.8' +services: + postgres: + image: postgres:16 + container_name: postgres + environment: + POSTGRES_DB: shareit + POSTGRES_USER: progingir + POSTGRES_PASSWORD: 12345 + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - shareit-network + + shareit-server: + build: + context: ./server + dockerfile: Dockerfile + container_name: shareit-server + ports: + - "9090:9090" + environment: + SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/shareit + SPRING_DATASOURCE_USERNAME: progingir + SPRING_DATASOURCE_PASSWORD: 12345 + SPRING_JPA_HIBERNATE_DDL_AUTO: update + SPRING_JPA_SHOW_SQL: true + SPRING_JPA_OPEN_IN_VIEW: false + depends_on: + - postgres + networks: + - shareit-network + + shareit-gateway: + build: + context: ./gateway + dockerfile: Dockerfile + container_name: shareit-gateway + ports: + - "8080:8080" + environment: + SHAREIT_SERVER_URL: http://shareit-server:9090 + depends_on: + - shareit-server + networks: + - shareit-network + +networks: + shareit-network: + driver: bridge + +volumes: + postgres_data: \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index a00b694..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,16 +0,0 @@ -services: - shareit-server: - build: - context: . - dockerfile: Dockerfile - container_name: shareit-server - ports: - - "8080:8080" - volumes: - - ./data:/data # Для сохранения базы H2 на диске - environment: - - SPRING_DATASOURCE_URL=jdbc:h2:file:/data/shareit - - SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.h2.Driver - - SPRING_DATASOURCE_USERNAME=sa - - SPRING_DATASOURCE_PASSWORD= - - SPRING_JPA_HIBERNATE_DDL_AUTO=update \ No newline at end of file diff --git a/gateway/Dockerfile b/gateway/Dockerfile new file mode 100644 index 0000000..b63f0ba --- /dev/null +++ b/gateway/Dockerfile @@ -0,0 +1,4 @@ +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp +ARG JAR_FILE=target/*.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..5b3f55f --- /dev/null +++ b/gateway/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + ru.practicum + shareit + 0.0.1-SNAPSHOT + + shareit-gateway + shareit-gateway + Gateway for ShareIt application + + + 21 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-actuator + + + org.hibernate.validator + hibernate-validator + + + org.apache.httpcomponents.client5 + httpclient5 + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 17 + 17 + + + + + \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/ShareItGatewayApp.java b/gateway/src/main/java/ru/practicum/shareit/ShareItGatewayApp.java new file mode 100644 index 0000000..802387d --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/ShareItGatewayApp.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ShareItGatewayApp { + + public static void main(String[] args) { + SpringApplication.run(ShareItGatewayApp.class, args); + } +} \ No newline at end of file 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..9c05759 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingClient.java @@ -0,0 +1,57 @@ +package ru.practicum.shareit.booking; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.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.booking.dto.BookingShortDto; + +import java.util.HashMap; +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, String state, Integer from, Integer size) { + Map parameters = new HashMap<>(); + parameters.put("state", state); + parameters.put("from", from); + parameters.put("size", size); + return get("?state={state}&from={from}&size={size}", userId, parameters); + } + + public ResponseEntity bookItem(long userId, BookingShortDto requestDto) { + return post("", userId, requestDto); + } + + public ResponseEntity getBooking(long userId, Long bookingId) { + return get("/" + bookingId, userId); + } + + public ResponseEntity updateStatus(long userId, Long bookingId, boolean approved) { + Map parameters = new HashMap<>(); + parameters.put("bookingId", bookingId); + parameters.put("approved", approved); + return patch("/{bookingId}?approved={approved}", userId, parameters, null); + } + + public ResponseEntity getBookingsByOwnerId(long userId, String state, Integer from, Integer size) { + Map parameters = new HashMap<>(); + parameters.put("state", state); + parameters.put("from", from); + parameters.put("size", size); + return get("/owner?state={state}&from={from}&size={size}", userId, parameters); + } +} \ No newline at end of file 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..de18da7 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingController.java @@ -0,0 +1,54 @@ +package ru.practicum.shareit.booking; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +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.BookingShortDto; + +@Controller +@RequestMapping(path = "/bookings") +@RequiredArgsConstructor +@Validated +public class BookingController { + private final BookingClient bookingClient; + + @GetMapping + public ResponseEntity getBookings(@RequestHeader("X-Sharer-User-Id") long userId, + @RequestParam(name = "state", defaultValue = "ALL") String state, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size) { + return bookingClient.getBookings(userId, state, from, size); + } + + @PostMapping + public ResponseEntity bookItem(@RequestHeader("X-Sharer-User-Id") long userId, + @RequestBody @Valid BookingShortDto requestDto) { + return bookingClient.bookItem(userId, requestDto); + } + + @GetMapping("/{bookingId}") + public ResponseEntity getBooking(@RequestHeader("X-Sharer-User-Id") long userId, + @PathVariable Long bookingId) { + return bookingClient.getBooking(userId, bookingId); + } + + @PatchMapping("/{bookingId}") + public ResponseEntity update(@RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable long bookingId, + @RequestParam(name = "approved") Boolean approved) { + return bookingClient.updateStatus(userId, bookingId, approved); + } + + @GetMapping("/owner") + public ResponseEntity getAllBookingsByOwnerId(@RequestHeader("X-Sharer-User-Id") long userId, + @RequestParam(name = "state", defaultValue = "ALL") String state, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size) { + return bookingClient.getBookingsByOwnerId(userId, state, from, size); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/BookingStatus.java b/gateway/src/main/java/ru/practicum/shareit/booking/BookingStatus.java similarity index 74% rename from src/main/java/ru/practicum/shareit/booking/BookingStatus.java rename to gateway/src/main/java/ru/practicum/shareit/booking/BookingStatus.java index ca4ab9f..05bdbbb 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingStatus.java +++ b/gateway/src/main/java/ru/practicum/shareit/booking/BookingStatus.java @@ -1,6 +1,6 @@ package ru.practicum.shareit.booking; -enum BookingStatus { +public enum BookingStatus { WAITING, APPROVED, REJECTED, diff --git a/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingShortDto.java b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingShortDto.java new file mode 100644 index 0000000..48f24ff --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/booking/dto/BookingShortDto.java @@ -0,0 +1,32 @@ +package ru.practicum.shareit.booking.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import ru.practicum.shareit.booking.BookingStatus; + +import java.time.LocalDateTime; + +@Data +public class BookingShortDto { + private Long id; + + @NotNull(message = "Время начала бронирования не может быть пустым") + @FutureOrPresent(message = "Время начала бронирования не может быть в прошлом") + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime start; + + @NotNull(message = "Время окончания бронирования не может быть пустым") + @Future(message = "Время окончания бронирования должно быть в будущем") + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime end; + + @NotNull(message = "ID вещи не может быть пустым") + private Long itemId; + + private Long bookerId; + + private BookingStatus status; +} \ No newline at end of file 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..e0c82c5 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/client/BaseClient.java @@ -0,0 +1,114 @@ +package ru.practicum.shareit.client; + +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +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; + } + + public ResponseEntity get(String path) { + return get(path, null, null); + } + + protected ResponseEntity get(String path, long userId) { + return get(path, userId, null); + } + + public ResponseEntity get(String path, Long userId, @Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.GET, path, userId, parameters, null); + } + + public ResponseEntity post(String path, T body) { + return post(path, null, null, body); + } + + protected ResponseEntity post(String path, long userId, T body) { + return post(path, userId, null, body); + } + + protected ResponseEntity post(String path, Long userId, @Nullable Map parameters, T body) { + return makeAndSendRequest(HttpMethod.POST, 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, long userId, T body) { + return patch(path, userId, null, body); + } + + public ResponseEntity patch(String path, 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); + } + + public 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, 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); + } + + 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(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(); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/item/ItemApiController.java b/gateway/src/main/java/ru/practicum/shareit/item/ItemApiController.java new file mode 100644 index 0000000..debaf6c --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemApiController.java @@ -0,0 +1,62 @@ +package ru.practicum.shareit.item; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +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.CommentDto; +import ru.practicum.shareit.item.dto.NewItemRequest; +import ru.practicum.shareit.item.dto.UpdateItemRequest; + +@Controller +@RequestMapping(path = "/items") +@RequiredArgsConstructor +@Validated +public class ItemApiController { + private final ItemClient itemClient; + + @GetMapping + public ResponseEntity getItemsById(@RequestHeader("X-Sharer-User-Id") Long userId, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size) { + return itemClient.getItemsByUserId(userId, from, size); + } + + @GetMapping("/{itemId}") + public ResponseEntity getItemById(@RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable long itemId) { + return itemClient.getItemById(userId, itemId); + } + + @GetMapping("/search") + public ResponseEntity searchItemByText(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestParam(name = "text") String text, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size) { + return itemClient.searchItemByText(userId, text, from, size); + } + + @PostMapping + public ResponseEntity createItem(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody @Valid NewItemRequest itemDto) { + return itemClient.createItem(userId, itemDto); + } + + @PatchMapping("/{itemId}") + public ResponseEntity update(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody @Valid UpdateItemRequest itemDto, + @PathVariable long itemId) { + return itemClient.updateItem(userId, itemDto, itemId); + } + + @PostMapping("/{itemId}/comment") + public ResponseEntity comment(@RequestHeader("X-Sharer-User-Id") Long userId, + @RequestBody @Valid CommentDto commentDto, + @PathVariable long itemId) { + return itemClient.comment(userId, commentDto, itemId); + } +} \ 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..1410454 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/item/ItemClient.java @@ -0,0 +1,53 @@ +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.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.CommentDto; +import ru.practicum.shareit.item.dto.NewItemRequest; +import ru.practicum.shareit.item.dto.UpdateItemRequest; + +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 getItemsByUserId(long userId, Integer from, Integer size) { + Map parameters = Map.of("from", from, "size", size); + return get("?from={from}&size={size}", userId, parameters); + } + + public ResponseEntity getItemById(long userId, long itemId) { + return get("/" + itemId, userId); + } + + public ResponseEntity searchItemByText(long userId, String text, Integer from, Integer size) { + Map parameters = Map.of("text", text, "from", from, "size", size); + return get("/search?text={text}&from={from}&size={size}", userId, parameters); + } + + public ResponseEntity createItem(long userId, NewItemRequest itemDto) { + return post("", userId, itemDto); + } + + public ResponseEntity updateItem(long userId, UpdateItemRequest itemDto, long itemId) { + return patch("/" + itemId, userId, itemDto); + } + + public ResponseEntity comment(long userId, CommentDto commentDto, long itemId) { + return post("/" + itemId + "/comment", userId, commentDto); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/dto/CommentDto.java rename to gateway/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java diff --git a/src/main/java/ru/practicum/shareit/item/dto/NewItemRequest.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/NewItemRequest.java similarity index 86% rename from src/main/java/ru/practicum/shareit/item/dto/NewItemRequest.java rename to gateway/src/main/java/ru/practicum/shareit/item/dto/NewItemRequest.java index 5bbc871..78da01f 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/NewItemRequest.java +++ b/gateway/src/main/java/ru/practicum/shareit/item/dto/NewItemRequest.java @@ -10,17 +10,23 @@ public class NewItemRequest { @NotBlank(message = "Название не может быть пустым") private String name; + @NotBlank(message = "Описание не может быть пустым") private String description; + @NotNull(message = "Доступность должна быть указана") private Boolean available; + private Long requestId; + @JsonCreator public NewItemRequest(@JsonProperty("name") String name, @JsonProperty("description") String description, - @JsonProperty("available") Boolean available) { + @JsonProperty("available") Boolean available, + @JsonProperty("requestId") Long requestId) { this.name = name; this.description = description; this.available = available; + this.requestId = requestId; } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/dto/UpdateItemRequest.java b/gateway/src/main/java/ru/practicum/shareit/item/dto/UpdateItemRequest.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/dto/UpdateItemRequest.java rename to gateway/src/main/java/ru/practicum/shareit/item/dto/UpdateItemRequest.java 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..ff7b3da --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -0,0 +1,45 @@ +package ru.practicum.shareit.request; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +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.request.dto.ItemRequestDto; + +@Controller +@RequestMapping(path = "/requests") +@RequiredArgsConstructor +@Validated +public class ItemRequestController { + private final RequestClient requestClient; + + @PostMapping + public ResponseEntity createItemRequest(@RequestHeader("X-Sharer-User-Id") Long userId, + @Valid @RequestBody ItemRequestDto itemRequestDto) { + return requestClient.createRequest(userId, itemRequestDto); + } + + @GetMapping("/{requestId}") + public ResponseEntity getItemRequestByRequestId(@RequestHeader("X-Sharer-User-Id") Long userId, + @PathVariable long requestId) { + return requestClient.getItemRequestById(userId, requestId); + } + + @GetMapping + public ResponseEntity getAllItemRequestsByUserId(@RequestHeader("X-Sharer-User-Id") Long userId, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size) { + return requestClient.getItemRequestsByUserId(userId, from, size); + } + + @GetMapping("/all") + public ResponseEntity getAllItemRequestsByOthers(@RequestHeader("X-Sharer-User-Id") Long userId, + @PositiveOrZero @RequestParam(name = "from", defaultValue = "0") Integer from, + @Positive @RequestParam(name = "size", defaultValue = "10") Integer size) { + return requestClient.getItemRequestsByOthers(userId, from, size); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/RequestClient.java b/gateway/src/main/java/ru/practicum/shareit/request/RequestClient.java new file mode 100644 index 0000000..8861ff4 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/RequestClient.java @@ -0,0 +1,43 @@ +package ru.practicum.shareit.request; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.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.ItemRequestDto; + +import java.util.Map; + +@Service +public class RequestClient extends BaseClient { + private static final String API_PREFIX = "/requests"; + + @Autowired + public RequestClient(@Value("${shareit-server.url}") String serverUrl, RestTemplateBuilder builder) { + super(builder.uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl + API_PREFIX)) + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) + .build()); + } + + public ResponseEntity createRequest(long userId, ItemRequestDto itemRequestDto) { + return post("", userId, itemRequestDto); + } + + public ResponseEntity getItemRequestById(long userId, long requestId) { + return get("/" + requestId, userId); + } + + public ResponseEntity getItemRequestsByUserId(long userId, Integer from, Integer size) { + Map parameters = Map.of("from", from, "size", size); + return get("?from={from}&size={size}", userId, parameters); + } + + public ResponseEntity getItemRequestsByOthers(long userId, Integer from, Integer size) { + Map parameters = Map.of("from", from, "size", size); + return get("/all?from={from}&size={size}", userId, parameters); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemDto.java b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemDto.java new file mode 100644 index 0000000..d6c61ef --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemDto.java @@ -0,0 +1,13 @@ +package ru.practicum.shareit.request.dto; + +import lombok.Data; + +@Data +public class ItemDto { + private Long id; + private String name; + private String description; + private Boolean available; + private OwnerDto owner; + private Long requestId; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java new file mode 100644 index 0000000..0ed61fc --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java @@ -0,0 +1,23 @@ +package ru.practicum.shareit.request.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +public class ItemRequestDto { + private Long id; + + @NotBlank(message = "Описание запроса не может быть пустым") + private String description; + + private RequesterDto requester; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS") + private LocalDateTime created; + + private List items; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/dto/OwnerDto.java b/gateway/src/main/java/ru/practicum/shareit/request/dto/OwnerDto.java new file mode 100644 index 0000000..fe73c5c --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/dto/OwnerDto.java @@ -0,0 +1,10 @@ +package ru.practicum.shareit.request.dto; + +import lombok.Data; + +@Data +public class OwnerDto { + private Long id; + private String email; + private String name; +} \ No newline at end of file diff --git a/gateway/src/main/java/ru/practicum/shareit/request/dto/RequesterDto.java b/gateway/src/main/java/ru/practicum/shareit/request/dto/RequesterDto.java new file mode 100644 index 0000000..b2f5a47 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/request/dto/RequesterDto.java @@ -0,0 +1,10 @@ +package ru.practicum.shareit.request.dto; + +import lombok.Data; + +@Data +public class RequesterDto { + private Long id; + private String email; + private String name; +} 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..d43344a --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserClient.java @@ -0,0 +1,40 @@ +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.NewUserRequest; +import ru.practicum.shareit.user.dto.UpdateUserRequest; + +@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 getUserById(long userId) { + return get("/" + userId); + } + + public ResponseEntity addUser(NewUserRequest requestDto) { + return post("", requestDto); + } + + public ResponseEntity updateUser(long userId, UpdateUserRequest requestDto) { + return patch("/" + userId, requestDto); + } + + public ResponseEntity deleteUser(long userId) { + return delete("/" + userId); + } +} \ No newline at end of file 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..af40816 --- /dev/null +++ b/gateway/src/main/java/ru/practicum/shareit/user/UserController.java @@ -0,0 +1,38 @@ +package ru.practicum.shareit.user; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +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.NewUserRequest; +import ru.practicum.shareit.user.dto.UpdateUserRequest; + +@Controller +@RequestMapping(path = "/users") +@RequiredArgsConstructor +@Validated +public class UserController { + private final UserClient userClient; + + @GetMapping("/{userId}") + public ResponseEntity getUserById(@PathVariable long userId) { + return userClient.getUserById(userId); + } + + @PostMapping + public ResponseEntity createUser(@RequestBody @Valid NewUserRequest userRequestDto) { + return userClient.addUser(userRequestDto); + } + + @PatchMapping("/{userId}") + public ResponseEntity update(@PathVariable long userId, @RequestBody @Valid UpdateUserRequest userRequestDto) { + return userClient.updateUser(userId, userRequestDto); + } + + @DeleteMapping("/{userId}") + public ResponseEntity deleteUser(@PathVariable long userId) { + return userClient.deleteUser(userId); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/dto/NewUserRequest.java b/gateway/src/main/java/ru/practicum/shareit/user/dto/NewUserRequest.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/dto/NewUserRequest.java rename to gateway/src/main/java/ru/practicum/shareit/user/dto/NewUserRequest.java diff --git a/src/main/java/ru/practicum/shareit/user/dto/UpdateUserRequest.java b/gateway/src/main/java/ru/practicum/shareit/user/dto/UpdateUserRequest.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/dto/UpdateUserRequest.java rename to gateway/src/main/java/ru/practicum/shareit/user/dto/UpdateUserRequest.java diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserResponse.java b/gateway/src/main/java/ru/practicum/shareit/user/dto/UserResponse.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/dto/UserResponse.java rename to gateway/src/main/java/ru/practicum/shareit/user/dto/UserResponse.java diff --git a/gateway/src/main/resources/application.properties b/gateway/src/main/resources/application.properties new file mode 100644 index 0000000..efd0901 --- /dev/null +++ b/gateway/src/main/resources/application.properties @@ -0,0 +1,4 @@ +server.port=8080 +shareit-server.url=http://shareit-server:9090 +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration +logging.level.org.springframework.web.client.RestTemplate=DEBUG \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/BaseClientTest.java b/gateway/src/test/java/ru/practicum/shareit/BaseClientTest.java new file mode 100644 index 0000000..3306a0b --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/BaseClientTest.java @@ -0,0 +1,105 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.BeforeEach; +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.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; +import ru.practicum.shareit.client.BaseClient; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class BaseClientTest { + + @Mock + private RestTemplate restTemplate; + + @InjectMocks + private BaseClient baseClient; + + @BeforeEach + void setUp() { + baseClient = new BaseClient(restTemplate); + } + + @Test + void get_withoutParameters_success() { + ResponseEntity expectedResponse = ResponseEntity.ok("Success"); + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object.class))) + .thenReturn(expectedResponse); + + ResponseEntity response = baseClient.get("/test"); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("Success", response.getBody()); + verify(restTemplate).exchange(eq("/test"), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object.class)); + } + + @Test + void get_withUserIdAndParameters_success() { + ResponseEntity expectedResponse = ResponseEntity.ok("Success"); + Map parameters = Map.of("key", "value"); + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object.class), eq(parameters))) + .thenReturn(expectedResponse); + + ResponseEntity response = baseClient.get("/test", 1L, parameters); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("Success", response.getBody()); + verify(restTemplate).exchange(eq("/test"), eq(HttpMethod.GET), any(HttpEntity.class), eq(Object.class), eq(parameters)); + } + + @Test + void post_withBody_success() { + ResponseEntity expectedResponse = ResponseEntity.ok("Success"); + when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(Object.class))) + .thenReturn(expectedResponse); + + ResponseEntity response = baseClient.post("/test", "body"); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("Success", response.getBody()); + verify(restTemplate).exchange(eq("/test"), eq(HttpMethod.POST), any(HttpEntity.class), eq(Object.class)); + } + + @Test + void patch_withUserIdAndParameters_errorResponse() { + HttpClientErrorException exception = new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Bad Request"); + when(restTemplate.exchange(anyString(), eq(HttpMethod.PATCH), any(HttpEntity.class), eq(Object.class), anyMap())) + .thenThrow(exception); + + ResponseEntity response = baseClient.patch("/test", 1L, Map.of("key", "value"), null); + + assertNotNull(response); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + verify(restTemplate).exchange(eq("/test"), eq(HttpMethod.PATCH), any(HttpEntity.class), eq(Object.class), anyMap()); + } + + @Test + void delete_withUserId_success() { + ResponseEntity expectedResponse = ResponseEntity.noContent().build(); + when(restTemplate.exchange(anyString(), eq(HttpMethod.DELETE), any(HttpEntity.class), eq(Object.class))) + .thenReturn(expectedResponse); + + ResponseEntity response = baseClient.delete("/test", 1L); + + assertNotNull(response); + assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode()); + verify(restTemplate).exchange(eq("/test"), eq(HttpMethod.DELETE), any(HttpEntity.class), eq(Object.class)); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/BookingControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/BookingControllerTest.java new file mode 100644 index 0000000..7e12e6c --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/BookingControllerTest.java @@ -0,0 +1,101 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.BeforeEach; +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.ResponseEntity; +import ru.practicum.shareit.booking.BookingClient; +import ru.practicum.shareit.booking.BookingController; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.booking.dto.BookingShortDto; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class BookingControllerTest { + + @Mock + private BookingClient bookingClient; + + @InjectMocks + private BookingController bookingController; + + private BookingShortDto bookingShortDto; + + @BeforeEach + void setUp() { + bookingShortDto = new BookingShortDto(); + bookingShortDto.setId(1L); + bookingShortDto.setStart(LocalDateTime.now().plusDays(1)); + bookingShortDto.setEnd(LocalDateTime.now().plusDays(2)); + bookingShortDto.setItemId(1L); + bookingShortDto.setBookerId(1L); + bookingShortDto.setStatus(BookingStatus.WAITING); + } + + @Test + void getBookings() { + ResponseEntity expectedResponse = ResponseEntity.ok(new Object()); + when(bookingClient.getBookings(anyLong(), anyString(), anyInt(), anyInt())) + .thenReturn(expectedResponse); + + ResponseEntity response = bookingController.getBookings(1L, "ALL", 0, 10); + + assertEquals(expectedResponse, response); + } + + @Test + void bookItem() { + ResponseEntity expectedResponse = ResponseEntity.ok(new Object()); + when(bookingClient.bookItem(anyLong(), any(BookingShortDto.class))) + .thenReturn(expectedResponse); + + ResponseEntity response = bookingController.bookItem(1L, bookingShortDto); + + assertEquals(expectedResponse, response); + } + + @Test + void getBooking() { + ResponseEntity expectedResponse = ResponseEntity.ok(new Object()); + when(bookingClient.getBooking(anyLong(), anyLong())) + .thenReturn(expectedResponse); + + ResponseEntity response = bookingController.getBooking(1L, 1L); + + assertEquals(expectedResponse, response); + } + + @Test + void update() { + ResponseEntity expectedResponse = ResponseEntity.ok(new Object()); + when(bookingClient.updateStatus(anyLong(), anyLong(), anyBoolean())) + .thenReturn(expectedResponse); + + ResponseEntity response = bookingController.update(1L, 1L, true); + + assertEquals(expectedResponse, response); + } + + @Test + void getAllBookingsByOwnerId() { + ResponseEntity expectedResponse = ResponseEntity.ok(new Object()); + when(bookingClient.getBookingsByOwnerId(anyLong(), anyString(), anyInt(), anyInt())) + .thenReturn(expectedResponse); + + ResponseEntity response = bookingController.getAllBookingsByOwnerId(1L, "ALL", 0, 10); + + assertEquals(expectedResponse, response); + } +} diff --git a/gateway/src/test/java/ru/practicum/shareit/BookingShortDtoTest.java b/gateway/src/test/java/ru/practicum/shareit/BookingShortDtoTest.java new file mode 100644 index 0000000..96bcda9 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/BookingShortDtoTest.java @@ -0,0 +1,79 @@ +package ru.practicum.shareit; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.booking.dto.BookingShortDto; + +import java.time.LocalDateTime; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class BookingShortDtoTest { + + private Validator validator; + private BookingShortDto bookingShortDto; + + @BeforeEach + void setUp() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + + bookingShortDto = new BookingShortDto(); + bookingShortDto.setId(1L); + bookingShortDto.setStart(LocalDateTime.now().plusDays(1)); + bookingShortDto.setEnd(LocalDateTime.now().plusDays(2)); + bookingShortDto.setItemId(1L); + bookingShortDto.setBookerId(2L); + } + + @Test + void validBookingShortDto_noViolations() { + Set> violations = validator.validate(bookingShortDto); + assertTrue(violations.isEmpty()); + } + + @Test + void nullStartDate_validationFails() { + bookingShortDto.setStart(null); + Set> violations = validator.validate(bookingShortDto); + assertEquals(1, violations.size()); + assertEquals("Время начала бронирования не может быть пустым", violations.iterator().next().getMessage()); + } + + @Test + void pastStartDate_validationFails() { + bookingShortDto.setStart(LocalDateTime.now().minusDays(1)); + Set> violations = validator.validate(bookingShortDto); + assertEquals(1, violations.size()); + assertEquals("Время начала бронирования не может быть в прошлом", violations.iterator().next().getMessage()); + } + + @Test + void nullEndDate_validationFails() { + bookingShortDto.setEnd(null); + Set> violations = validator.validate(bookingShortDto); + assertEquals(1, violations.size()); + assertEquals("Время окончания бронирования не может быть пустым", violations.iterator().next().getMessage()); + } + + @Test + void pastEndDate_validationFails() { + bookingShortDto.setEnd(LocalDateTime.now().minusDays(1)); + Set> violations = validator.validate(bookingShortDto); + assertEquals(1, violations.size()); + assertEquals("Время окончания бронирования должно быть в будущем", violations.iterator().next().getMessage()); + } + + @Test + void nullItemId_validationFails() { + bookingShortDto.setItemId(null); + Set> violations = validator.validate(bookingShortDto); + assertEquals(1, violations.size()); + assertEquals("ID вещи не может быть пустым", violations.iterator().next().getMessage()); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/CommentDtoTest.java b/gateway/src/test/java/ru/practicum/shareit/CommentDtoTest.java new file mode 100644 index 0000000..e510d24 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/CommentDtoTest.java @@ -0,0 +1,43 @@ +package ru.practicum.shareit; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.item.dto.CommentDto; + +import java.time.LocalDateTime; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class CommentDtoTest { + + private Validator validator; + private ObjectMapper objectMapper; + private CommentDto commentDto; + + @BeforeEach + void setUp() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + objectMapper = new ObjectMapper(); + + commentDto = new CommentDto(); + commentDto.setId(1L); + commentDto.setText("Test Comment"); + commentDto.setItemId(1L); + commentDto.setAuthorId(1L); + commentDto.setAuthorName("Test User"); + commentDto.setCreated(LocalDateTime.now()); + } + + @Test + void validCommentDto_noViolations() { + Set> violations = validator.validate(commentDto); + assertTrue(violations.isEmpty()); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/ItemApiControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/ItemApiControllerTest.java new file mode 100644 index 0000000..4ecff4e --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/ItemApiControllerTest.java @@ -0,0 +1,131 @@ +package ru.practicum.shareit; + +import com.fasterxml.jackson.databind.ObjectMapper; +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.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.item.ItemApiController; +import ru.practicum.shareit.item.ItemClient; +import ru.practicum.shareit.item.dto.CommentDto; +import ru.practicum.shareit.item.dto.NewItemRequest; +import ru.practicum.shareit.item.dto.UpdateItemRequest; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(controllers = ItemApiController.class) +class ItemApiControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private ItemClient itemClient; + + @Autowired + private ObjectMapper objectMapper; + + private NewItemRequest newItemRequest; + private UpdateItemRequest updateItemRequest; + private CommentDto commentDto; + + @BeforeEach + void setUp() { + newItemRequest = new NewItemRequest("Test Item", "Test Description", true, null); + updateItemRequest = new UpdateItemRequest("Updated Item", "Updated Description", false); + commentDto = new CommentDto(); + commentDto.setId(1L); + commentDto.setText("Test Comment"); + commentDto.setItemId(1L); + commentDto.setAuthorId(1L); + commentDto.setAuthorName("Test User"); + commentDto.setCreated(LocalDateTime.now()); + } + + @Test + void getItemsById_success() throws Exception { + when(itemClient.getItemsByUserId(anyLong(), anyInt(), anyInt())) + .thenReturn(ResponseEntity.ok(List.of())); + + mockMvc.perform(get("/items") + .header("X-Sharer-User-Id", 1L) + .param("from", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()); + } + + @Test + void getItemById_success() throws Exception { + when(itemClient.getItemById(anyLong(), anyLong())) + .thenReturn(ResponseEntity.ok(newItemRequest)); + + mockMvc.perform(get("/items/1") + .header("X-Sharer-User-Id", 1L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("Test Item")); + } + + @Test + void searchItemByText_success() throws Exception { + when(itemClient.searchItemByText(anyLong(), anyString(), anyInt(), anyInt())) + .thenReturn(ResponseEntity.ok(List.of())); + + mockMvc.perform(get("/items/search") + .header("X-Sharer-User-Id", 1L) + .param("text", "test") + .param("from", "0") + .param("size", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()); + } + + @Test + void createItem_success() throws Exception { + when(itemClient.createItem(anyLong(), any(NewItemRequest.class))) + .thenReturn(ResponseEntity.ok(newItemRequest)); + + mockMvc.perform(post("/items") + .header("X-Sharer-User-Id", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newItemRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("Test Item")); + } + + @Test + void update_success() throws Exception { + when(itemClient.updateItem(anyLong(), any(UpdateItemRequest.class), anyLong())) + .thenReturn(ResponseEntity.ok(updateItemRequest)); + + mockMvc.perform(patch("/items/1") + .header("X-Sharer-User-Id", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateItemRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("Updated Item")); + } + + @Test + void comment_success() throws Exception { + when(itemClient.comment(anyLong(), any(CommentDto.class), anyLong())) + .thenReturn(ResponseEntity.ok(commentDto)); + + mockMvc.perform(post("/items/1/comment") + .header("X-Sharer-User-Id", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(commentDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.text").value("Test Comment")); + } +} diff --git a/gateway/src/test/java/ru/practicum/shareit/ItemDtoTest.java b/gateway/src/test/java/ru/practicum/shareit/ItemDtoTest.java new file mode 100644 index 0000000..43e2f60 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/ItemDtoTest.java @@ -0,0 +1,45 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.request.dto.ItemDto; +import ru.practicum.shareit.request.dto.OwnerDto; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class ItemDtoTest { + + @Test + void testItemDto() { + ItemDto itemDto = new ItemDto(); + itemDto.setId(1L); + itemDto.setName("Item"); + itemDto.setDescription("Description"); + itemDto.setAvailable(true); + itemDto.setRequestId(2L); + + OwnerDto owner = new OwnerDto(); + owner.setId(1L); + owner.setName("Owner"); + owner.setEmail("owner@example.com"); + itemDto.setOwner(owner); + + assertEquals(1L, itemDto.getId()); + assertEquals("Item", itemDto.getName()); + assertEquals("Description", itemDto.getDescription()); + assertEquals(true, itemDto.getAvailable()); + assertEquals(2L, itemDto.getRequestId()); + assertEquals(owner, itemDto.getOwner()); + } + + @Test + void testItemDtoWithNulls() { + ItemDto itemDto = new ItemDto(); + assertNull(itemDto.getId()); + assertNull(itemDto.getName()); + assertNull(itemDto.getDescription()); + assertNull(itemDto.getAvailable()); + assertNull(itemDto.getRequestId()); + assertNull(itemDto.getOwner()); + } +} diff --git a/gateway/src/test/java/ru/practicum/shareit/ItemRequestControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/ItemRequestControllerTest.java new file mode 100644 index 0000000..831bba3 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/ItemRequestControllerTest.java @@ -0,0 +1,81 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.BeforeEach; +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.ResponseEntity; +import ru.practicum.shareit.request.ItemRequestController; +import ru.practicum.shareit.request.RequestClient; +import ru.practicum.shareit.request.dto.ItemRequestDto; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ItemRequestControllerTest { + + @Mock + private RequestClient requestClient; + + @InjectMocks + private ItemRequestController itemRequestController; + + private ItemRequestDto itemRequestDto; + + @BeforeEach + void setUp() { + itemRequestDto = new ItemRequestDto(); + itemRequestDto.setId(1L); + itemRequestDto.setDescription("Test description"); + } + + @Test + void createItemRequest() { + ResponseEntity expectedResponse = ResponseEntity.ok(new Object()); + when(requestClient.createRequest(anyLong(), any(ItemRequestDto.class))) + .thenReturn(expectedResponse); + + ResponseEntity response = itemRequestController.createItemRequest(1L, itemRequestDto); + + assertEquals(expectedResponse, response); + } + + @Test + void getItemRequestByRequestId() { + ResponseEntity expectedResponse = ResponseEntity.ok(new Object()); + when(requestClient.getItemRequestById(anyLong(), anyLong())) + .thenReturn(expectedResponse); + + ResponseEntity response = itemRequestController.getItemRequestByRequestId(1L, 1L); + + assertEquals(expectedResponse, response); + } + + @Test + void getAllItemRequestsByUserId() { + ResponseEntity expectedResponse = ResponseEntity.ok(new Object()); + when(requestClient.getItemRequestsByUserId(anyLong(), anyInt(), anyInt())) + .thenReturn(expectedResponse); + + ResponseEntity response = itemRequestController.getAllItemRequestsByUserId(1L, 0, 10); + + assertEquals(expectedResponse, response); + } + + @Test + void getAllItemRequestsByOthers() { + ResponseEntity expectedResponse = ResponseEntity.ok(new Object()); + when(requestClient.getItemRequestsByOthers(anyLong(), anyInt(), anyInt())) + .thenReturn(expectedResponse); + + ResponseEntity response = itemRequestController.getAllItemRequestsByOthers(1L, 0, 10); + + assertEquals(expectedResponse, response); + } +} diff --git a/gateway/src/test/java/ru/practicum/shareit/ItemRequestDtoTest.java b/gateway/src/test/java/ru/practicum/shareit/ItemRequestDtoTest.java new file mode 100644 index 0000000..c7b9ca8 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/ItemRequestDtoTest.java @@ -0,0 +1,55 @@ +package ru.practicum.shareit; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.dto.RequesterDto; + +import java.time.LocalDateTime; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class ItemRequestDtoTest { + + private Validator validator; + private ItemRequestDto itemRequestDto; + + @BeforeEach + void setUp() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + + itemRequestDto = new ItemRequestDto(); + itemRequestDto.setId(1L); + itemRequestDto.setDescription("Test Description"); + itemRequestDto.setCreated(LocalDateTime.now()); + itemRequestDto.setRequester(new RequesterDto()); + } + + @Test + void validItemRequestDto_noViolations() { + Set> violations = validator.validate(itemRequestDto); + assertTrue(violations.isEmpty()); + } + + @Test + void blankDescription_validationFails() { + itemRequestDto.setDescription(""); + Set> violations = validator.validate(itemRequestDto); + assertEquals(1, violations.size()); + assertEquals("Описание запроса не может быть пустым", violations.iterator().next().getMessage()); + } + + @Test + void nullDescription_validationFails() { + itemRequestDto.setDescription(null); + Set> violations = validator.validate(itemRequestDto); + assertEquals(1, violations.size()); + assertEquals("Описание запроса не может быть пустым", violations.iterator().next().getMessage()); + } +} diff --git a/gateway/src/test/java/ru/practicum/shareit/NewItemRequestTest.java b/gateway/src/test/java/ru/practicum/shareit/NewItemRequestTest.java new file mode 100644 index 0000000..cb9ab75 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/NewItemRequestTest.java @@ -0,0 +1,73 @@ +package ru.practicum.shareit; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.item.dto.NewItemRequest; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class NewItemRequestTest { + + private Validator validator; + private ObjectMapper objectMapper; + private NewItemRequest newItemRequest; + + @BeforeEach + void setUp() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + objectMapper = new ObjectMapper(); + + newItemRequest = new NewItemRequest("Test Item", "Test Description", true, null); + } + + @Test + void validNewItemRequest_noViolations() { + Set> violations = validator.validate(newItemRequest); + assertTrue(violations.isEmpty()); + } + + @Test + void blankName_validationFails() { + newItemRequest.setName(""); + Set> violations = validator.validate(newItemRequest); + assertEquals(1, violations.size()); + assertEquals("Название не может быть пустым", violations.iterator().next().getMessage()); + } + + @Test + void nullDescription_validationFails() { + newItemRequest.setDescription(null); + Set> violations = validator.validate(newItemRequest); + assertEquals(1, violations.size()); + assertEquals("Описание не может быть пустым", violations.iterator().next().getMessage()); + } + + @Test + void nullAvailable_validationFails() { + newItemRequest.setAvailable(null); + Set> violations = validator.validate(newItemRequest); + assertEquals(1, violations.size()); + assertEquals("Доступность должна быть указана", violations.iterator().next().getMessage()); + } + + @Test + void serializeAndDeserialize_success() throws Exception { + String json = objectMapper.writeValueAsString(newItemRequest); + NewItemRequest deserialized = objectMapper.readValue(json, NewItemRequest.class); + + assertNotNull(deserialized); + assertEquals(newItemRequest.getName(), deserialized.getName()); + assertEquals(newItemRequest.getDescription(), deserialized.getDescription()); + assertEquals(newItemRequest.getAvailable(), deserialized.getAvailable()); + assertEquals(newItemRequest.getRequestId(), deserialized.getRequestId()); + } +} diff --git a/gateway/src/test/java/ru/practicum/shareit/OwnerDtoTest.java b/gateway/src/test/java/ru/practicum/shareit/OwnerDtoTest.java new file mode 100644 index 0000000..88efb62 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/OwnerDtoTest.java @@ -0,0 +1,30 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.request.dto.OwnerDto; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class OwnerDtoTest { + + @Test + void testOwnerDto() { + OwnerDto ownerDto = new OwnerDto(); + ownerDto.setId(1L); + ownerDto.setName("Owner"); + ownerDto.setEmail("owner@example.com"); + + assertEquals(1L, ownerDto.getId()); + assertEquals("Owner", ownerDto.getName()); + assertEquals("owner@example.com", ownerDto.getEmail()); + } + + @Test + void testOwnerDtoWithNulls() { + OwnerDto ownerDto = new OwnerDto(); + assertNull(ownerDto.getId()); + assertNull(ownerDto.getName()); + assertNull(ownerDto.getEmail()); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/UpdateItemRequestTest.java b/gateway/src/test/java/ru/practicum/shareit/UpdateItemRequestTest.java new file mode 100644 index 0000000..f7dd4f9 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/UpdateItemRequestTest.java @@ -0,0 +1,40 @@ +package ru.practicum.shareit; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.item.dto.UpdateItemRequest; + +import static org.junit.jupiter.api.Assertions.*; + +class UpdateItemRequestTest { + + private ObjectMapper objectMapper; + private UpdateItemRequest updateItemRequest; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + updateItemRequest = new UpdateItemRequest("Updated Item", "Updated Description", false); + } + + @Test + void partialUpdate_noViolations() { + UpdateItemRequest partialUpdate = new UpdateItemRequest(null, "New Description", null); + assertNull(partialUpdate.getName()); + assertEquals("New Description", partialUpdate.getDescription()); + assertNull(partialUpdate.getAvailable()); + } + + @Test + void serializeAndDeserialize_success() throws Exception { + String json = objectMapper.writeValueAsString(updateItemRequest); + UpdateItemRequest deserialized = objectMapper.readValue(json, UpdateItemRequest.class); + + assertNotNull(deserialized); + assertEquals(updateItemRequest.getName(), deserialized.getName()); + assertEquals(updateItemRequest.getDescription(), deserialized.getDescription()); + assertEquals(updateItemRequest.getAvailable(), deserialized.getAvailable()); + } +} \ No newline at end of file diff --git a/gateway/src/test/java/ru/practicum/shareit/UserControllerTest.java b/gateway/src/test/java/ru/practicum/shareit/UserControllerTest.java new file mode 100644 index 0000000..09e2845 --- /dev/null +++ b/gateway/src/test/java/ru/practicum/shareit/UserControllerTest.java @@ -0,0 +1,77 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.BeforeEach; +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.ResponseEntity; +import ru.practicum.shareit.user.UserClient; +import ru.practicum.shareit.user.UserController; +import ru.practicum.shareit.user.dto.NewUserRequest; +import ru.practicum.shareit.user.dto.UpdateUserRequest; + +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.when; + +@ExtendWith(MockitoExtension.class) +class UserControllerTest { + + @Mock + private UserClient userClient; + + @InjectMocks + private UserController userController; + + private NewUserRequest newUserRequest; + private UpdateUserRequest updateUserRequest; + + @BeforeEach + void setUp() { + newUserRequest = new NewUserRequest("John", "john@example.com"); + updateUserRequest = new UpdateUserRequest("John Updated", "john.updated@example.com"); + } + + @Test + void getUserById() { + ResponseEntity expectedResponse = ResponseEntity.ok(new Object()); + when(userClient.getUserById(anyLong())).thenReturn(expectedResponse); + + ResponseEntity response = userController.getUserById(1L); + + assertEquals(expectedResponse, response); + } + + @Test + void createUser() { + ResponseEntity expectedResponse = ResponseEntity.ok(new Object()); + when(userClient.addUser(any(NewUserRequest.class))).thenReturn(expectedResponse); + + ResponseEntity response = userController.createUser(newUserRequest); + + assertEquals(expectedResponse, response); + } + + @Test + void update() { + ResponseEntity expectedResponse = ResponseEntity.ok(new Object()); + when(userClient.updateUser(anyLong(), any(UpdateUserRequest.class))).thenReturn(expectedResponse); + + ResponseEntity response = userController.update(1L, updateUserRequest); + + assertEquals(expectedResponse, response); + } + + @Test + void deleteUser() { + ResponseEntity expectedResponse = ResponseEntity.ok().build(); + when(userClient.deleteUser(anyLong())).thenReturn(expectedResponse); + + ResponseEntity response = userController.deleteUser(1L); + + assertEquals(expectedResponse, response); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 180295d..a4714ca 100644 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,7 @@ ru.practicum shareit + pom 0.0.1-SNAPSHOT ShareIt @@ -19,100 +20,29 @@ 21 - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.projectlombok - lombok - true - - - - org.postgresql - postgresql - 42.7.3 - - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.boot - spring-boot-starter-validation - - - - org.slf4j - slf4j-api - 2.0.13 - - - ch.qos.logback - logback-classic - 1.4.14 - - - - jakarta.persistence - jakarta.persistence-api - 3.1.0 - - - - - com.fasterxml.jackson.core - jackson-databind - 2.15.2 - - - - org.springframework.boot - spring-boot-starter-data-jpa - 3.2.0 - - - - + + gateway + server + - - - src/main/resources - true - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - + + org.springframework.boot + spring-boot-maven-plugin + + + true + + + + org.projectlombok + lombok + + + + org.apache.maven.plugins maven-surefire-plugin @@ -196,27 +126,27 @@ LINE COVEREDRATIO - 0.9 + 0.5 BRANCH COVEREDRATIO - 0.6 + 0.2 COMPLEXITY COVEREDRATIO - 0.6 + 0.3 METHOD COVEREDRATIO - 0.7 + 0.4 CLASS MISSEDCOUNT - 1 + 5 @@ -259,17 +189,5 @@ - - coverage - - - - org.jacoco - jacoco-maven-plugin - - - - - \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..b63f0ba --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,4 @@ +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp +ARG JAR_FILE=target/*.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..5dd8414 --- /dev/null +++ b/server/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + ru.practicum + shareit + 0.0.1-SNAPSHOT + + + shareit-server + 0.0.1-SNAPSHOT + + ShareIt Server + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.postgresql + postgresql + runtime + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + coverage + + + + org.jacoco + jacoco-maven-plugin + + + + + + + \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/ShareItApp.java b/server/src/main/java/ru/practicum/shareit/ShareItApp.java similarity index 100% rename from src/main/java/ru/practicum/shareit/ShareItApp.java rename to server/src/main/java/ru/practicum/shareit/ShareItApp.java diff --git a/src/main/java/ru/practicum/shareit/booking/Booking.java b/server/src/main/java/ru/practicum/shareit/booking/Booking.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/Booking.java rename to server/src/main/java/ru/practicum/shareit/booking/Booking.java diff --git a/src/main/java/ru/practicum/shareit/booking/BookingController.java b/server/src/main/java/ru/practicum/shareit/booking/BookingController.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/BookingController.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingController.java diff --git a/src/main/java/ru/practicum/shareit/booking/BookingMapper.java b/server/src/main/java/ru/practicum/shareit/booking/BookingMapper.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/BookingMapper.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingMapper.java diff --git a/src/main/java/ru/practicum/shareit/booking/BookingRepository.java b/server/src/main/java/ru/practicum/shareit/booking/BookingRepository.java similarity index 95% rename from src/main/java/ru/practicum/shareit/booking/BookingRepository.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingRepository.java index 8705cd1..a46bce9 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingRepository.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingRepository.java @@ -7,8 +7,6 @@ import java.util.List; public interface BookingRepository extends JpaRepository { - - // Для getUserBookings List findByBookerId(Long bookerId, Sort sort); List findByBookerIdAndStartBeforeAndEndAfter(Long bookerId, LocalDateTime start, LocalDateTime end, Sort sort); @@ -19,7 +17,6 @@ public interface BookingRepository extends JpaRepository { List findByBookerIdAndStatus(Long bookerId, BookingStatus status, Sort sort); - // Для getOwnerBookings List findByItemOwnerId(Long ownerId, Sort sort); List findByItemOwnerIdAndStartBeforeAndEndAfter(Long ownerId, LocalDateTime start, LocalDateTime end, Sort sort); diff --git a/src/main/java/ru/practicum/shareit/booking/BookingService.java b/server/src/main/java/ru/practicum/shareit/booking/BookingService.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/BookingService.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingService.java diff --git a/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java b/server/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java similarity index 93% rename from src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java index a795e92..5c45450 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingServiceImpl.java @@ -22,7 +22,7 @@ public class BookingServiceImpl implements BookingService { private final BookingRepository bookingRepository; private final UserRepository userRepository; private final ItemRepository itemRepository; - private final BookingMapper bookingMapper; // Добавляем маппер + private final BookingMapper bookingMapper; @Override public BookingDto createBooking(BookingDto bookingDto, Long userId) { @@ -64,7 +64,7 @@ public BookingDto createBooking(BookingDto bookingDto, Long userId) { booking.setStatus(BookingStatus.WAITING); Booking savedBooking = bookingRepository.save(booking); - return bookingMapper.toDto(savedBooking); // Используем маппер + return bookingMapper.toDto(savedBooking); } @Override @@ -80,7 +80,7 @@ public BookingDto updateBooking(Long bookingId, Long userId, Boolean approved) { booking.setStatus(approved ? BookingStatus.APPROVED : BookingStatus.REJECTED); booking = bookingRepository.save(booking); - return bookingMapper.toDto(booking); // Используем маппер + return bookingMapper.toDto(booking); } @Override @@ -90,7 +90,7 @@ public BookingDto getBooking(Long bookingId, Long userId) { if (!booking.getBooker().getId().equals(userId) && !booking.getItem().getOwner().getId().equals(userId)) { throw new ForbiddenAccessException("Access denied"); } - return bookingMapper.toDto(booking); // Используем маппер + return bookingMapper.toDto(booking); } @Override @@ -120,7 +120,7 @@ public List getUserBookings(Long userId, String state) { default: bookings = bookingRepository.findByBookerId(userId, sort); } - return bookings.stream().map(bookingMapper::toDto).collect(Collectors.toList()); // Используем маппер + return bookings.stream().map(bookingMapper::toDto).collect(Collectors.toList()); } @Override @@ -150,6 +150,6 @@ public List getOwnerBookings(Long userId, String state) { default: bookings = bookingRepository.findByItemOwnerId(userId, sort); } - return bookings.stream().map(bookingMapper::toDto).collect(Collectors.toList()); // Используем маппер + return bookings.stream().map(bookingMapper::toDto).collect(Collectors.toList()); } } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/BookingShortDto.java b/server/src/main/java/ru/practicum/shareit/booking/BookingShortDto.java similarity index 99% rename from src/main/java/ru/practicum/shareit/booking/BookingShortDto.java rename to server/src/main/java/ru/practicum/shareit/booking/BookingShortDto.java index bf3190d..531e0f2 100644 --- a/src/main/java/ru/practicum/shareit/booking/BookingShortDto.java +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingShortDto.java @@ -15,4 +15,4 @@ public class BookingShortDto { @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") private LocalDateTime end; -} +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/booking/BookingStatus.java b/server/src/main/java/ru/practicum/shareit/booking/BookingStatus.java new file mode 100644 index 0000000..05bdbbb --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/booking/BookingStatus.java @@ -0,0 +1,7 @@ +package ru.practicum.shareit.booking; + +public enum BookingStatus { + WAITING, + APPROVED, + REJECTED, +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookerDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookerDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dto/BookerDto.java rename to server/src/main/java/ru/practicum/shareit/booking/dto/BookerDto.java diff --git a/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java rename to server/src/main/java/ru/practicum/shareit/booking/dto/BookingDto.java diff --git a/src/main/java/ru/practicum/shareit/booking/dto/ItemDto.java b/server/src/main/java/ru/practicum/shareit/booking/dto/ItemDto.java similarity index 100% rename from src/main/java/ru/practicum/shareit/booking/dto/ItemDto.java rename to server/src/main/java/ru/practicum/shareit/booking/dto/ItemDto.java diff --git a/src/main/java/ru/practicum/shareit/exception/ApiError.java b/server/src/main/java/ru/practicum/shareit/exception/ApiError.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/ApiError.java rename to server/src/main/java/ru/practicum/shareit/exception/ApiError.java diff --git a/src/main/java/ru/practicum/shareit/exception/EmailDuplicateException.java b/server/src/main/java/ru/practicum/shareit/exception/EmailDuplicateException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/EmailDuplicateException.java rename to server/src/main/java/ru/practicum/shareit/exception/EmailDuplicateException.java diff --git a/src/main/java/ru/practicum/shareit/exception/ForbiddenAccessException.java b/server/src/main/java/ru/practicum/shareit/exception/ForbiddenAccessException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/ForbiddenAccessException.java rename to server/src/main/java/ru/practicum/shareit/exception/ForbiddenAccessException.java diff --git a/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java b/server/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java rename to server/src/main/java/ru/practicum/shareit/exception/GlobalExceptionHandler.java diff --git a/src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java b/server/src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java rename to server/src/main/java/ru/practicum/shareit/exception/ItemNotFoundException.java diff --git a/src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java b/server/src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java similarity index 100% rename from src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java rename to server/src/main/java/ru/practicum/shareit/exception/UserNotFoundException.java diff --git a/src/main/java/ru/practicum/shareit/item/ItemApiController.java b/server/src/main/java/ru/practicum/shareit/item/ItemApiController.java similarity index 93% rename from src/main/java/ru/practicum/shareit/item/ItemApiController.java rename to server/src/main/java/ru/practicum/shareit/item/ItemApiController.java index 75da6de..b1426dd 100644 --- a/src/main/java/ru/practicum/shareit/item/ItemApiController.java +++ b/server/src/main/java/ru/practicum/shareit/item/ItemApiController.java @@ -1,6 +1,5 @@ package ru.practicum.shareit.item; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -36,7 +35,7 @@ public ResponseEntity findById(@RequestHeader(USER_ID_HEADER) Long @PostMapping public ResponseEntity addItem(@RequestHeader(USER_ID_HEADER) Long userId, - @RequestBody @Valid NewItemRequest request) { + @RequestBody NewItemRequest request) { log.info("Добавление нового предмета для пользователя с ID: {}", userId); return ResponseEntity.ok(itemManager.addItem(request, userId)); } @@ -44,7 +43,7 @@ public ResponseEntity addItem(@RequestHeader(USER_ID_HEADER) Long @PatchMapping("/{id}") public ResponseEntity updateItem(@RequestHeader(USER_ID_HEADER) Long userId, @PathVariable Long id, - @RequestBody @Valid UpdateItemRequest request) { + @RequestBody UpdateItemRequest request) { log.info("Обновление предмета с ID: {} пользователем с ID: {}", id, userId); if (id == null) { @@ -73,7 +72,7 @@ public ResponseEntity removeItem(@RequestHeader(USER_ID_HEADER) Long userI @PostMapping("/{itemId}/comment") public ResponseEntity addComment(@RequestHeader(USER_ID_HEADER) Long userId, @PathVariable Long itemId, - @RequestBody @Valid CommentDto commentDto) { + @RequestBody CommentDto commentDto) { log.info("Добавление комментария к предмету с ID: {} пользователем с ID: {}", itemId, userId); return ResponseEntity.ok(itemManager.addComment(commentDto, userId, itemId)); } diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java new file mode 100644 index 0000000..84cb1fa --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/CommentDto.java @@ -0,0 +1,15 @@ +package ru.practicum.shareit.item.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class CommentDto { + private Long id; + private String text; + private Long itemId; + private Long authorId; + private String authorName; + private LocalDateTime created; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemResponse.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemResponse.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/dto/ItemResponse.java rename to server/src/main/java/ru/practicum/shareit/item/dto/ItemResponse.java diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemTransformer.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemTransformer.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/dto/ItemTransformer.java rename to server/src/main/java/ru/practicum/shareit/item/dto/ItemTransformer.java diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemTransformerImpl.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemTransformerImpl.java similarity index 88% rename from src/main/java/ru/practicum/shareit/item/dto/ItemTransformerImpl.java rename to server/src/main/java/ru/practicum/shareit/item/dto/ItemTransformerImpl.java index 6be737b..4c1ce3b 100644 --- a/src/main/java/ru/practicum/shareit/item/dto/ItemTransformerImpl.java +++ b/server/src/main/java/ru/practicum/shareit/item/dto/ItemTransformerImpl.java @@ -7,6 +7,8 @@ import ru.practicum.shareit.booking.BookingRepository; import ru.practicum.shareit.booking.BookingShortDto; import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.ItemRequest; +import ru.practicum.shareit.request.ItemRequestRepository; import java.time.LocalDateTime; import java.util.Comparator; @@ -16,6 +18,7 @@ @RequiredArgsConstructor public class ItemTransformerImpl implements ItemTransformer { private final BookingRepository bookingRepository; + private final ItemRequestRepository requestRepository; @Override public ItemResponse toResponse(Item item) { @@ -68,6 +71,11 @@ public Item toItem(NewItemRequest request) { item.setName(request.getName()); item.setDescription(request.getDescription()); item.setAvailable(request.getAvailable() != null && request.getAvailable()); + if (request.getRequestId() != null) { + ItemRequest itemRequest = requestRepository.findById(request.getRequestId()) + .orElseThrow(() -> new IllegalArgumentException("Запрос с ID " + request.getRequestId() + " не найден")); + item.setItemRequest(itemRequest); + } return item; } diff --git a/src/main/java/ru/practicum/shareit/item/dto/ItemWithBookingsResponse.java b/server/src/main/java/ru/practicum/shareit/item/dto/ItemWithBookingsResponse.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/dto/ItemWithBookingsResponse.java rename to server/src/main/java/ru/practicum/shareit/item/dto/ItemWithBookingsResponse.java diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/NewItemRequest.java b/server/src/main/java/ru/practicum/shareit/item/dto/NewItemRequest.java new file mode 100644 index 0000000..49eda08 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/NewItemRequest.java @@ -0,0 +1,24 @@ +package ru.practicum.shareit.item.dto; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class NewItemRequest { + private String name; + private String description; + private Boolean available; + private Long requestId; + + @JsonCreator + public NewItemRequest(@JsonProperty("name") String name, + @JsonProperty("description") String description, + @JsonProperty("available") Boolean available, + @JsonProperty("requestId") Long requestId) { + this.name = name; + this.description = description; + this.available = available; + this.requestId = requestId; + } +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/item/dto/UpdateItemRequest.java b/server/src/main/java/ru/practicum/shareit/item/dto/UpdateItemRequest.java new file mode 100644 index 0000000..80f64a0 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/item/dto/UpdateItemRequest.java @@ -0,0 +1,21 @@ +package ru.practicum.shareit.item.dto; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class UpdateItemRequest { + private String name; + private String description; + private Boolean available; + + @JsonCreator + public UpdateItemRequest(@JsonProperty("name") String name, + @JsonProperty("description") String description, + @JsonProperty("available") Boolean available) { + this.name = name; + this.description = description; + this.available = available; + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/model/Comment.java b/server/src/main/java/ru/practicum/shareit/item/model/Comment.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/model/Comment.java rename to server/src/main/java/ru/practicum/shareit/item/model/Comment.java diff --git a/src/main/java/ru/practicum/shareit/item/model/CommentRepository.java b/server/src/main/java/ru/practicum/shareit/item/model/CommentRepository.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/model/CommentRepository.java rename to server/src/main/java/ru/practicum/shareit/item/model/CommentRepository.java diff --git a/src/main/java/ru/practicum/shareit/item/model/Item.java b/server/src/main/java/ru/practicum/shareit/item/model/Item.java similarity index 82% rename from src/main/java/ru/practicum/shareit/item/model/Item.java rename to server/src/main/java/ru/practicum/shareit/item/model/Item.java index 55cd899..13ea48a 100644 --- a/src/main/java/ru/practicum/shareit/item/model/Item.java +++ b/server/src/main/java/ru/practicum/shareit/item/model/Item.java @@ -3,6 +3,7 @@ import jakarta.persistence.*; import lombok.Data; import lombok.NoArgsConstructor; +import ru.practicum.shareit.request.ItemRequest; import ru.practicum.shareit.user.model.User; @Data @@ -26,4 +27,8 @@ public class Item { @Column(name = "is_available", nullable = false) private boolean available = false; + + @ManyToOne + @JoinColumn(name = "request_id") + private ItemRequest itemRequest; } \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/item/model/ItemManager.java b/server/src/main/java/ru/practicum/shareit/item/model/ItemManager.java similarity index 100% rename from src/main/java/ru/practicum/shareit/item/model/ItemManager.java rename to server/src/main/java/ru/practicum/shareit/item/model/ItemManager.java diff --git a/src/main/java/ru/practicum/shareit/item/model/ItemManagerImpl.java b/server/src/main/java/ru/practicum/shareit/item/model/ItemManagerImpl.java similarity index 98% rename from src/main/java/ru/practicum/shareit/item/model/ItemManagerImpl.java rename to server/src/main/java/ru/practicum/shareit/item/model/ItemManagerImpl.java index 6725271..7d94e21 100644 --- a/src/main/java/ru/practicum/shareit/item/model/ItemManagerImpl.java +++ b/server/src/main/java/ru/practicum/shareit/item/model/ItemManagerImpl.java @@ -37,7 +37,7 @@ public class ItemManagerImpl implements ItemManager { @Override public List fetchAllItems() { List items = itemRepository.findAll().stream() - .map(item -> transformer.toResponse(item, List.of(), null)) // Передаём null для userId + .map(item -> transformer.toResponse(item, List.of(), null)) .toList(); log.debug("Получено {} предметов", items.size()); return items; @@ -75,7 +75,7 @@ public ItemResponse findItemById(Long id, Long userId) { List comments = commentRepository.findByItemId(id).stream() .map(this::toCommentDto) .collect(Collectors.toList()); - return transformer.toResponse(item, comments, userId); // Передаём userId + return transformer.toResponse(item, comments, userId); } @Override diff --git a/src/main/java/ru/practicum/shareit/item/model/ItemRepository.java b/server/src/main/java/ru/practicum/shareit/item/model/ItemRepository.java similarity index 55% rename from src/main/java/ru/practicum/shareit/item/model/ItemRepository.java rename to server/src/main/java/ru/practicum/shareit/item/model/ItemRepository.java index 68fcb2c..fea2a1c 100644 --- a/src/main/java/ru/practicum/shareit/item/model/ItemRepository.java +++ b/server/src/main/java/ru/practicum/shareit/item/model/ItemRepository.java @@ -1,15 +1,25 @@ package ru.practicum.shareit.item.model; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import java.util.List; public interface ItemRepository extends JpaRepository { + List findByOwnerId(Long ownerId, Pageable pageable); + List findByOwnerId(Long ownerId); + @Query("SELECT i FROM Item i WHERE i.available = true AND " + + "(LOWER(i.name) LIKE LOWER(CONCAT('%', ?1, '%')) OR " + + "LOWER(i.description) LIKE LOWER(CONCAT('%', ?1, '%')))") + List search(String text, Pageable pageable); + @Query("SELECT i FROM Item i WHERE i.available = true AND " + "(LOWER(i.name) LIKE LOWER(CONCAT('%', ?1, '%')) OR " + "LOWER(i.description) LIKE LOWER(CONCAT('%', ?1, '%')))") List search(String text); + + List findByItemRequest_Id(Long requestId); } \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequest.java new file mode 100644 index 0000000..faa4a9c --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequest.java @@ -0,0 +1,28 @@ +package ru.practicum.shareit.request; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "requests") +@Getter +@Setter +public class ItemRequest { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String description; + + @ManyToOne + @JoinColumn(name = "requestor_id", nullable = false) + private User requestor; + + @Column(name = "created", nullable = false) + private LocalDateTime created; +} \ No newline at end of file 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..2b415f4 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestController.java @@ -0,0 +1,42 @@ +package ru.practicum.shareit.request; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.practicum.shareit.request.dto.ItemRequestDto; + +import java.util.List; + +@RestController +@RequestMapping(path = "/requests") +@RequiredArgsConstructor +public class ItemRequestController { + private final ItemRequestService requestService; + private static final String USER_ID_HEADER = "X-Sharer-User-Id"; + + @PostMapping + public ResponseEntity createRequest(@RequestHeader(USER_ID_HEADER) Long userId, + @RequestBody ItemRequestDto requestDto) { + return ResponseEntity.ok(requestService.createRequest(requestDto, userId)); + } + + @GetMapping + public ResponseEntity> getUserRequests(@RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size) { + return ResponseEntity.ok(requestService.getUserRequests(userId, from, size)); + } + + @GetMapping("/all") + public ResponseEntity> getAllRequests(@RequestHeader(USER_ID_HEADER) Long userId, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size) { + return ResponseEntity.ok(requestService.getAllRequests(userId, from, size)); + } + + @GetMapping("/{requestId}") + public ResponseEntity getRequestById(@RequestHeader(USER_ID_HEADER) Long userId, + @PathVariable Long requestId) { + return ResponseEntity.ok(requestService.getRequestById(requestId, userId)); + } +} diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestMapper.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestMapper.java new file mode 100644 index 0000000..63c24ea --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestMapper.java @@ -0,0 +1,72 @@ +package ru.practicum.shareit.request; + +import org.springframework.stereotype.Component; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.dto.ItemDto; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.dto.OwnerDto; +import ru.practicum.shareit.request.dto.RequesterDto; +import ru.practicum.shareit.user.model.User; + +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class ItemRequestMapper { + + public ItemRequestDto toDto(ItemRequest request, List items) { + ItemRequestDto dto = new ItemRequestDto(); + dto.setId(request.getId()); + dto.setDescription(request.getDescription() != null ? request.getDescription() : ""); + + RequesterDto requester = new RequesterDto(); + User requestor = request.getRequestor(); + if (requestor != null) { + requester.setId(requestor.getId()); + requester.setEmail(requestor.getEmail() != null ? requestor.getEmail() : ""); + requester.setName(requestor.getName() != null ? requestor.getName() : ""); + } else { + requester.setName(""); + requester.setEmail(""); + } + dto.setRequester(requester); + + dto.setCreated(request.getCreated()); + + dto.setItems(items != null ? items.stream() + .map(this::toItemDto) + .collect(Collectors.toList()) : List.of()); + + return dto; + } + + public ItemRequest toEntity(ItemRequestDto dto) { + ItemRequest request = new ItemRequest(); + request.setDescription(dto.getDescription() != null ? dto.getDescription() : ""); + return request; + } + + private ItemDto toItemDto(Item item) { + ItemDto itemDto = new ItemDto(); + itemDto.setId(item.getId()); + itemDto.setName(item.getName() != null ? item.getName() : ""); + itemDto.setDescription(item.getDescription() != null ? item.getDescription() : ""); + itemDto.setAvailable(item.isAvailable()); + + OwnerDto owner = new OwnerDto(); + User ownerUser = item.getOwner(); + if (ownerUser != null) { + owner.setId(ownerUser.getId()); + owner.setEmail(ownerUser.getEmail() != null ? ownerUser.getEmail() : ""); + owner.setName(ownerUser.getName() != null ? ownerUser.getName() : ""); + } else { + owner.setEmail(""); + owner.setName(""); + } + itemDto.setOwner(owner); + + itemDto.setRequestId(item.getItemRequest() != null ? item.getItemRequest().getId() : null); + + return itemDto; + } +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestRepository.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestRepository.java new file mode 100644 index 0000000..5db788f --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestRepository.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit.request; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ItemRequestRepository extends JpaRepository { + List findByRequestorId(Long requestorId, Pageable pageable); + + List findByRequestorIdNot(Long requestorId, Pageable pageable); +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestService.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestService.java new file mode 100644 index 0000000..3876cc5 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestService.java @@ -0,0 +1,15 @@ +package ru.practicum.shareit.request; + +import ru.practicum.shareit.request.dto.ItemRequestDto; + +import java.util.List; + +public interface ItemRequestService { + ItemRequestDto createRequest(ItemRequestDto requestDto, Long userId); + + List getUserRequests(Long userId, int from, int size); + + List getAllRequests(Long userId, int from, int size); + + ItemRequestDto getRequestById(Long requestId, Long userId); +} diff --git a/server/src/main/java/ru/practicum/shareit/request/ItemRequestServiceImpl.java b/server/src/main/java/ru/practicum/shareit/request/ItemRequestServiceImpl.java new file mode 100644 index 0000000..d4c363b --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/ItemRequestServiceImpl.java @@ -0,0 +1,98 @@ +package ru.practicum.shareit.request; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import ru.practicum.shareit.exception.UserNotFoundException; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.model.ItemRepository; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.model.UserRepository; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ItemRequestServiceImpl implements ItemRequestService { + private final ItemRequestRepository requestRepository; + private final UserRepository userRepository; + private final ItemRepository itemRepository; + private final ItemRequestMapper mapper; + + @Override + public ItemRequestDto createRequest(ItemRequestDto requestDto, Long userId) { + log.info("Создание запроса для пользователя с ID {}", userId); + User requestor = userRepository.findById(userId) + .orElseThrow(() -> { + log.warn("Пользователь с ID {} не найден", userId); + return new UserNotFoundException("Пользователь с ID " + userId + " не найден"); + }); + ItemRequest request = mapper.toEntity(requestDto); + request.setRequestor(requestor); + request.setCreated(LocalDateTime.now()); + request = requestRepository.save(request); + log.debug("Создан запрос с ID {}", request.getId()); + + List items = itemRepository.findByItemRequest_Id(request.getId()); + return mapper.toDto(request, items); + } + + @Override + public List getUserRequests(Long userId, int from, int size) { + log.info("Получение запросов пользователя с ID {}, from={}, size={}", userId, from, size); + userRepository.findById(userId) + .orElseThrow(() -> { + log.warn("Пользователь с ID {} не найден", userId); + return new UserNotFoundException("Пользователь с ID " + userId + " не найден"); + }); + PageRequest page = PageRequest.of(from / size, size, Sort.by("created").descending()); + List requests = requestRepository.findByRequestorId(userId, page); + return requests.stream() + .map(req -> mapper.toDto(req, itemRepository.findByItemRequest_Id(req.getId()))) + .collect(Collectors.toList()); + } + + @Override + public List getAllRequests(Long userId, int from, int size) { + log.info("Получение всех запросов, кроме пользователя с ID {}, from={}, size={}", userId, from, size); + userRepository.findById(userId) + .orElseThrow(() -> { + log.warn("Пользователь с ID {} не найден", userId); + return new UserNotFoundException("Пользователь с ID " + userId + " не найден"); + }); + PageRequest page = PageRequest.of(from / size, size, Sort.by("created").descending()); + List requests = requestRepository.findByRequestorIdNot(userId, page); + return requests.stream() + .map(req -> mapper.toDto(req, itemRepository.findByItemRequest_Id(req.getId()))) + .collect(Collectors.toList()); + } + + @Override + public ItemRequestDto getRequestById(Long requestId, Long userId) { + log.info("Получение запроса с ID {} пользователем с ID {}", requestId, userId); + userRepository.findById(userId) + .orElseThrow(() -> { + log.warn("Пользователь с ID {} не найден", userId); + return new UserNotFoundException("Пользователь с ID " + userId + " не найден"); + }); + ItemRequest request = requestRepository.findById(requestId) + .orElseThrow(() -> { + log.warn("Запрос с ID {} не найден", requestId); + return new IllegalArgumentException("Запрос с ID " + requestId + " не найден"); + }); + if (request.getDescription() == null) { + log.warn("Описание запроса с ID {} равно null", requestId); + request.setDescription(""); + } + List items = itemRepository.findByItemRequest_Id(requestId); + ItemRequestDto dto = mapper.toDto(request, items != null ? items : List.of()); + log.debug("Возвращён DTO для запроса с ID {}: {}", requestId, dto); + return dto; + } +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/ItemDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/ItemDto.java new file mode 100644 index 0000000..d6c61ef --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dto/ItemDto.java @@ -0,0 +1,13 @@ +package ru.practicum.shareit.request.dto; + +import lombok.Data; + +@Data +public class ItemDto { + private Long id; + private String name; + private String description; + private Boolean available; + private OwnerDto owner; + private Long requestId; +} \ No newline at end of file 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..9223977 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dto/ItemRequestDto.java @@ -0,0 +1,21 @@ +package ru.practicum.shareit.request.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +public class ItemRequestDto { + private Long id; + + private String description; + + private RequesterDto requester; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS") + private LocalDateTime created; + + private List items; +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/OwnerDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/OwnerDto.java new file mode 100644 index 0000000..e6d4687 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dto/OwnerDto.java @@ -0,0 +1,10 @@ +package ru.practicum.shareit.request.dto; + +import lombok.Data; + +@Data +public class OwnerDto { + private Long id; + private String email; + private String name; +} diff --git a/server/src/main/java/ru/practicum/shareit/request/dto/RequesterDto.java b/server/src/main/java/ru/practicum/shareit/request/dto/RequesterDto.java new file mode 100644 index 0000000..6c5a79c --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/request/dto/RequesterDto.java @@ -0,0 +1,10 @@ +package ru.practicum.shareit.request.dto; + +import lombok.Data; + +@Data +public class RequesterDto { + private Long id; + private String email; + private String name; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/UserApiController.java b/server/src/main/java/ru/practicum/shareit/user/UserApiController.java similarity index 91% rename from src/main/java/ru/practicum/shareit/user/UserApiController.java rename to server/src/main/java/ru/practicum/shareit/user/UserApiController.java index 0cb31b2..2363b52 100644 --- a/src/main/java/ru/practicum/shareit/user/UserApiController.java +++ b/server/src/main/java/ru/practicum/shareit/user/UserApiController.java @@ -1,6 +1,5 @@ package ru.practicum.shareit.user; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -26,7 +25,7 @@ public ResponseEntity> fetchAllUsers() { } @PostMapping - public ResponseEntity addUser(@RequestBody @Valid NewUserRequest request) { + public ResponseEntity addUser(@RequestBody NewUserRequest request) { log.info("Добавление нового пользователя"); return ResponseEntity.ok(userManager.addUser(request)); } @@ -38,7 +37,7 @@ public ResponseEntity findById(@PathVariable Long id) { } @PatchMapping("/{id}") - public ResponseEntity updateUser(@RequestBody @Valid UpdateUserRequest request, + public ResponseEntity updateUser(@RequestBody UpdateUserRequest request, @PathVariable Long id) { log.info("Обновление пользователя с ID: {}", id); return ResponseEntity.ok(userManager.modifyUser(request, id)); diff --git a/server/src/main/java/ru/practicum/shareit/user/dto/NewUserRequest.java b/server/src/main/java/ru/practicum/shareit/user/dto/NewUserRequest.java new file mode 100644 index 0000000..db025ed --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/user/dto/NewUserRequest.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class NewUserRequest { + private String name; + private String email; +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/user/dto/UpdateUserRequest.java b/server/src/main/java/ru/practicum/shareit/user/dto/UpdateUserRequest.java new file mode 100644 index 0000000..0a3759a --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/user/dto/UpdateUserRequest.java @@ -0,0 +1,11 @@ +package ru.practicum.shareit.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class UpdateUserRequest { + private String name; + private String email; +} \ No newline at end of file diff --git a/server/src/main/java/ru/practicum/shareit/user/dto/UserResponse.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserResponse.java new file mode 100644 index 0000000..628bda1 --- /dev/null +++ b/server/src/main/java/ru/practicum/shareit/user/dto/UserResponse.java @@ -0,0 +1,12 @@ +package ru.practicum.shareit.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class UserResponse { + private Long id; + private String name; + private String email; +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserTransformer.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserTransformer.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/dto/UserTransformer.java rename to server/src/main/java/ru/practicum/shareit/user/dto/UserTransformer.java diff --git a/src/main/java/ru/practicum/shareit/user/dto/UserTransformerImpl.java b/server/src/main/java/ru/practicum/shareit/user/dto/UserTransformerImpl.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/dto/UserTransformerImpl.java rename to server/src/main/java/ru/practicum/shareit/user/dto/UserTransformerImpl.java diff --git a/src/main/java/ru/practicum/shareit/user/model/User.java b/server/src/main/java/ru/practicum/shareit/user/model/User.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/model/User.java rename to server/src/main/java/ru/practicum/shareit/user/model/User.java diff --git a/src/main/java/ru/practicum/shareit/user/model/UserManager.java b/server/src/main/java/ru/practicum/shareit/user/model/UserManager.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/model/UserManager.java rename to server/src/main/java/ru/practicum/shareit/user/model/UserManager.java diff --git a/src/main/java/ru/practicum/shareit/user/model/UserManagerImpl.java b/server/src/main/java/ru/practicum/shareit/user/model/UserManagerImpl.java similarity index 100% rename from src/main/java/ru/practicum/shareit/user/model/UserManagerImpl.java rename to server/src/main/java/ru/practicum/shareit/user/model/UserManagerImpl.java diff --git a/src/main/java/ru/practicum/shareit/user/model/UserRepository.java b/server/src/main/java/ru/practicum/shareit/user/model/UserRepository.java similarity index 99% rename from src/main/java/ru/practicum/shareit/user/model/UserRepository.java rename to server/src/main/java/ru/practicum/shareit/user/model/UserRepository.java index 0d37b0c..1be4b80 100644 --- a/src/main/java/ru/practicum/shareit/user/model/UserRepository.java +++ b/server/src/main/java/ru/practicum/shareit/user/model/UserRepository.java @@ -4,4 +4,4 @@ public interface UserRepository extends JpaRepository { boolean existsByEmail(String email); -} +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/server/src/main/resources/application.properties similarity index 64% rename from src/main/resources/application.properties rename to server/src/main/resources/application.properties index f98512c..4f98849 100644 --- a/src/main/resources/application.properties +++ b/server/src/main/resources/application.properties @@ -1,8 +1,12 @@ +server.port=9090 spring.datasource.url=jdbc:postgresql://localhost:5432/shareit spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.username=progingir spring.datasource.password=12345 spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true +spring.jpa.open-in-view=false spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect -server.port=8080 \ No newline at end of file +logging.level.org.hibernate=DEBUG +logging.level.org.springframework.orm.jpa=DEBUG +logging.level.org.springframework.jdbc.datasource=DEBUG \ No newline at end of file diff --git a/src/main/resources/schema.sql b/server/src/main/resources/schema.sql similarity index 100% rename from src/main/resources/schema.sql rename to server/src/main/resources/schema.sql diff --git a/server/src/test/java/ru/practicum/shareit/BookerDtoTest.java b/server/src/test/java/ru/practicum/shareit/BookerDtoTest.java new file mode 100644 index 0000000..7a3594f --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/BookerDtoTest.java @@ -0,0 +1,51 @@ +package ru.practicum.shareit; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.booking.dto.BookerDto; + +import static org.assertj.core.api.Assertions.assertThat; + +class BookerDtoTest { + + private ObjectMapper objectMapper; + private BookerDto bookerDto; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + bookerDto = new BookerDto(); + bookerDto.setId(1L); + bookerDto.setName("Test User"); + } + + @Test + void gettersAndSetters_workCorrectly() { + assertThat(bookerDto.getId()).isEqualTo(1L); + assertThat(bookerDto.getName()).isEqualTo("Test User"); + + bookerDto.setId(2L); + bookerDto.setName("New User"); + assertThat(bookerDto.getId()).isEqualTo(2L); + assertThat(bookerDto.getName()).isEqualTo("New User"); + } + + @Test + void serializeAndDeserialize_success() throws Exception { + String json = objectMapper.writeValueAsString(bookerDto); + BookerDto deserialized = objectMapper.readValue(json, BookerDto.class); + + assertThat(deserialized).isEqualTo(bookerDto); + } + + @Test + void equalsAndHashCode_sameObjectsAreEqual() { + BookerDto other = new BookerDto(); + other.setId(1L); + other.setName("Test User"); + + assertThat(bookerDto).isEqualTo(other); + assertThat(bookerDto.hashCode()).isEqualTo(other.hashCode()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/BookingControllerTest.java b/server/src/test/java/ru/practicum/shareit/BookingControllerTest.java new file mode 100644 index 0000000..23d23ee --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/BookingControllerTest.java @@ -0,0 +1,100 @@ +package ru.practicum.shareit; + +import com.fasterxml.jackson.databind.ObjectMapper; +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.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.booking.BookingController; +import ru.practicum.shareit.booking.BookingService; +import ru.practicum.shareit.booking.dto.BookingDto; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(controllers = BookingController.class) +class BookingControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private BookingService bookingService; + + @Autowired + private ObjectMapper objectMapper; + + private BookingDto bookingDto; + + @BeforeEach + void setUp() { + bookingDto = new BookingDto(); + bookingDto.setId(1L); + bookingDto.setStart(LocalDateTime.now().plusDays(1)); + bookingDto.setEnd(LocalDateTime.now().plusDays(2)); + bookingDto.setItemId(1L); + } + + @Test + void createBooking_success() throws Exception { + when(bookingService.createBooking(any(BookingDto.class), eq(1L))).thenReturn(bookingDto); + + mockMvc.perform(post("/bookings") + .header("X-Sharer-User-Id", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(bookingDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(bookingDto.getId())); + } + + @Test + void updateBooking_success() throws Exception { + when(bookingService.updateBooking(1L, 1L, true)).thenReturn(bookingDto); + + mockMvc.perform(patch("/bookings/1") + .header("X-Sharer-User-Id", 1L) + .param("approved", "true")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(bookingDto.getId())); + } + + @Test + void getBooking_success() throws Exception { + when(bookingService.getBooking(1L, 1L)).thenReturn(bookingDto); + + mockMvc.perform(get("/bookings/1") + .header("X-Sharer-User-Id", 1L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(bookingDto.getId())); + } + + @Test + void getUserBookings_success() throws Exception { + when(bookingService.getUserBookings(1L, "ALL")).thenReturn(List.of(bookingDto)); + + mockMvc.perform(get("/bookings") + .header("X-Sharer-User-Id", 1L) + .param("state", "ALL")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(bookingDto.getId())); + } + + @Test + void getOwnerBookings_success() throws Exception { + when(bookingService.getOwnerBookings(1L, "ALL")).thenReturn(List.of(bookingDto)); + + mockMvc.perform(get("/bookings/owner") + .header("X-Sharer-User-Id", 1L) + .param("state", "ALL")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(bookingDto.getId())); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/BookingDtoTest.java b/server/src/test/java/ru/practicum/shareit/BookingDtoTest.java new file mode 100644 index 0000000..f0c01d4 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/BookingDtoTest.java @@ -0,0 +1,73 @@ +package ru.practicum.shareit; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.booking.dto.BookerDto; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.dto.ItemDto; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +class BookingDtoTest { + + private ObjectMapper objectMapper; + private BookingDto bookingDto; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + bookingDto = new BookingDto(); + bookingDto.setId(1L); + bookingDto.setStart(LocalDateTime.now().plusDays(1)); + bookingDto.setEnd(LocalDateTime.now().plusDays(2)); + bookingDto.setItem(new ItemDto()); + bookingDto.getItem().setId(1L); + bookingDto.getItem().setName("Test Item"); + bookingDto.setItemId(1L); + bookingDto.setBooker(new BookerDto()); + bookingDto.getBooker().setId(2L); + bookingDto.getBooker().setName("Test User"); + bookingDto.setBookerId(2L); + bookingDto.setStatus("WAITING"); + } + + @Test + void gettersAndSetters_workCorrectly() { + assertThat(bookingDto.getId()).isEqualTo(1L); + assertThat(bookingDto.getItemId()).isEqualTo(1L); + assertThat(bookingDto.getBookerId()).isEqualTo(2L); + assertThat(bookingDto.getStatus()).isEqualTo("WAITING"); + + bookingDto.setId(2L); + bookingDto.setItemId(2L); + bookingDto.setBookerId(3L); + bookingDto.setStatus("APPROVED"); + assertThat(bookingDto.getId()).isEqualTo(2L); + assertThat(bookingDto.getItemId()).isEqualTo(2L); + assertThat(bookingDto.getBookerId()).isEqualTo(3L); + assertThat(bookingDto.getStatus()).isEqualTo("APPROVED"); + } + + @Test + void equalsAndHashCode_sameObjectsAreEqual() { + BookingDto other = new BookingDto(); + other.setId(1L); + other.setStart(bookingDto.getStart()); + other.setEnd(bookingDto.getEnd()); + other.setItem(new ItemDto()); + other.getItem().setId(1L); + other.getItem().setName("Test Item"); + other.setItemId(1L); + other.setBooker(new BookerDto()); + other.getBooker().setId(2L); + other.getBooker().setName("Test User"); + other.setBookerId(2L); + other.setStatus("WAITING"); + + assertThat(bookingDto).isEqualTo(other); + assertThat(bookingDto.hashCode()).isEqualTo(other.hashCode()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/BookingMapperTest.java b/server/src/test/java/ru/practicum/shareit/BookingMapperTest.java new file mode 100644 index 0000000..796538f --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/BookingMapperTest.java @@ -0,0 +1,67 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.booking.Booking; +import ru.practicum.shareit.booking.BookingMapper; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.booking.dto.BookerDto; +import ru.practicum.shareit.booking.dto.ItemDto; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +class BookingMapperTest { + + private BookingMapper bookingMapper; + private Booking booking; + private User booker; + private Item item; + + @BeforeEach + void setUp() { + bookingMapper = new BookingMapper(); + + booker = new User(); + booker.setId(1L); + booker.setName("Test User"); + booker.setEmail("test@example.com"); + + item = new Item(); + item.setId(1L); + item.setName("Test Item"); + + booking = new Booking(); + booking.setId(1L); + booking.setStart(LocalDateTime.now()); + booking.setEnd(LocalDateTime.now().plusDays(1)); + booking.setItem(item); + booking.setBooker(booker); + booking.setStatus(BookingStatus.APPROVED); + } + + @Test + void toDto_success() { + BookingDto result = bookingMapper.toDto(booking); + + assertNotNull(result); + assertEquals(booking.getId(), result.getId()); + assertEquals(booking.getStart(), result.getStart()); + assertEquals(booking.getEnd(), result.getEnd()); + assertEquals(booking.getStatus().name(), result.getStatus()); + + ItemDto itemDto = result.getItem(); + assertNotNull(itemDto); + assertEquals(item.getId(), itemDto.getId()); + assertEquals(item.getName(), itemDto.getName()); + + BookerDto bookerDto = result.getBooker(); + assertNotNull(bookerDto); + assertEquals(booker.getId(), bookerDto.getId()); + assertEquals(booker.getName(), bookerDto.getName()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/BookingServiceImplTest.java b/server/src/test/java/ru/practicum/shareit/BookingServiceImplTest.java new file mode 100644 index 0000000..15875ea --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/BookingServiceImplTest.java @@ -0,0 +1,176 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.BeforeEach; +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.booking.*; +import ru.practicum.shareit.booking.dto.BookingDto; +import ru.practicum.shareit.exception.ForbiddenAccessException; +import ru.practicum.shareit.exception.ItemNotFoundException; +import ru.practicum.shareit.exception.UserNotFoundException; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.model.ItemRepository; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.model.UserRepository; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class BookingServiceImplTest { + + @Mock + private BookingRepository bookingRepository; + + @Mock + private UserRepository userRepository; + + @Mock + private ItemRepository itemRepository; + + @Mock + private BookingMapper bookingMapper; + + @InjectMocks + private BookingServiceImpl bookingService; + + private BookingDto bookingDto; + private Booking booking; + private User booker; + private Item item; + + @BeforeEach + void setUp() { + booker = new User(); + booker.setId(1L); + booker.setName("Test User"); + booker.setEmail("test@example.com"); + + User owner = new User(); + owner.setId(2L); + owner.setName("Owner"); + owner.setEmail("owner@example.com"); + + item = new Item(); + item.setId(1L); + item.setName("Test Item"); + item.setAvailable(true); + item.setOwner(owner); + + bookingDto = new BookingDto(); + bookingDto.setId(1L); + bookingDto.setStart(LocalDateTime.now().plusDays(1)); + bookingDto.setEnd(LocalDateTime.now().plusDays(2)); + bookingDto.setItemId(1L); + bookingDto.setBookerId(1L); + bookingDto.setStatus("WAITING"); + + booking = new Booking(); + booking.setId(1L); + booking.setStart(bookingDto.getStart()); + booking.setEnd(bookingDto.getEnd()); + booking.setItem(item); + booking.setBooker(booker); + booking.setStatus(BookingStatus.WAITING); + } + + @Test + void createBooking_success() { + when(userRepository.findById(1L)).thenReturn(Optional.of(booker)); + when(itemRepository.findById(1L)).thenReturn(Optional.of(item)); + when(bookingRepository.save(any(Booking.class))).thenReturn(booking); + when(bookingMapper.toDto(booking)).thenReturn(bookingDto); + + BookingDto result = bookingService.createBooking(bookingDto, 1L); + + assertThat(result).isEqualTo(bookingDto); + verify(bookingRepository).save(any(Booking.class)); + } + + @Test + void createBooking_userNotFound_throwsException() { + when(userRepository.findById(1L)).thenReturn(Optional.empty()); + + assertThrows(UserNotFoundException.class, () -> bookingService.createBooking(bookingDto, 1L)); + } + + @Test + void createBooking_itemNotFound_throwsException() { + when(userRepository.findById(1L)).thenReturn(Optional.of(booker)); + when(itemRepository.findById(1L)).thenReturn(Optional.empty()); + + assertThrows(ItemNotFoundException.class, () -> bookingService.createBooking(bookingDto, 1L)); + } + + @Test + void createBooking_ownerBooksOwnItem_throwsException() { + item.setOwner(booker); + when(userRepository.findById(1L)).thenReturn(Optional.of(booker)); + when(itemRepository.findById(1L)).thenReturn(Optional.of(item)); + + assertThrows(ForbiddenAccessException.class, () -> bookingService.createBooking(bookingDto, 1L)); + } + + @Test + void updateBooking_approved_success() { + booking.setStatus(BookingStatus.WAITING); + when(bookingRepository.findById(1L)).thenReturn(Optional.of(booking)); + when(bookingRepository.save(any(Booking.class))).thenReturn(booking); + when(bookingMapper.toDto(booking)).thenReturn(bookingDto); + + BookingDto result = bookingService.updateBooking(1L, 2L, true); + + assertThat(result).isEqualTo(bookingDto); + assertThat(booking.getStatus()).isEqualTo(BookingStatus.APPROVED); + } + + @Test + void updateBooking_notOwner_throwsException() { + when(bookingRepository.findById(1L)).thenReturn(Optional.of(booking)); + + assertThrows(ForbiddenAccessException.class, () -> bookingService.updateBooking(1L, 1L, true)); + } + + @Test + void getBooking_success() { + when(bookingRepository.findById(1L)).thenReturn(Optional.of(booking)); + when(bookingMapper.toDto(booking)).thenReturn(bookingDto); + + BookingDto result = bookingService.getBooking(1L, 1L); + + assertThat(result).isEqualTo(bookingDto); + } + + @Test + void getUserBookings_all_success() { + when(userRepository.findById(1L)).thenReturn(Optional.of(booker)); + when(bookingRepository.findByBookerId(eq(1L), any())).thenReturn(List.of(booking)); + when(bookingMapper.toDto(booking)).thenReturn(bookingDto); + + List result = bookingService.getUserBookings(1L, "ALL"); + + assertThat(result).hasSize(1); + assertThat(result.get(0)).isEqualTo(bookingDto); + } + + @Test + void getOwnerBookings_waiting_success() { + when(userRepository.findById(2L)).thenReturn(Optional.of(item.getOwner())); + when(bookingRepository.findByItemOwnerIdAndStatus(eq(2L), eq(BookingStatus.WAITING), any())).thenReturn(List.of(booking)); + when(bookingMapper.toDto(booking)).thenReturn(bookingDto); + + List result = bookingService.getOwnerBookings(2L, "WAITING"); + + assertThat(result).hasSize(1); + assertThat(result.get(0)).isEqualTo(bookingDto); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/BookingShortDtoTest.java b/server/src/test/java/ru/practicum/shareit/BookingShortDtoTest.java new file mode 100644 index 0000000..c6b62ff --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/BookingShortDtoTest.java @@ -0,0 +1,35 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.booking.BookingShortDto; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class BookingShortDtoTest { + + @Test + void testBookingShortDto() { + BookingShortDto dto = new BookingShortDto(); + dto.setId(1L); + dto.setBookerId(1L); + dto.setStart(LocalDateTime.now().plusDays(1)); + dto.setEnd(LocalDateTime.now().plusDays(2)); + + assertEquals(1L, dto.getId()); + assertEquals(1L, dto.getBookerId()); + assertEquals(LocalDateTime.now().plusDays(1).withNano(0), dto.getStart().withNano(0)); + assertEquals(LocalDateTime.now().plusDays(2).withNano(0), dto.getEnd().withNano(0)); + } + + @Test + void testBookingShortDtoWithNulls() { + BookingShortDto dto = new BookingShortDto(); + assertNull(dto.getId()); + assertNull(dto.getBookerId()); + assertNull(dto.getStart()); + assertNull(dto.getEnd()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/BookingStatusTest.java b/server/src/test/java/ru/practicum/shareit/BookingStatusTest.java new file mode 100644 index 0000000..e8bb522 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/BookingStatusTest.java @@ -0,0 +1,25 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.booking.BookingStatus; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class BookingStatusTest { + + @Test + void testBookingStatusValues() { + BookingStatus[] expected = {BookingStatus.WAITING, BookingStatus.APPROVED, BookingStatus.REJECTED}; + assertEquals(3, BookingStatus.values().length); + assertEquals(expected[0], BookingStatus.WAITING); + assertEquals(expected[1], BookingStatus.APPROVED); + assertEquals(expected[2], BookingStatus.REJECTED); + } + + @Test + void testBookingStatusValueOf() { + assertEquals(BookingStatus.WAITING, BookingStatus.valueOf("WAITING")); + assertEquals(BookingStatus.APPROVED, BookingStatus.valueOf("APPROVED")); + assertEquals(BookingStatus.REJECTED, BookingStatus.valueOf("REJECTED")); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/BookingTest.java b/server/src/test/java/ru/practicum/shareit/BookingTest.java new file mode 100644 index 0000000..9aaacd6 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/BookingTest.java @@ -0,0 +1,57 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.booking.Booking; +import ru.practicum.shareit.booking.BookingStatus; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class BookingTest { + + @Test + void testBooking() { + Booking booking = new Booking(); + booking.setId(1L); + Item item = new Item(); + item.setId(1L); + booking.setItem(item); + User booker = new User(); + booker.setId(1L); + booking.setBooker(booker); + LocalDateTime start = LocalDateTime.now(); + booking.setStart(start); + LocalDateTime end = start.plusDays(1); + booking.setEnd(end); + booking.setStatus(BookingStatus.APPROVED); + + assertEquals(1L, booking.getId()); + assertEquals(item, booking.getItem()); + assertEquals(booker, booking.getBooker()); + assertEquals(start, booking.getStart()); + assertEquals(end, booking.getEnd()); + assertEquals(BookingStatus.APPROVED, booking.getStatus()); + } + + @Test + void testBookingWithNulls() { + Booking booking = new Booking(); + assertNull(booking.getId()); + assertNull(booking.getItem()); + assertNull(booking.getBooker()); + assertNull(booking.getStart()); + assertNull(booking.getEnd()); + assertNull(booking.getStatus()); + } + + @Test + void testBookingStatusEnum() { + assertEquals("WAITING", BookingStatus.WAITING.name()); + assertEquals("APPROVED", BookingStatus.APPROVED.name()); + assertEquals("REJECTED", BookingStatus.REJECTED.name()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/CommentDtoTest.java b/server/src/test/java/ru/practicum/shareit/CommentDtoTest.java new file mode 100644 index 0000000..9be0083 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/CommentDtoTest.java @@ -0,0 +1,41 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.item.dto.CommentDto; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class CommentDtoTest { + + @Test + void testCommentDto() { + CommentDto dto = new CommentDto(); + dto.setId(1L); + dto.setText("Great item!"); + dto.setItemId(1L); + dto.setAuthorId(1L); + dto.setAuthorName("John"); + dto.setCreated(LocalDateTime.now()); + + assertEquals(1L, dto.getId()); + assertEquals("Great item!", dto.getText()); + assertEquals(1L, dto.getItemId()); + assertEquals(1L, dto.getAuthorId()); + assertEquals("John", dto.getAuthorName()); + assertEquals(LocalDateTime.now().withNano(0), dto.getCreated().withNano(0)); // Сравнение без наносекунд + } + + @Test + void testCommentDtoWithNulls() { + CommentDto dto = new CommentDto(); + assertNull(dto.getId()); + assertNull(dto.getText()); + assertNull(dto.getItemId()); + assertNull(dto.getAuthorId()); + assertNull(dto.getAuthorName()); + assertNull(dto.getCreated()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/CommentTest.java b/server/src/test/java/ru/practicum/shareit/CommentTest.java new file mode 100644 index 0000000..5847a88 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/CommentTest.java @@ -0,0 +1,45 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +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; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class CommentTest { + + @Test + void testComment() { + Comment comment = new Comment(); + comment.setId(1L); + comment.setText("Great item!"); + Item item = new Item(); + item.setId(1L); + comment.setItem(item); + User author = new User(); + author.setId(1L); + comment.setAuthor(author); + LocalDateTime now = LocalDateTime.now(); + comment.setCreated(now); + + assertEquals(1L, comment.getId()); + assertEquals("Great item!", comment.getText()); + assertEquals(item, comment.getItem()); + assertEquals(author, comment.getAuthor()); + assertEquals(now, comment.getCreated()); + } + + @Test + void testCommentWithNulls() { + Comment comment = new Comment(); + assertNull(comment.getId()); + assertNull(comment.getText()); + assertNull(comment.getItem()); + assertNull(comment.getAuthor()); + assertNull(comment.getCreated()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/ExceptionTest.java b/server/src/test/java/ru/practicum/shareit/ExceptionTest.java new file mode 100644 index 0000000..e38197c --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/ExceptionTest.java @@ -0,0 +1,29 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.exception.ForbiddenAccessException; +import ru.practicum.shareit.exception.ItemNotFoundException; +import ru.practicum.shareit.exception.UserNotFoundException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ExceptionTest { + + @Test + void testUserNotFoundException() { + UserNotFoundException exception = new UserNotFoundException("User not found"); + assertEquals("User not found", exception.getMessage()); + } + + @Test + void testItemNotFoundException() { + ItemNotFoundException exception = new ItemNotFoundException("Item not found"); + assertEquals("Item not found", exception.getMessage()); + } + + @Test + void testForbiddenAccessException() { + ForbiddenAccessException exception = new ForbiddenAccessException("Access denied"); + assertEquals("Access denied", exception.getMessage()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/ItemApiControllerTest.java b/server/src/test/java/ru/practicum/shareit/ItemApiControllerTest.java new file mode 100644 index 0000000..7fbdd56 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/ItemApiControllerTest.java @@ -0,0 +1,85 @@ +package ru.practicum.shareit; + +import com.fasterxml.jackson.databind.ObjectMapper; +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.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.item.ItemApiController; +import ru.practicum.shareit.item.dto.*; +import ru.practicum.shareit.item.model.ItemManager; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(controllers = ItemApiController.class) +class ItemApiControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private ItemManager itemManager; + + @Autowired + private ObjectMapper objectMapper; + + private ItemResponse itemResponse; + private NewItemRequest newItemRequest; + + @BeforeEach + void setUp() { + itemResponse = new ItemResponse(1L, "Test Item", "Description", true, null, null, List.of()); + newItemRequest = new NewItemRequest("Test Item", "Description", true, null); + } + + @Test + void fetchUserItems_success() throws Exception { + when(itemManager.fetchUserItems(1L)).thenReturn(List.of(new ItemWithBookingsResponse(1L, "Test Item", "Description", true))); + + mockMvc.perform(get("/items") + .header("X-Sharer-User-Id", 1L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1L)); + } + + @Test + void findById_success() throws Exception { + when(itemManager.findItemById(eq(1L), eq(1L))).thenReturn(itemResponse); + + mockMvc.perform(get("/items/1") + .header("X-Sharer-User-Id", 1L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1L)); + } + + @Test + void addItem_success() throws Exception { + when(itemManager.addItem(any(NewItemRequest.class), eq(1L))).thenReturn(itemResponse); + + mockMvc.perform(post("/items") + .header("X-Sharer-User-Id", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newItemRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1L)); + } + + @Test + void searchItems_success() throws Exception { + when(itemManager.searchItems(eq("test"), eq(1L))).thenReturn(List.of(itemResponse)); + + mockMvc.perform(get("/items/search") + .header("X-Sharer-User-Id", 1L) + .param("text", "test")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1L)); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/ItemManagerImplTest.java b/server/src/test/java/ru/practicum/shareit/ItemManagerImplTest.java new file mode 100644 index 0000000..630b5cf --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/ItemManagerImplTest.java @@ -0,0 +1,150 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.BeforeEach; +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.booking.BookingRepository; +import ru.practicum.shareit.exception.UserNotFoundException; +import ru.practicum.shareit.item.dto.*; +import ru.practicum.shareit.item.model.*; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.model.UserRepository; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ItemManagerImplTest { + + @Mock + private ItemRepository itemRepository; + + @Mock + private UserRepository userRepository; + + @Mock + private ItemTransformer transformer; + + @Mock + private BookingRepository bookingRepository; + + @Mock + private CommentRepository commentRepository; + + @InjectMocks + private ItemManagerImpl itemManager; + + private User user; + private Item item; + private NewItemRequest newItemRequest; + private UpdateItemRequest updateItemRequest; + private ItemResponse itemResponse; + private CommentDto commentDto; + + @BeforeEach + void setUp() { + user = new User(); + user.setId(1L); + user.setName("Test User"); + user.setEmail("test@example.com"); + + item = new Item(); + item.setId(1L); + item.setName("Test Item"); + item.setDescription("Test Description"); + item.setAvailable(true); + item.setOwner(user); + + newItemRequest = new NewItemRequest("Test Item", "Test Description", true, null); + updateItemRequest = new UpdateItemRequest("Updated Item", "Updated Description", false); + itemResponse = new ItemResponse(1L, "Test Item", "Test Description", true, null, null, List.of()); + + commentDto = new CommentDto(); + commentDto.setText("Test Comment"); + commentDto.setItemId(1L); + commentDto.setAuthorId(1L); + commentDto.setAuthorName("Test User"); + commentDto.setCreated(LocalDateTime.now()); + } + + @Test + void addItem_success() { + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(transformer.toItem(newItemRequest)).thenReturn(item); + when(itemRepository.save(item)).thenReturn(item); + when(transformer.toResponse(item, List.of(), 1L)).thenReturn(itemResponse); + + ItemResponse result = itemManager.addItem(newItemRequest, 1L); + + assertNotNull(result); + assertEquals(itemResponse, result); + verify(itemRepository).save(item); + } + + @Test + void addItem_userNotFound_throwsUserNotFoundException() { + when(userRepository.findById(1L)).thenReturn(Optional.empty()); + + assertThrows(UserNotFoundException.class, () -> itemManager.addItem(newItemRequest, 1L)); + } + + @Test + void findItemById_success_owner() { + when(itemRepository.findById(1L)).thenReturn(Optional.of(item)); + when(bookingRepository.findByBookerId(anyLong(), any())).thenReturn(List.of()); + when(commentRepository.findByItemId(1L)).thenReturn(List.of()); + when(transformer.toResponse(item, List.of(), 1L)).thenReturn(itemResponse); + + ItemResponse result = itemManager.findItemById(1L, 1L); + + assertNotNull(result); + assertEquals(itemResponse, result); + } + + @Test + void modifyItem_success() { + when(itemRepository.findById(1L)).thenReturn(Optional.of(item)); + when(transformer.applyUpdates(updateItemRequest, item)).thenReturn(item); + when(itemRepository.save(item)).thenReturn(item); + when(transformer.toResponse(item, List.of(), 1L)).thenReturn(itemResponse); + + ItemResponse result = itemManager.modifyItem(updateItemRequest, 1L, 1L); + + assertNotNull(result); + assertEquals(itemResponse, result); + verify(itemRepository).save(item); + } + + @Test + void fetchUserItems_success() { + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(itemRepository.findByOwnerId(1L)).thenReturn(List.of(item)); + when(bookingRepository.findByItemOwnerId(eq(1L), any())).thenReturn(List.of()); + when(commentRepository.findByItemId(1L)).thenReturn(List.of()); + ItemWithBookingsResponse response = new ItemWithBookingsResponse(1L, "Test Item", "Test Description", true); + when(transformer.toResponseWithBookingsAndComments(any(), any(), any(), any())).thenReturn(response); + + List result = itemManager.fetchUserItems(1L); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(response, result.get(0)); + } + + @Test + void addComment_noCompletedBooking_throwsRuntimeException() { + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(itemRepository.findById(1L)).thenReturn(Optional.of(item)); + when(bookingRepository.findByBookerId(eq(1L), any())).thenReturn(List.of()); + + assertThrows(RuntimeException.class, () -> itemManager.addComment(commentDto, 1L, 1L)); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/ItemRequestDtoTest.java b/server/src/test/java/ru/practicum/shareit/ItemRequestDtoTest.java new file mode 100644 index 0000000..7fdc77f --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/ItemRequestDtoTest.java @@ -0,0 +1,76 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.request.dto.ItemDto; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.request.dto.OwnerDto; +import ru.practicum.shareit.request.dto.RequesterDto; + +import java.time.LocalDateTime; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.*; + +class ItemRequestDtoTest { + + @Test + void testItemRequestDto() { + ItemRequestDto dto = new ItemRequestDto(); + dto.setId(1L); + dto.setDescription("Need a drill"); + RequesterDto requester = new RequesterDto(); + requester.setId(1L); + dto.setRequester(requester); + dto.setCreated(LocalDateTime.now()); + dto.setItems(Collections.emptyList()); + + assertEquals(1L, dto.getId()); + assertEquals("Need a drill", dto.getDescription()); + assertEquals(requester, dto.getRequester()); + assertEquals(Collections.emptyList(), dto.getItems()); + } + + @Test + void testItemDto() { + ItemDto itemDto = new ItemDto(); + itemDto.setId(1L); + itemDto.setName("Drill"); + itemDto.setDescription("Powerful drill"); + itemDto.setAvailable(true); + OwnerDto owner = new OwnerDto(); + owner.setId(1L); + itemDto.setOwner(owner); + itemDto.setRequestId(1L); + + assertEquals(1L, itemDto.getId()); + assertEquals("Drill", itemDto.getName()); + assertEquals("Powerful drill", itemDto.getDescription()); + assertTrue(itemDto.getAvailable()); + assertEquals(owner, itemDto.getOwner()); + assertEquals(1L, itemDto.getRequestId()); + } + + @Test + void testOwnerDto() { + OwnerDto owner = new OwnerDto(); + owner.setId(1L); + owner.setEmail("john@example.com"); + owner.setName("John"); + + assertEquals(1L, owner.getId()); + assertEquals("john@example.com", owner.getEmail()); + assertEquals("John", owner.getName()); + } + + @Test + void testRequesterDto() { + RequesterDto requester = new RequesterDto(); + requester.setId(1L); + requester.setEmail("john@example.com"); + requester.setName("John"); + + assertEquals(1L, requester.getId()); + assertEquals("john@example.com", requester.getEmail()); + assertEquals("John", requester.getName()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/ItemRequestServiceImplTest.java b/server/src/test/java/ru/practicum/shareit/ItemRequestServiceImplTest.java new file mode 100644 index 0000000..644b8c1 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/ItemRequestServiceImplTest.java @@ -0,0 +1,130 @@ +package ru.practicum.shareit; + +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.data.domain.PageRequest; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.item.model.ItemRepository; +import ru.practicum.shareit.request.ItemRequest; +import ru.practicum.shareit.request.ItemRequestMapper; +import ru.practicum.shareit.request.ItemRequestRepository; +import ru.practicum.shareit.request.ItemRequestServiceImpl; +import ru.practicum.shareit.request.dto.ItemRequestDto; +import ru.practicum.shareit.user.model.User; +import ru.practicum.shareit.user.model.UserRepository; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ItemRequestServiceImplTest { + + @Mock + private ItemRequestRepository requestRepository; + + @Mock + private UserRepository userRepository; + + @Mock + private ItemRepository itemRepository; + + @Mock + private ItemRequestMapper mapper; + + @InjectMocks + private ItemRequestServiceImpl requestService; + + @Test + void createRequest() { + User user = new User(); + user.setId(1L); + ItemRequest request = new ItemRequest(); + request.setId(1L); + ItemRequestDto dto = new ItemRequestDto(); + dto.setId(1L); + Item item = new Item(); + item.setId(1L); + + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(mapper.toEntity(dto)).thenReturn(request); + when(requestRepository.save(any(ItemRequest.class))).thenReturn(request); + when(itemRepository.findByItemRequest_Id(1L)).thenReturn(Collections.singletonList(item)); + when(mapper.toDto(request, Collections.singletonList(item))).thenReturn(dto); + + ItemRequestDto result = requestService.createRequest(dto, 1L); + assertEquals(dto, result); + } + + @Test + void getUserRequests() { + User user = new User(); + user.setId(1L); + ItemRequest request = new ItemRequest(); + request.setId(1L); + ItemRequestDto dto = new ItemRequestDto(); + dto.setId(1L); + + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(requestRepository.findByRequestorId(anyLong(), any(PageRequest.class))).thenReturn(Collections.singletonList(request)); + when(itemRepository.findByItemRequest_Id(1L)).thenReturn(Collections.emptyList()); + when(mapper.toDto(request, Collections.emptyList())).thenReturn(dto); + + List result = requestService.getUserRequests(1L, 0, 10); + assertEquals(1, result.size()); + assertEquals(dto, result.get(0)); + } + + @Test + void getAllRequests() { + User user = new User(); + user.setId(1L); + ItemRequest request = new ItemRequest(); + request.setId(1L); + ItemRequestDto dto = new ItemRequestDto(); + dto.setId(1L); + + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(requestRepository.findByRequestorIdNot(anyLong(), any(PageRequest.class))).thenReturn(Collections.singletonList(request)); + when(itemRepository.findByItemRequest_Id(1L)).thenReturn(Collections.emptyList()); + when(mapper.toDto(request, Collections.emptyList())).thenReturn(dto); + + List result = requestService.getAllRequests(1L, 0, 10); + assertEquals(1, result.size()); + assertEquals(dto, result.get(0)); + } + + @Test + void getRequestById() { + User user = new User(); + user.setId(1L); + ItemRequest request = new ItemRequest(); + request.setId(1L); + ItemRequestDto dto = new ItemRequestDto(); + dto.setId(1L); + + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(requestRepository.findById(1L)).thenReturn(Optional.of(request)); + when(itemRepository.findByItemRequest_Id(1L)).thenReturn(Collections.emptyList()); + when(mapper.toDto(request, Collections.emptyList())).thenReturn(dto); + + ItemRequestDto result = requestService.getRequestById(1L, 1L); + assertEquals(dto, result); + } + + @Test + void getRequestByIdNotFound() { + when(userRepository.findById(1L)).thenReturn(Optional.of(new User())); + when(requestRepository.findById(1L)).thenReturn(Optional.empty()); + assertThrows(IllegalArgumentException.class, () -> requestService.getRequestById(1L, 1L)); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/ItemRequestTest.java b/server/src/test/java/ru/practicum/shareit/ItemRequestTest.java new file mode 100644 index 0000000..cc1f62f --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/ItemRequestTest.java @@ -0,0 +1,39 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.request.ItemRequest; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class ItemRequestTest { + + @Test + void testItemRequest() { + ItemRequest request = new ItemRequest(); + request.setId(1L); + request.setDescription("Need a drill"); + User requestor = new User(); + requestor.setId(1L); + request.setRequestor(requestor); + LocalDateTime now = LocalDateTime.now(); + request.setCreated(now); + + assertEquals(1L, request.getId()); + assertEquals("Need a drill", request.getDescription()); + assertEquals(requestor, request.getRequestor()); + assertEquals(now, request.getCreated()); + } + + @Test + void testItemRequestWithNulls() { + ItemRequest request = new ItemRequest(); + assertNull(request.getId()); + assertNull(request.getDescription()); + assertNull(request.getRequestor()); + assertNull(request.getCreated()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/ItemResponseTest.java b/server/src/test/java/ru/practicum/shareit/ItemResponseTest.java new file mode 100644 index 0000000..2cf47bc --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/ItemResponseTest.java @@ -0,0 +1,60 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.booking.BookingShortDto; +import ru.practicum.shareit.item.dto.ItemResponse; +import ru.practicum.shareit.item.dto.ItemWithBookingsResponse; + +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class ItemResponseTest { + + @Test + void testItemResponse() { + ItemResponse response = new ItemResponse( + 1L, "Item", "Description", true, null, null, Collections.emptyList() + ); + assertEquals(1L, response.getId()); + assertEquals("Item", response.getName()); + assertEquals("Description", response.getDescription()); + assertEquals(true, response.isAvailable()); + assertNull(response.getLastBooking()); + assertNull(response.getNextBooking()); + assertEquals(Collections.emptyList(), response.getComments()); + } + + @Test + void testItemWithBookingsResponse() { + ItemWithBookingsResponse response = new ItemWithBookingsResponse( + 1L, "Item", "Description", true + ); + response.setLastBooking(new BookingShortDto()); + response.setNextBooking(new BookingShortDto()); + response.setComments(Collections.emptyList()); + + assertEquals(1L, response.getId()); + assertEquals("Item", response.getName()); + assertEquals("Description", response.getDescription()); + assertEquals(true, response.isAvailable()); + assertEquals(new BookingShortDto(), response.getLastBooking()); + assertEquals(new BookingShortDto(), response.getNextBooking()); + assertEquals(Collections.emptyList(), response.getComments()); + } + + @Test + void testItemWithBookingsResponseWithNulls() { + ItemWithBookingsResponse response = new ItemWithBookingsResponse( + null, null, null, false + ); + assertNull(response.getId()); + assertNull(response.getName()); + assertNull(response.getDescription()); + assertEquals(false, response.isAvailable()); + assertNull(response.getLastBooking()); + assertNull(response.getNextBooking()); + assertNull(response.getComments()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/ItemTest.java b/server/src/test/java/ru/practicum/shareit/ItemTest.java new file mode 100644 index 0000000..822cc30 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/ItemTest.java @@ -0,0 +1,47 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.user.model.User; + +import static org.assertj.core.api.Assertions.assertThat; + +class ItemTest { + + private Item item; + + @BeforeEach + void setUp() { + item = new Item(); + item.setId(1L); + item.setName("Test Item"); + item.setDescription("Description"); + item.setOwner(new User()); + item.setAvailable(true); + } + + @Test + void gettersAndSetters_workCorrectly() { + assertThat(item.getId()).isEqualTo(1L); + assertThat(item.getName()).isEqualTo("Test Item"); + + item.setId(2L); + item.setName("New Item"); + assertThat(item.getId()).isEqualTo(2L); + assertThat(item.getName()).isEqualTo("New Item"); + } + + @Test + void equalsAndHashCode_sameObjectsAreEqual() { + Item other = new Item(); + other.setId(1L); + other.setName("Test Item"); + other.setDescription("Description"); + other.setOwner(new User()); + other.setAvailable(true); + + assertThat(item).isEqualTo(other); + assertThat(item.hashCode()).isEqualTo(other.hashCode()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/ItemTransformerImplTest.java b/server/src/test/java/ru/practicum/shareit/ItemTransformerImplTest.java new file mode 100644 index 0000000..4b59b23 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/ItemTransformerImplTest.java @@ -0,0 +1,117 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.BeforeEach; +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.booking.Booking; +import ru.practicum.shareit.booking.BookingRepository; +import ru.practicum.shareit.booking.BookingShortDto; +import ru.practicum.shareit.item.dto.*; +import ru.practicum.shareit.item.model.Item; +import ru.practicum.shareit.request.ItemRequestRepository; +import ru.practicum.shareit.user.model.User; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(MockitoExtension.class) +class ItemTransformerImplTest { + + @Mock + private BookingRepository bookingRepository; + + @Mock + private ItemRequestRepository requestRepository; + + @InjectMocks + private ItemTransformerImpl transformer; + + private Item item; + private User owner; + private Booking booking; + private BookingShortDto bookingShortDto; + + @BeforeEach + void setUp() { + owner = new User(); + owner.setId(1L); + owner.setName("Owner"); + owner.setEmail("owner@example.com"); + + item = new Item(); + item.setId(1L); + item.setName("Test Item"); + item.setDescription("Test Description"); + item.setAvailable(true); + item.setOwner(owner); + + booking = new Booking(); + booking.setId(1L); + booking.setItem(item); + booking.setStart(LocalDateTime.now().minusDays(2)); + booking.setEnd(LocalDateTime.now().minusDays(1)); + + bookingShortDto = new BookingShortDto(); + bookingShortDto.setId(1L); + bookingShortDto.setBookerId(2L); + bookingShortDto.setStart(booking.getStart()); + bookingShortDto.setEnd(booking.getEnd()); + } + + @Test + void toResponse_simple() { + ItemResponse result = transformer.toResponse(item); + + assertNotNull(result); + assertEquals(item.getId(), result.getId()); + assertEquals(item.getName(), result.getName()); + assertEquals(item.getDescription(), result.getDescription()); + assertEquals(item.isAvailable(), result.isAvailable()); + assertNull(result.getLastBooking()); + assertNull(result.getNextBooking()); + assertTrue(result.getComments().isEmpty()); + } + + @Test + void toResponseWithBookingsAndComments_success() { + ItemWithBookingsResponse result = transformer.toResponseWithBookingsAndComments( + item, bookingShortDto, null, List.of(new CommentDto())); + + assertNotNull(result); + assertEquals(item.getId(), result.getId()); + assertEquals(item.getName(), result.getName()); + assertEquals(item.getDescription(), result.getDescription()); + assertEquals(item.isAvailable(), result.isAvailable()); + assertEquals(bookingShortDto, result.getLastBooking()); + assertNull(result.getNextBooking()); + assertEquals(1, result.getComments().size()); + } + + @Test + void toItem_success() { + NewItemRequest request = new NewItemRequest("Test Item", "Test Description", true, null); + Item result = transformer.toItem(request); + + assertNotNull(result); + assertEquals(request.getName(), result.getName()); + assertEquals(request.getDescription(), result.getDescription()); + assertEquals(request.getAvailable(), result.isAvailable()); + assertNull(result.getItemRequest()); + } + + @Test + void applyUpdates_success() { + UpdateItemRequest request = new UpdateItemRequest("Updated Item", "Updated Description", false); + Item result = transformer.applyUpdates(request, item); + + assertNotNull(result); + assertEquals(request.getName(), result.getName()); + assertEquals(request.getDescription(), result.getDescription()); + assertEquals(request.getAvailable(), result.isAvailable()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/UserApiControllerTest.java b/server/src/test/java/ru/practicum/shareit/UserApiControllerTest.java new file mode 100644 index 0000000..ace002f --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/UserApiControllerTest.java @@ -0,0 +1,92 @@ +package ru.practicum.shareit; + +import com.fasterxml.jackson.databind.ObjectMapper; +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.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import ru.practicum.shareit.user.UserApiController; +import ru.practicum.shareit.user.dto.NewUserRequest; +import ru.practicum.shareit.user.dto.UpdateUserRequest; +import ru.practicum.shareit.user.dto.UserResponse; +import ru.practicum.shareit.user.model.UserManager; + +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(UserApiController.class) +class UserApiControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private UserManager userManager; + + @Autowired + private ObjectMapper objectMapper; + + @Test + void fetchAllUsers() throws Exception { + UserResponse userResponse = new UserResponse(1L, "John", "john@example.com"); + when(userManager.fetchAllUsers()).thenReturn(Collections.singletonList(userResponse)); + + mockMvc.perform(get("/users")) + .andExpect(status().isOk()) + .andExpect(content().json(objectMapper.writeValueAsString(Collections.singletonList(userResponse)))); + } + + @Test + void addUser() throws Exception { + NewUserRequest request = new NewUserRequest("John", "john@example.com"); + UserResponse response = new UserResponse(1L, "John", "john@example.com"); + when(userManager.addUser(any(NewUserRequest.class))).thenReturn(response); + + mockMvc.perform(post("/users") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(content().json(objectMapper.writeValueAsString(response))); + } + + + @Test + void findById() throws Exception { + UserResponse response = new UserResponse(1L, "John", "john@example.com"); + when(userManager.findUserById(1L)).thenReturn(response); + + mockMvc.perform(get("/users/1")) + .andExpect(status().isOk()) + .andExpect(content().json(objectMapper.writeValueAsString(response))); + } + + @Test + void updateUser() throws Exception { + UpdateUserRequest request = new UpdateUserRequest("John Updated", "john.updated@example.com"); + UserResponse response = new UserResponse(1L, "John Updated", "john.updated@example.com"); + when(userManager.modifyUser(any(UpdateUserRequest.class), eq(1L))).thenReturn(response); + + mockMvc.perform(patch("/users/1") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(content().json(objectMapper.writeValueAsString(response))); + } + + @Test + void removeUser() throws Exception { + doNothing().when(userManager).removeUser(1L); + + mockMvc.perform(delete("/users/1")) + .andExpect(status().isNoContent()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/UserDtoTest.java b/server/src/test/java/ru/practicum/shareit/UserDtoTest.java new file mode 100644 index 0000000..bd79380 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/UserDtoTest.java @@ -0,0 +1,58 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.user.dto.NewUserRequest; +import ru.practicum.shareit.user.dto.UpdateUserRequest; +import ru.practicum.shareit.user.dto.UserResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class UserDtoTest { + + @Test + void testNewUserRequest() { + NewUserRequest request = new NewUserRequest("John", "john@example.com"); + assertEquals("John", request.getName()); + assertEquals("john@example.com", request.getEmail()); + + request.setName("Jane"); + request.setEmail("jane@example.com"); + assertEquals("Jane", request.getName()); + assertEquals("jane@example.com", request.getEmail()); + } + + @Test + void testNewUserRequestWithNulls() { + NewUserRequest request = new NewUserRequest(null, null); + assertNull(request.getName()); + assertNull(request.getEmail()); + } + + @Test + void testUpdateUserRequest() { + UpdateUserRequest request = new UpdateUserRequest("John", "john@example.com"); + assertEquals("John", request.getName()); + assertEquals("john@example.com", request.getEmail()); + + request.setName(null); + request.setEmail(null); + assertNull(request.getName()); + assertNull(request.getEmail()); + } + + @Test + void testUserResponse() { + UserResponse response = new UserResponse(1L, "John", "john@example.com"); + assertEquals(1L, response.getId()); + assertEquals("John", response.getName()); + assertEquals("john@example.com", response.getEmail()); + + response.setId(2L); + response.setName("Jane"); + response.setEmail("jane@example.com"); + assertEquals(2L, response.getId()); + assertEquals("Jane", response.getName()); + assertEquals("jane@example.com", response.getEmail()); + } +} \ No newline at end of file diff --git a/server/src/test/java/ru/practicum/shareit/UserTest.java b/server/src/test/java/ru/practicum/shareit/UserTest.java new file mode 100644 index 0000000..0e937b0 --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/UserTest.java @@ -0,0 +1,30 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.user.model.User; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class UserTest { + + @Test + void testUser() { + User user = new User(); + user.setId(1L); + user.setName("John"); + user.setEmail("john@example.com"); + + assertEquals(1L, user.getId()); + assertEquals("John", user.getName()); + assertEquals("john@example.com", user.getEmail()); + } + + @Test + void testUserWithNulls() { + User user = new User(); + assertNull(user.getId()); + assertNull(user.getName()); + assertNull(user.getEmail()); + } +} diff --git a/server/src/test/java/ru/practicum/shareit/UserTransformerImplTest.java b/server/src/test/java/ru/practicum/shareit/UserTransformerImplTest.java new file mode 100644 index 0000000..f73ca5f --- /dev/null +++ b/server/src/test/java/ru/practicum/shareit/UserTransformerImplTest.java @@ -0,0 +1,76 @@ +package ru.practicum.shareit; + +import org.junit.jupiter.api.Test; +import ru.practicum.shareit.user.dto.NewUserRequest; +import ru.practicum.shareit.user.dto.UpdateUserRequest; +import ru.practicum.shareit.user.dto.UserResponse; +import ru.practicum.shareit.user.dto.UserTransformerImpl; +import ru.practicum.shareit.user.model.User; + +import static org.junit.jupiter.api.Assertions.*; + +class UserTransformerImplTest { + + private final UserTransformerImpl transformer = new UserTransformerImpl(); + + @Test + void testToResponse() { + User user = new User(); + user.setId(1L); + user.setName("John"); + user.setEmail("john@example.com"); + + UserResponse response = transformer.toResponse(user); + assertEquals(1L, response.getId()); + assertEquals("John", response.getName()); + assertEquals("john@example.com", response.getEmail()); + } + + @Test + void testToUser() { + NewUserRequest request = new NewUserRequest("John", "john@example.com"); + User user = transformer.toUser(request); + assertEquals("John", user.getName()); + assertEquals("john@example.com", user.getEmail()); + assertNull(user.getId()); + } + + @Test + void testApplyUpdatesWithAllFields() { + User user = new User(); + user.setName("Old Name"); + user.setEmail("old@example.com"); + + UpdateUserRequest updates = new UpdateUserRequest("New Name", "new@example.com"); + User updatedUser = transformer.applyUpdates(updates, user); + + assertEquals("New Name", updatedUser.getName()); + assertEquals("new@example.com", updatedUser.getEmail()); + } + + @Test + void testApplyUpdatesWithPartialFields() { + User user = new User(); + user.setName("Old Name"); + user.setEmail("old@example.com"); + + UpdateUserRequest updates = new UpdateUserRequest(null, "new@example.com"); + User updatedUser = transformer.applyUpdates(updates, user); + + assertEquals("Old Name", updatedUser.getName()); + assertEquals("new@example.com", updatedUser.getEmail()); + } + + @Test + void testApplyUpdatesWithNoChanges() { + User user = new User(); + user.setName("Old Name"); + user.setEmail("old@example.com"); + + UpdateUserRequest updates = new UpdateUserRequest(null, null); + User updatedUser = transformer.applyUpdates(updates, user); + + assertEquals("Old Name", updatedUser.getName()); + assertEquals("old@example.com", updatedUser.getEmail()); + } +} \ No newline at end of file diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequest.java b/src/main/java/ru/practicum/shareit/request/ItemRequest.java deleted file mode 100644 index 95d6f23..0000000 --- a/src/main/java/ru/practicum/shareit/request/ItemRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.shareit.request; - -/** - * TODO Sprint add-item-requests. - */ -public class ItemRequest { -} diff --git a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java b/src/main/java/ru/practicum/shareit/request/ItemRequestController.java deleted file mode 100644 index 064e2e9..0000000 --- a/src/main/java/ru/practicum/shareit/request/ItemRequestController.java +++ /dev/null @@ -1,12 +0,0 @@ -package ru.practicum.shareit.request; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * TODO Sprint add-item-requests. - */ -@RestController -@RequestMapping(path = "/requests") -public class ItemRequestController { -} 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/test/java/ru/practicum/shareit/UserManagerImplTest.java b/src/test/java/ru/practicum/shareit/UserManagerImplTest.java deleted file mode 100644 index b51c28d..0000000 --- a/src/test/java/ru/practicum/shareit/UserManagerImplTest.java +++ /dev/null @@ -1,155 +0,0 @@ -package ru.practicum.shareit; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import ru.practicum.shareit.exception.UserNotFoundException; -import ru.practicum.shareit.user.dto.NewUserRequest; -import ru.practicum.shareit.user.dto.UpdateUserRequest; -import ru.practicum.shareit.user.dto.UserResponse; -import ru.practicum.shareit.user.dto.UserTransformer; -import ru.practicum.shareit.user.model.User; -import ru.practicum.shareit.user.model.UserManagerImpl; -import ru.practicum.shareit.user.model.UserRepository; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -class UserManagerImplTest { - - @Mock - private UserRepository userRepository; - - @Mock - private UserTransformer transformer; - - @InjectMocks - private UserManagerImpl userManager; - - private User user; - private UserResponse userResponse; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - user = new User(); - user.setId(1L); - user.setName("Test User"); - user.setEmail("test@example.com"); - - userResponse = new UserResponse(1L, "Test User", "test@example.com"); - } - - @Test - void fetchAllUsers_whenUsersExist_returnsList() { - when(userRepository.findAll()).thenReturn(List.of(user)); - when(transformer.toResponse(user)).thenReturn(userResponse); - - List result = userManager.fetchAllUsers(); - - assertNotNull(result); - assertEquals(1, result.size()); - assertEquals(userResponse, result.get(0)); - verify(userRepository, times(1)).findAll(); - verify(transformer, times(1)).toResponse(user); - } - - @Test - void fetchAllUsers_whenNoUsers_returnsEmptyList() { - when(userRepository.findAll()).thenReturn(Collections.emptyList()); - - List result = userManager.fetchAllUsers(); - - assertNotNull(result); - assertTrue(result.isEmpty()); - verify(userRepository, times(1)).findAll(); - verify(transformer, never()).toResponse(any()); - } - - @Test - void addUser_whenValidRequest_returnsUserResponse() { - NewUserRequest request = new NewUserRequest("Test User", "test@example.com"); - when(transformer.toUser(request)).thenReturn(user); - when(userRepository.existsByEmail("test@example.com")).thenReturn(false); - when(userRepository.save(user)).thenReturn(user); - when(transformer.toResponse(user)).thenReturn(userResponse); - - UserResponse result = userManager.addUser(request); - - assertNotNull(result); - assertEquals(userResponse, result); - verify(transformer, times(1)).toUser(request); - verify(userRepository, times(1)).existsByEmail("test@example.com"); - verify(userRepository, times(1)).save(user); - verify(transformer, times(1)).toResponse(user); - } - - - @Test - void findUserById_whenUserExists_returnsUserResponse() { - when(userRepository.findById(1L)).thenReturn(Optional.of(user)); - when(transformer.toResponse(user)).thenReturn(userResponse); - - UserResponse result = userManager.findUserById(1L); - - assertNotNull(result); - assertEquals(userResponse, result); - verify(userRepository, times(1)).findById(1L); - verify(transformer, times(1)).toResponse(user); - } - - @Test - void findUserById_whenUserNotFound_throwsUserNotFoundException() { - when(userRepository.findById(1L)).thenReturn(Optional.empty()); - - UserNotFoundException exception = assertThrows(UserNotFoundException.class, - () -> userManager.findUserById(1L)); - - assertEquals("Пользователь с ID 1 не найден", exception.getMessage()); - verify(userRepository, times(1)).findById(1L); - verify(transformer, never()).toResponse(any()); - } - - @Test - void modifyUser_whenValidRequest_returnsUpdatedUserResponse() { - UpdateUserRequest request = new UpdateUserRequest("Updated User", "updated@example.com"); - User updatedUser = new User(); - updatedUser.setId(1L); - updatedUser.setName("Updated User"); - updatedUser.setEmail("updated@example.com"); - UserResponse updatedResponse = new UserResponse(1L, "Updated User", "updated@example.com"); - - when(userRepository.findById(1L)).thenReturn(Optional.of(user)); - when(userRepository.existsByEmail("updated@example.com")).thenReturn(false); - when(transformer.applyUpdates(request, user)).thenReturn(updatedUser); - when(userRepository.save(updatedUser)).thenReturn(updatedUser); - when(transformer.toResponse(updatedUser)).thenReturn(updatedResponse); - - UserResponse result = userManager.modifyUser(request, 1L); - - assertNotNull(result); - assertEquals(updatedResponse, result); - verify(userRepository, times(1)).findById(1L); - verify(userRepository, times(1)).existsByEmail("updated@example.com"); - verify(transformer, times(1)).applyUpdates(request, user); - verify(userRepository, times(1)).save(updatedUser); - verify(transformer, times(1)).toResponse(updatedUser); - } - - @Test - void removeUser_whenUserExists_deletesUser() { - when(userRepository.findById(1L)).thenReturn(Optional.of(user)); - - userManager.removeUser(1L); - - verify(userRepository, times(1)).findById(1L); - verify(userRepository, times(1)).deleteById(1L); - } -} \ No newline at end of file