diff --git a/HELP.md b/HELP.md new file mode 100644 index 0000000..763be44 --- /dev/null +++ b/HELP.md @@ -0,0 +1,16 @@ +# Getting Started + +### Reference Documentation +For further reference, please consider the following sections: + +* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) +* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/4.0.3-SNAPSHOT/maven-plugin) +* [Create an OCI image](https://docs.spring.io/spring-boot/4.0.3-SNAPSHOT/maven-plugin/build-image.html) + +### Maven Parent overrides + +Due to Maven's design, elements are inherited from the parent POM to the project POM. +While most of the inheritance is fine, it also inherits unwanted elements like `` and `` from the parent. +To prevent this, the project POM contains empty overrides for these elements. +If you manually switch to a different parent and actually want the inheritance, you need to remove those overrides. + diff --git a/README.md b/README.md index 8419acd..1bcd147 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # SpringBootMvc -SorokinP + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a52e326 --- /dev/null +++ b/pom.xml @@ -0,0 +1,88 @@ + + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.5.9 + + + + com.example + demo + 0.0.1-SNAPSHOT + demo + Spring Boot MVC demo + + + 21 + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-devtools + compile + + + org.springframework.boot + spring-boot-starter-test + test + + + org.projectlombok + lombok + true + + + com.fasterxml.jackson.core + jackson-databind + 2.20.1 + compile + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + + diff --git a/src/main/java/com/example/demo/DemoApplication.java b/src/main/java/com/example/demo/DemoApplication.java new file mode 100644 index 0000000..64b538a --- /dev/null +++ b/src/main/java/com/example/demo/DemoApplication.java @@ -0,0 +1,13 @@ +package com.example.demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } + +} diff --git a/src/main/java/com/example/demo/controller/PetController.java b/src/main/java/com/example/demo/controller/PetController.java new file mode 100644 index 0000000..383e9a5 --- /dev/null +++ b/src/main/java/com/example/demo/controller/PetController.java @@ -0,0 +1,43 @@ +package com.example.demo.controller; + +import com.example.demo.model.PetDto; +import com.example.demo.model.UserDto; +import com.example.demo.service.PetService; +import com.example.demo.service.UserService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + + +@RestController +@Slf4j +@RequiredArgsConstructor +@RequestMapping("/pets") +public class PetController { + + private final PetService petService; + + @GetMapping(path = "/{id}") + public ResponseEntity getPet(@PathVariable long id) { + return ResponseEntity.status(HttpStatus.OK).body(petService.getPet(id)); + } + + @PostMapping(path = "/add") + public ResponseEntity createPet(@Valid @RequestBody PetDto petDto) { + return ResponseEntity.status(HttpStatus.CREATED).body(petService.createPet(petDto)); + } + + @PutMapping(path = "/update") + public ResponseEntity updatePet(@Valid @RequestBody PetDto petDto) { + return ResponseEntity.status(HttpStatus.OK).body(petService.updatePet(petDto)); + } + + @DeleteMapping(path = "/delete/{id}") + public ResponseEntity deletePet(@PathVariable long id) { + return ResponseEntity.status(HttpStatus.NO_CONTENT).body(petService.deletePet(id)); + } + +} diff --git a/src/main/java/com/example/demo/controller/UserController.java b/src/main/java/com/example/demo/controller/UserController.java new file mode 100644 index 0000000..c2fb026 --- /dev/null +++ b/src/main/java/com/example/demo/controller/UserController.java @@ -0,0 +1,41 @@ +package com.example.demo.controller; + +import com.example.demo.model.UserDto; +import com.example.demo.service.UserService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + + +@RestController +@Slf4j +@RequiredArgsConstructor +@RequestMapping("/users") +public class UserController { + + private final UserService userService; + + @GetMapping(path = "/{id}") + public ResponseEntity getUser(@PathVariable long id) { + return ResponseEntity.status(HttpStatus.OK).body(userService.getUser(id)); + } + + @PostMapping(path = "/add") + public ResponseEntity createUser(@Valid @RequestBody UserDto userDto) { + return ResponseEntity.status(HttpStatus.CREATED).body(userService.createUser(userDto)); + } + + @PutMapping(path = "/update") + public ResponseEntity updateUser(@Valid @RequestBody UserDto userDto) { + return ResponseEntity.status(HttpStatus.OK).body(userService.updateUser(userDto)); + } + + @DeleteMapping(path = "/delete/{id}") + public ResponseEntity deleteUser(@PathVariable long id) { + return ResponseEntity.status(HttpStatus.NO_CONTENT).body(userService.deleteUser(id)); + } + +} diff --git a/src/main/java/com/example/demo/exception/BadRequestException.java b/src/main/java/com/example/demo/exception/BadRequestException.java new file mode 100644 index 0000000..0a3e63a --- /dev/null +++ b/src/main/java/com/example/demo/exception/BadRequestException.java @@ -0,0 +1,21 @@ +package com.example.demo.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + + +@Getter +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class BadRequestException extends RuntimeException { + + private final Long resourceId; + private final String operation; + + public BadRequestException(Long resourceId, String message, String operation) { + super(message); + this.resourceId = resourceId; + this.operation = operation; + } + +} diff --git a/src/main/java/com/example/demo/exception/ErrorMessageResponse.java b/src/main/java/com/example/demo/exception/ErrorMessageResponse.java new file mode 100644 index 0000000..6c17f16 --- /dev/null +++ b/src/main/java/com/example/demo/exception/ErrorMessageResponse.java @@ -0,0 +1,20 @@ +package com.example.demo.exception; + +import lombok.Getter; +import java.time.Instant; + + +@Getter +public class ErrorMessageResponse { + + private final String error; + private final Instant timestamp; + private final String operation; + + public ErrorMessageResponse(String error, String operation) { + this.error = error; + this.timestamp = Instant.now(); + this.operation = operation; + } + +} diff --git a/src/main/java/com/example/demo/exception/GlobalExceptionHandler.java b/src/main/java/com/example/demo/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..eecec95 --- /dev/null +++ b/src/main/java/com/example/demo/exception/GlobalExceptionHandler.java @@ -0,0 +1,41 @@ +package com.example.demo.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidationException(MethodArgumentNotValidException ex) { + var error = new ErrorMessageResponse("Неверный аргумент: " + ex.getMessage(), "Controller input"); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleResourceNotFoundException(ResourceNotFoundException ex) { + var error = new ErrorMessageResponse(ex.getMessage(), ex.getOperation()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + @ExceptionHandler(BadRequestException.class) + public ResponseEntity handleBadRequestException(BadRequestException ex) { + var error = new ErrorMessageResponse(ex.getMessage(), ex.getOperation()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception ex) { + var error = new ErrorMessageResponse(ex.getMessage(), "Generic"); + if (ex instanceof HttpMessageNotReadableException) { + error = new ErrorMessageResponse(ex.getMessage(), "Controller input"); + } + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + +} diff --git a/src/main/java/com/example/demo/exception/ResourceNotFoundException.java b/src/main/java/com/example/demo/exception/ResourceNotFoundException.java new file mode 100644 index 0000000..77ac55b --- /dev/null +++ b/src/main/java/com/example/demo/exception/ResourceNotFoundException.java @@ -0,0 +1,21 @@ +package com.example.demo.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + + +@Getter +@ResponseStatus(HttpStatus.NOT_FOUND) +public class ResourceNotFoundException extends RuntimeException { + + private final Long resourceId; + private final String operation; + + public ResourceNotFoundException(Long resourceId, String message, String operation) { + super(message); + this.resourceId = resourceId; + this.operation = operation; + } + +} diff --git a/src/main/java/com/example/demo/model/PetDto.java b/src/main/java/com/example/demo/model/PetDto.java new file mode 100644 index 0000000..7d65423 --- /dev/null +++ b/src/main/java/com/example/demo/model/PetDto.java @@ -0,0 +1,22 @@ +package com.example.demo.model; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + + +@Getter +@Setter +@RequiredArgsConstructor +@AllArgsConstructor +public class PetDto { + @NotNull + private Long id; + @NotNull + private String name; + @Positive + private Long userId; +} diff --git a/src/main/java/com/example/demo/model/UserDto.java b/src/main/java/com/example/demo/model/UserDto.java new file mode 100644 index 0000000..89738bc --- /dev/null +++ b/src/main/java/com/example/demo/model/UserDto.java @@ -0,0 +1,31 @@ +package com.example.demo.model; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.util.List; + + +@Getter +@Setter +@RequiredArgsConstructor +@AllArgsConstructor +public class UserDto { + @NotNull + private Long id; + @NotNull + private String name; + @Email + private String email; + @Positive + private Integer age; + @NotNull + private List pets; +} + +//public record UserDto(Long id, String name, String email, Integer age, List pets) {} diff --git a/src/main/java/com/example/demo/service/PetService.java b/src/main/java/com/example/demo/service/PetService.java new file mode 100644 index 0000000..e8abad5 --- /dev/null +++ b/src/main/java/com/example/demo/service/PetService.java @@ -0,0 +1,102 @@ +package com.example.demo.service; + +import com.example.demo.exception.ResourceNotFoundException; +import com.example.demo.model.PetDto; +import com.example.demo.model.UserDto; +import com.example.demo.service.inter.IPetService; +import org.springframework.stereotype.Service; + +import java.util.*; + + +@Service +public class PetService implements IPetService { + + private final UserService userService; + + public PetService(UserService userService) { + this.userService = userService; + } + + @Override + public PetDto getPet(Long id) { + return this.getAllPets() + .stream() + .filter(p -> (long)p.getId() == id) + .findFirst() + .orElseThrow(() -> new ResourceNotFoundException(id, "Pet not found", "Get pet")); + } + + @Override + public PetDto createPet(PetDto pet) { + UserDto user = this.userService.getUserMap() + .values() + .stream() + .filter(u -> u.getId().compareTo(pet.getUserId()) == 0) + .findFirst() + .orElseThrow(() -> new ResourceNotFoundException(pet.getUserId(), "User not found", "Create pet")); + //pet.setId((long) (this.getAllPets().size() + 1)); + user.getPets().add(pet); + return pet; + } + + @Override + public Long updatePet(PetDto pet) { + PetDto foundPet = this.getPet(pet.getId()); + // pet owner has changed + if (foundPet.getUserId().compareTo(pet.getUserId()) != 0) { + // remove pet from previous owner + this.userService.getUserMap().values().stream() + .filter(u -> u.getId().compareTo(foundPet.getUserId()) == 0) + .findFirst() + .map(u -> u.getPets().remove(foundPet)) + .orElseThrow(() -> new ResourceNotFoundException(pet.getUserId(), "Current pet owner not found", "Update pet")); + // add pet to new owner + foundPet.setName(pet.getName()); + foundPet.setUserId(pet.getUserId()); + this.userService.getUserMap().values().stream() + .filter(u -> u.getId().compareTo(foundPet.getUserId()) == 0) + .findFirst() + .map(u -> u.getPets().add(foundPet)) + .orElseThrow(() -> new ResourceNotFoundException(pet.getUserId(), "New pet owner not found", "Update pet")); + } else { + foundPet.setName(pet.getName()); + foundPet.setUserId(pet.getUserId()); + } + return foundPet.getId(); + } + + @Override + public Long deletePet(Long id) { + // collect all pet id + final Set petIds = new HashSet<>(); + this.userService.getUserMap().values().stream() + .map(UserDto::getPets) + .forEach(pets -> { + for (PetDto pet : pets) { + petIds.add(pet.getId()); + } + }); + // raise exception if the pet with id doesn't exist + if (!petIds.contains(id)) { + throw new ResourceNotFoundException(id, "Pet not found", "Delete pet"); + } + // iterate users and remove the pet + this.userService.getUserMap() + .values() + .forEach(user -> user.getPets().removeIf(pet -> id.compareTo(pet.getId()) == 0)); + return id; + } + + /** + * Get all Pets from all Users + */ + private Set getAllPets() { + final Set petSet = new HashSet<>(); + this.userService.getUserMap().values().stream() + .map(UserDto::getPets) + .forEach(petSet::addAll); + return petSet; + } + +} diff --git a/src/main/java/com/example/demo/service/UserService.java b/src/main/java/com/example/demo/service/UserService.java new file mode 100644 index 0000000..6abef1c --- /dev/null +++ b/src/main/java/com/example/demo/service/UserService.java @@ -0,0 +1,61 @@ +package com.example.demo.service; + +import com.example.demo.exception.BadRequestException; +import com.example.demo.exception.ResourceNotFoundException; +import com.example.demo.model.UserDto; +import com.example.demo.service.inter.IUserService; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + + +@Service +public class UserService implements IUserService { + + private final Map userMap; + + public UserService() { + this.userMap = new HashMap<>(); + } + + @Override + public UserDto getUser(Long id) { + return Optional.ofNullable(this.userMap.get(id)) + .orElseThrow(() -> new ResourceNotFoundException(id, "User not found", "Get user")); + } + + @Override + public UserDto createUser(UserDto dto) { + if (this.userMap.get(dto.getId()) != null) { + throw new BadRequestException(dto.getId(), "User already exists", "Create user"); + } + this.userMap.put(dto.getId(), dto); + return dto; + } + + @Override + public UserDto updateUser(UserDto dto) { + UserDto user = Optional.ofNullable(this.userMap.get(dto.getId())) + .orElseThrow(() -> new ResourceNotFoundException(dto.getId(), "User not found", "Update user")); + user.setName(dto.getName()); + user.setAge(dto.getAge()); + user.setEmail(dto.getEmail()); + user.setPets(dto.getPets()); + + return user; + } + + @Override + public Long deleteUser(Long id) { + UserDto user = Optional.ofNullable(this.userMap.get(id)) + .orElseThrow(() -> new ResourceNotFoundException(id, "User not found", "Delete user")); + return this.userMap.remove(user.getId()).getId(); + } + + public Map getUserMap() { + return this.userMap; + } + +} diff --git a/src/main/java/com/example/demo/service/inter/IPetService.java b/src/main/java/com/example/demo/service/inter/IPetService.java new file mode 100644 index 0000000..6fec18f --- /dev/null +++ b/src/main/java/com/example/demo/service/inter/IPetService.java @@ -0,0 +1,15 @@ +package com.example.demo.service.inter; + +import com.example.demo.model.PetDto; + + +public interface IPetService { + + PetDto createPet(PetDto dto); + + PetDto getPet(Long id); + + Long updatePet(PetDto dto); + + Long deletePet(Long id); +} diff --git a/src/main/java/com/example/demo/service/inter/IUserService.java b/src/main/java/com/example/demo/service/inter/IUserService.java new file mode 100644 index 0000000..294913b --- /dev/null +++ b/src/main/java/com/example/demo/service/inter/IUserService.java @@ -0,0 +1,15 @@ +package com.example.demo.service.inter; + +import com.example.demo.model.UserDto; + + +public interface IUserService { + + UserDto createUser(UserDto dto); + + UserDto getUser(Long id); + + UserDto updateUser(UserDto dto); + + Long deleteUser(Long id); +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..2109a44 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=demo diff --git a/src/test/java/com/example/demo/PetServiceTest.java b/src/test/java/com/example/demo/PetServiceTest.java new file mode 100644 index 0000000..d2e9a9d --- /dev/null +++ b/src/test/java/com/example/demo/PetServiceTest.java @@ -0,0 +1,102 @@ +package com.example.demo; + +import com.example.demo.model.PetDto; +import com.example.demo.model.UserDto; +import com.example.demo.service.PetService; +import com.example.demo.service.UserService; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.ArrayList; +import java.util.List; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; + + +@AutoConfigureMockMvc +@SpringBootTest +class PetServiceTest { + + @Autowired + private PetService petService; + + @Autowired + private UserService userService; + + @Autowired + private MockMvc mockMvc; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void shouldCreateNewPet() throws Exception { + var userDto = new UserDto(1L, + "Alex", + "test@example.com", + 35, + new ArrayList<>()); + + var petDto = new PetDto(0L, + "Jack", + 1L); + + this.userService.getUserMap().put(1L, userDto); + + String newPetJson = objectMapper.writeValueAsString(petDto); + + var jsonResponse = mockMvc.perform(post("/pets/add") + .contentType(MediaType.APPLICATION_JSON) + .content(newPetJson)) + .andExpect(status().isCreated()) + .andReturn() + .getResponse() + .getContentAsString(); + + var petDtoResponse = objectMapper.readValue(jsonResponse, PetDto.class); + + Assertions.assertEquals(petDto.getId(), petDtoResponse.getId()); + Assertions.assertEquals(petDto.getName(), petDtoResponse.getName()); + Assertions.assertEquals(petDto.getUserId(), petDtoResponse.getUserId()); + Assertions.assertDoesNotThrow(() -> petService.getPet(petDtoResponse.getId())); + } + + @Test + public void shouldGetPetById() throws Exception { + var petDto = new PetDto(1L, + "Jack", + 1L); + + var userDto = new UserDto(1L, + "Alex", + "test@example.com", + 35, + List.of(petDto)); + + this.userService.getUserMap().put(1L, userDto); + + var jsonResponse = mockMvc.perform(get("/pets/{id}", 1L)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + + var petDtoResponse = objectMapper.readValue(jsonResponse, PetDto.class); + + Assertions.assertEquals(petDto.getId(), petDtoResponse.getId()); + Assertions.assertEquals(petDto.getName(), petDtoResponse.getName()); + Assertions.assertEquals(petDto.getUserId(), petDtoResponse.getUserId()); + Assertions.assertDoesNotThrow(() -> petService.getPet(petDtoResponse.getId())); + } + +} + diff --git a/src/test/java/com/example/demo/UserServiceTest.java b/src/test/java/com/example/demo/UserServiceTest.java new file mode 100644 index 0000000..ca12da1 --- /dev/null +++ b/src/test/java/com/example/demo/UserServiceTest.java @@ -0,0 +1,90 @@ +package com.example.demo; + +import com.example.demo.model.UserDto; +import com.example.demo.service.UserService; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; + + +@AutoConfigureMockMvc +@SpringBootTest +class UserServiceTest { + + @Autowired + private UserService userService; + + @Autowired + private MockMvc mockMvc; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void shouldCreateNewUser() throws Exception { + var userDto = new UserDto(0L, + "Pavel", + "test@example.com", + 25, + List.of()); + + String newUserJson = objectMapper.writeValueAsString(userDto); + + var jsonResponse = mockMvc.perform(post("/users/add") + .contentType(MediaType.APPLICATION_JSON) + .content(newUserJson)) + .andExpect(status().isCreated()) + .andReturn() + .getResponse() + .getContentAsString(); + + var userDtoResponse = objectMapper.readValue(jsonResponse, UserDto.class); + + Assertions.assertEquals(userDto.getId(), userDtoResponse.getId()); + Assertions.assertEquals(userDto.getName(), userDtoResponse.getName()); + Assertions.assertEquals(userDto.getAge(), userDtoResponse.getAge()); + Assertions.assertEquals(userDto.getEmail(), userDtoResponse.getEmail()); + Assertions.assertEquals(userDto.getPets(), userDtoResponse.getPets()); + Assertions.assertDoesNotThrow(() -> userService.getUser(userDtoResponse.getId())); + } + + @Test + public void shouldGetUserById() throws Exception { + + var userDto = new UserDto(1L, + "Alex", + "test@example.com", + 35, + List.of()); + + this.userService.getUserMap().put(1L, userDto); + + var jsonResponse = mockMvc.perform(get("/users/{id}", 1L)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + + var userDtoResponse = objectMapper.readValue(jsonResponse, UserDto.class); + + Assertions.assertEquals(userDto.getId(), userDtoResponse.getId()); + Assertions.assertEquals(userDto.getName(), userDtoResponse.getName()); + Assertions.assertEquals(userDto.getAge(), userDtoResponse.getAge()); + Assertions.assertEquals(userDto.getEmail(), userDtoResponse.getEmail()); + Assertions.assertEquals(userDto.getPets(), userDtoResponse.getPets()); + Assertions.assertDoesNotThrow(() -> userService.getUser(userDtoResponse.getId())); + } + +}