diff --git a/.github/dco.yml b/.github/dco.yml deleted file mode 100644 index 37e411e1b..000000000 --- a/.github/dco.yml +++ /dev/null @@ -1,2 +0,0 @@ -require: - members: false \ No newline at end of file diff --git a/.github/workflows/deploy-and-test-cluster.yml b/.github/workflows/deploy-and-test-cluster.yml deleted file mode 100644 index 7353a604b..000000000 --- a/.github/workflows/deploy-and-test-cluster.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Deploy and Test Cluster - -on: - push: - branches: [main] - paths: - - 'k8s/**' - pull_request: - branches: [main] - paths: - - 'k8s/**' - -jobs: - deploy-and-test-cluster: - runs-on: ubuntu-latest - steps: - - name: Check out the repository - uses: actions/checkout@v2 - - - name: Create k8s Kind Cluster - uses: helm/kind-action@v1 - - - name: Deploy application - run: | - kubectl apply -f k8s/ - - - name: Wait for Pods to be ready - run: | - kubectl wait --for=condition=ready pod -l app=demo-db --timeout=180s - kubectl wait --for=condition=ready pod -l app=petclinic --timeout=180s - diff --git a/.github/workflows/gradle-build.yml b/.github/workflows/gradle-build.yml deleted file mode 100644 index c24c121b1..000000000 --- a/.github/workflows/gradle-build.yml +++ /dev/null @@ -1,31 +0,0 @@ -# This workflow will build a Java project with Gradle, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-java-with-gradle - -name: Java CI with Gradle - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - matrix: - java: [ '17' ] - - steps: - - uses: actions/checkout@v4 - - name: Set up JDK ${{matrix.java}} - uses: actions/setup-java@v4 - with: - java-version: ${{matrix.java}} - distribution: 'adopt' - cache: maven - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - - name: Build with Gradle - run: ./gradlew build diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml deleted file mode 100644 index a1ec4dab7..000000000 --- a/.github/workflows/maven-build.yml +++ /dev/null @@ -1,29 +0,0 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-java-with-maven - -name: Java CI with Maven - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - matrix: - java: [ '17' ] - - steps: - - uses: actions/checkout@v4 - - name: Set up JDK ${{matrix.java}} - uses: actions/setup-java@v4 - with: - java-version: ${{matrix.java}} - distribution: 'adopt' - cache: maven - - name: Build with Maven Wrapper - run: ./mvnw -B verify diff --git a/README.md b/README.md index c865c3b51..20db3e715 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,42 @@ +## Pet Attributes API Persistent Assignment + +## Project Updates + +- **Refactored / Rearranged Project Structure** + [Commit Link](https://github.com/shrirangjoshi94/spring-petclinic/commit/569cc89c3ae4ebe50a432022cbf71e568ef5b4d5) + +- **Save and Fetch Pet Attributes Feature** + [Pull Request Link](https://github.com/shrirangjoshi94/spring-petclinic/pull/2) + +--- + +### Development Notes + +What I did was first restructured the project and merged the refactoring PR. +Then I created a new PR to implement the save and fetch Pet Attributes feature. + + +### 1. Save Attributes for a Pet +**POST** `/pets/{petId}/attributes` +Example request: +`POST localhost:8080/pets/2/attributes` + +**Request Body:** +```json +{ + "temperament": "angry", + "lengthCm": 401, + "weightKg": 100 +} +``` + +### 2. GET Attributes for a Pet +**GET** `/pets/{petId}/attributes` +Example request: +`GET localhost:8080/pets/2/attributes` + +---------------------------------------------------------- + # Spring PetClinic Sample Application [![Build Status](https://github.com/spring-projects/spring-petclinic/actions/workflows/maven-build.yml/badge.svg)](https://github.com/spring-projects/spring-petclinic/actions/workflows/maven-build.yml)[![Build Status](https://github.com/spring-projects/spring-petclinic/actions/workflows/gradle-build.yml/badge.svg)](https://github.com/spring-projects/spring-petclinic/actions/workflows/gradle-build.yml) [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/spring-projects/spring-petclinic) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=7517918) @@ -126,40 +165,3 @@ The following items should be installed in your system: 1. Navigate to the Petclinic Visit [http://localhost:8080](http://localhost:8080) in your browser. - -## Looking for something in particular? - -|Spring Boot Configuration | Class or Java property files | -|--------------------------|---| -|The Main Class | [PetClinicApplication](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java) | -|Properties Files | [application.properties](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/resources) | -|Caching | [CacheConfiguration](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java) | - -## Interesting Spring Petclinic branches and forks - -The Spring Petclinic "main" branch in the [spring-projects](https://github.com/spring-projects/spring-petclinic) -GitHub org is the "canonical" implementation based on Spring Boot and Thymeleaf. There are -[quite a few forks](https://spring-petclinic.github.io/docs/forks.html) in the GitHub org -[spring-petclinic](https://github.com/spring-petclinic). If you are interested in using a different technology stack to implement the Pet Clinic, please join the community there. - -## Interaction with other open-source projects - -One of the best parts about working on the Spring Petclinic application is that we have the opportunity to work in direct contact with many Open Source projects. We found bugs/suggested improvements on various topics such as Spring, Spring Data, Bean Validation and even Eclipse! In many cases, they've been fixed/implemented in just a few days. -Here is a list of them: - -| Name | Issue | -|------|-------| -| Spring JDBC: simplify usage of NamedParameterJdbcTemplate | [SPR-10256](https://github.com/spring-projects/spring-framework/issues/14889) and [SPR-10257](https://github.com/spring-projects/spring-framework/issues/14890) | -| Bean Validation / Hibernate Validator: simplify Maven dependencies and backward compatibility |[HV-790](https://hibernate.atlassian.net/browse/HV-790) and [HV-792](https://hibernate.atlassian.net/browse/HV-792) | -| Spring Data: provide more flexibility when working with JPQL queries | [DATAJPA-292](https://github.com/spring-projects/spring-data-jpa/issues/704) | - -## Contributing - -The [issue tracker](https://github.com/spring-projects/spring-petclinic/issues) is the preferred channel for bug reports, feature requests and submitting pull requests. - -For pull requests, editor preferences are available in the [editor config](.editorconfig) for easy use in common text editors. Read more and download plugins at . All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin. -For additional details, please refer to the blog post [Hello DCO, Goodbye CLA: Simplifying Contributions to Spring](https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring). - -## License - -The Spring PetClinic sample application is released under version 2.0 of the [Apache License](https://www.apache.org/licenses/LICENSE-2.0). diff --git a/src/main/java/org/springframework/samples/petclinic/PetClinicRuntimeHints.java b/src/main/java/org/springframework/samples/petclinic/PetClinicRuntimeHints.java index 2975923d7..18e8dd2a3 100644 --- a/src/main/java/org/springframework/samples/petclinic/PetClinicRuntimeHints.java +++ b/src/main/java/org/springframework/samples/petclinic/PetClinicRuntimeHints.java @@ -18,9 +18,9 @@ package org.springframework.samples.petclinic; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.samples.petclinic.model.BaseEntity; -import org.springframework.samples.petclinic.model.Person; -import org.springframework.samples.petclinic.vet.Vet; +import org.springframework.samples.petclinic.common.model.BaseEntity; +import org.springframework.samples.petclinic.common.model.Person; +import org.springframework.samples.petclinic.vet.model.Vet; public class PetClinicRuntimeHints implements RuntimeHintsRegistrar { diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetTypeFormatter.java b/src/main/java/org/springframework/samples/petclinic/common/formatter/PetTypeFormatter.java similarity index 89% rename from src/main/java/org/springframework/samples/petclinic/owner/PetTypeFormatter.java rename to src/main/java/org/springframework/samples/petclinic/common/formatter/PetTypeFormatter.java index 73bfa1ae1..8c00458ef 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/PetTypeFormatter.java +++ b/src/main/java/org/springframework/samples/petclinic/common/formatter/PetTypeFormatter.java @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.owner; +package org.springframework.samples.petclinic.common.formatter; import org.springframework.format.Formatter; +import org.springframework.samples.petclinic.owner.model.PetType; +import org.springframework.samples.petclinic.owner.repository.OwnerRepository; import org.springframework.stereotype.Component; import java.text.ParseException; diff --git a/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java b/src/main/java/org/springframework/samples/petclinic/common/model/BaseEntity.java similarity index 95% rename from src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java rename to src/main/java/org/springframework/samples/petclinic/common/model/BaseEntity.java index 3038bce3a..ce063a244 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java +++ b/src/main/java/org/springframework/samples/petclinic/common/model/BaseEntity.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.model; +package org.springframework.samples.petclinic.common.model; import java.io.Serializable; diff --git a/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java b/src/main/java/org/springframework/samples/petclinic/common/model/NamedEntity.java similarity index 95% rename from src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java rename to src/main/java/org/springframework/samples/petclinic/common/model/NamedEntity.java index 012e8c4be..2077c4489 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java +++ b/src/main/java/org/springframework/samples/petclinic/common/model/NamedEntity.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.model; +package org.springframework.samples.petclinic.common.model; import jakarta.persistence.Column; import jakarta.persistence.MappedSuperclass; diff --git a/src/main/java/org/springframework/samples/petclinic/model/Person.java b/src/main/java/org/springframework/samples/petclinic/common/model/Person.java similarity index 95% rename from src/main/java/org/springframework/samples/petclinic/model/Person.java rename to src/main/java/org/springframework/samples/petclinic/common/model/Person.java index 7c3d81a84..b1d7b3137 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/Person.java +++ b/src/main/java/org/springframework/samples/petclinic/common/model/Person.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.model; +package org.springframework.samples.petclinic.common.model; import jakarta.persistence.Column; import jakarta.persistence.MappedSuperclass; diff --git a/src/main/java/org/springframework/samples/petclinic/model/package-info.java b/src/main/java/org/springframework/samples/petclinic/model/package-info.java deleted file mode 100644 index 37d6295e8..000000000 --- a/src/main/java/org/springframework/samples/petclinic/model/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * The classes in this package represent utilities used by the domain. - */ -package org.springframework.samples.petclinic.model; diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/owner/controller/OwnerController.java similarity index 96% rename from src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java rename to src/main/java/org/springframework/samples/petclinic/owner/controller/OwnerController.java index fa3506456..0fbf7e2e4 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/controller/OwnerController.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.owner; +package org.springframework.samples.petclinic.owner.controller; import java.util.List; import java.util.Optional; @@ -21,6 +21,8 @@ import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.samples.petclinic.owner.model.Owner; +import org.springframework.samples.petclinic.owner.repository.OwnerRepository; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; @@ -44,7 +46,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes; * @author Wick Dynex */ @Controller -class OwnerController { +public class OwnerController { private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm"; diff --git a/src/main/java/org/springframework/samples/petclinic/owner/controller/PetAttributesController.java b/src/main/java/org/springframework/samples/petclinic/owner/controller/PetAttributesController.java new file mode 100644 index 000000000..b6813a3d0 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/controller/PetAttributesController.java @@ -0,0 +1,67 @@ +package org.springframework.samples.petclinic.owner.controller; + +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.samples.petclinic.owner.dto.PetAttributesDTO; +import org.springframework.samples.petclinic.owner.expection.PetNotFoundException; +import org.springframework.samples.petclinic.owner.model.PetAttributes; +import org.springframework.samples.petclinic.owner.repository.PetRepository; +import org.springframework.samples.petclinic.owner.service.PetAttributesService; +import org.springframework.samples.petclinic.owner.validation.PetIdExistsValidator; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/pets/{petId}/attributes") +public class PetAttributesController { + + private final PetAttributesService petAttributesService; + + private PetIdExistsValidator petIdExistsValidator; + + private PetRepository petRepository; + + public PetAttributesController(PetIdExistsValidator petIdExistsValidator, PetAttributesService petAttributesService, + PetRepository petRepository) { + this.petIdExistsValidator = petIdExistsValidator; + this.petAttributesService = petAttributesService; + this.petRepository = petRepository; + } + + @GetMapping + public ResponseEntity getPetAttributes(@PathVariable("petId") int petId) { + if (!petRepository.existsById(petId)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Pet not found"); + } + + Optional optionalAttributes = petAttributesService.findByPetId(petId); + + return optionalAttributes.>map(ResponseEntity::ok) + .orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND).body("Attributes not found")); + } + + @PostMapping + public ResponseEntity savePetAttributes(@PathVariable int petId, @Valid @RequestBody PetAttributesDTO dto, + BindingResult bindingResult) { + // Validate petId exists + petIdExistsValidator.validate(petId, bindingResult); + + if (bindingResult.hasErrors()) { + return ResponseEntity.badRequest().body(bindingResult.getAllErrors()); + } + + dto.setPetId(petId); + + try { + petAttributesService.savePetAttributes(dto); + return ResponseEntity.status(HttpStatus.CREATED).body("Pet attributes saved"); + } catch (PetNotFoundException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage()); + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java b/src/main/java/org/springframework/samples/petclinic/owner/controller/PetController.java similarity index 92% rename from src/main/java/org/springframework/samples/petclinic/owner/PetController.java rename to src/main/java/org/springframework/samples/petclinic/owner/controller/PetController.java index 3234e39d6..63df354f2 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/controller/PetController.java @@ -13,12 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.owner; +package org.springframework.samples.petclinic.owner.controller; import java.time.LocalDate; import java.util.Collection; import java.util.Optional; +import org.springframework.samples.petclinic.owner.model.Owner; +import org.springframework.samples.petclinic.owner.model.Pet; +import org.springframework.samples.petclinic.owner.model.PetType; +import org.springframework.samples.petclinic.owner.repository.OwnerRepository; +import org.springframework.samples.petclinic.owner.validation.PetValidator; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.util.StringUtils; @@ -42,7 +47,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes; */ @Controller @RequestMapping("/owners/{ownerId}") -class PetController { +public class PetController { private static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm"; diff --git a/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java b/src/main/java/org/springframework/samples/petclinic/owner/controller/VisitController.java similarity index 90% rename from src/main/java/org/springframework/samples/petclinic/owner/VisitController.java rename to src/main/java/org/springframework/samples/petclinic/owner/controller/VisitController.java index b546f609a..072dfcc51 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/controller/VisitController.java @@ -13,11 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.owner; +package org.springframework.samples.petclinic.owner.controller; import java.util.Map; import java.util.Optional; +import org.springframework.samples.petclinic.owner.model.Owner; +import org.springframework.samples.petclinic.owner.repository.OwnerRepository; +import org.springframework.samples.petclinic.owner.model.Pet; +import org.springframework.samples.petclinic.owner.model.Visit; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.WebDataBinder; @@ -39,7 +43,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes; * @author Wick Dynex */ @Controller -class VisitController { +public class VisitController { private final OwnerRepository owners; diff --git a/src/main/java/org/springframework/samples/petclinic/owner/dto/PetAttributesDTO.java b/src/main/java/org/springframework/samples/petclinic/owner/dto/PetAttributesDTO.java new file mode 100644 index 000000000..a619c631d --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/dto/PetAttributesDTO.java @@ -0,0 +1,60 @@ +package org.springframework.samples.petclinic.owner.dto; + +import jakarta.validation.constraints.*; + +import java.math.BigDecimal; + +public class PetAttributesDTO { + + @NotNull(message = "Pet ID is required") + private int petId; + + @NotBlank(message = "Temperament is required") + @Size(max = 100, message = "Temperament must not exceed 100 characters") + private String temperament; + + @NotNull + @DecimalMin(value = "0.1", inclusive = true, message = "Length must be at least 0.1 cm") + @DecimalMax(value = "500.0", inclusive = true, message = "Length must be less than or equal to 500 cm") + private BigDecimal lengthCm; + + @NotNull + @DecimalMin(value = "0.1", inclusive = true, message = "Weight must be at least 0.1 kg") + @DecimalMax(value = "500.0", inclusive = true, message = "Weight must be less than or equal to 500 kg") + private BigDecimal weightKg; + + // Getters and setters + + public int getPetId() { + return petId; + } + + public void setPetId(int petId) { + this.petId = petId; + } + + public String getTemperament() { + return temperament; + } + + public void setTemperament(String temperament) { + this.temperament = temperament; + } + + public BigDecimal getLengthCm() { + return lengthCm; + } + + public void setLengthCm(BigDecimal lengthCm) { + this.lengthCm = lengthCm; + } + + public BigDecimal getWeightKg() { + return weightKg; + } + + public void setWeightKg(BigDecimal weightKg) { + this.weightKg = weightKg; + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/expection/PetNotFoundException.java b/src/main/java/org/springframework/samples/petclinic/owner/expection/PetNotFoundException.java new file mode 100644 index 000000000..a1746f5a2 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/expection/PetNotFoundException.java @@ -0,0 +1,9 @@ +package org.springframework.samples.petclinic.owner.expection; + +public class PetNotFoundException extends RuntimeException { + + public PetNotFoundException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java b/src/main/java/org/springframework/samples/petclinic/owner/model/Owner.java similarity index 97% rename from src/main/java/org/springframework/samples/petclinic/owner/Owner.java rename to src/main/java/org/springframework/samples/petclinic/owner/model/Owner.java index c5ae067dc..9a1e179b4 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/model/Owner.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.owner; +package org.springframework.samples.petclinic.owner.model; import java.util.ArrayList; import java.util.List; import org.springframework.core.style.ToStringCreator; -import org.springframework.samples.petclinic.model.Person; +import org.springframework.samples.petclinic.common.model.Person; import org.springframework.util.Assert; import jakarta.persistence.CascadeType; diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Pet.java b/src/main/java/org/springframework/samples/petclinic/owner/model/Pet.java similarity index 94% rename from src/main/java/org/springframework/samples/petclinic/owner/Pet.java rename to src/main/java/org/springframework/samples/petclinic/owner/model/Pet.java index 1fdc77cec..1f36454ed 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Pet.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/model/Pet.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.owner; +package org.springframework.samples.petclinic.owner.model; import java.time.LocalDate; import java.util.Collection; @@ -21,7 +21,7 @@ import java.util.LinkedHashSet; import java.util.Set; import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.samples.petclinic.model.NamedEntity; +import org.springframework.samples.petclinic.common.model.NamedEntity; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; diff --git a/src/main/java/org/springframework/samples/petclinic/owner/model/PetAttributes.java b/src/main/java/org/springframework/samples/petclinic/owner/model/PetAttributes.java new file mode 100644 index 000000000..69f9204d2 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/model/PetAttributes.java @@ -0,0 +1,82 @@ +package org.springframework.samples.petclinic.owner.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.*; + +import java.math.BigDecimal; + +@Entity +@Table(name = "pet_attributes") +public class PetAttributes { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pet_id", nullable = false) + @JsonIgnore + private Pet pet; + + private String temperament; + + @Column(name = "length_cm", precision = 5, scale = 2) + private BigDecimal lengthCm; + + @Column(name = "weight_kg", precision = 5, scale = 2) + private BigDecimal weightKg; + + @Column(name = "additional_attributes", columnDefinition = "json") + private String additionalAttributes; + + // --- Getters and Setters --- + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Pet getPet() { + return pet; + } + + public void setPet(Pet pet) { + this.pet = pet; + } + + public String getTemperament() { + return temperament; + } + + public void setTemperament(String temperament) { + this.temperament = temperament; + } + + public BigDecimal getLengthCm() { + return lengthCm; + } + + public void setLengthCm(BigDecimal lengthCm) { + this.lengthCm = lengthCm; + } + + public BigDecimal getWeightKg() { + return weightKg; + } + + public void setWeightKg(BigDecimal weightKg) { + this.weightKg = weightKg; + } + + public String getAdditionalAttributes() { + return additionalAttributes; + } + + public void setAdditionalAttributes(String additionalAttributes) { + this.additionalAttributes = additionalAttributes; + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetType.java b/src/main/java/org/springframework/samples/petclinic/owner/model/PetType.java similarity index 86% rename from src/main/java/org/springframework/samples/petclinic/owner/PetType.java rename to src/main/java/org/springframework/samples/petclinic/owner/model/PetType.java index eeea6a758..19cb20aa2 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/PetType.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/model/PetType.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.owner; +package org.springframework.samples.petclinic.owner.model; -import org.springframework.samples.petclinic.model.NamedEntity; +import org.springframework.samples.petclinic.common.model.NamedEntity; import jakarta.persistence.Entity; import jakarta.persistence.Table; diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Visit.java b/src/main/java/org/springframework/samples/petclinic/owner/model/Visit.java similarity index 92% rename from src/main/java/org/springframework/samples/petclinic/owner/Visit.java rename to src/main/java/org/springframework/samples/petclinic/owner/model/Visit.java index 35569bdaa..03a09ea6d 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Visit.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/model/Visit.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.owner; +package org.springframework.samples.petclinic.owner.model; import java.time.LocalDate; import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.samples.petclinic.model.BaseEntity; +import org.springframework.samples.petclinic.common.model.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java b/src/main/java/org/springframework/samples/petclinic/owner/repository/OwnerRepository.java similarity index 92% rename from src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java rename to src/main/java/org/springframework/samples/petclinic/owner/repository/OwnerRepository.java index ae4659114..a78c154c1 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/repository/OwnerRepository.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.owner; +package org.springframework.samples.petclinic.owner.repository; import java.util.List; import java.util.Optional; @@ -23,6 +23,8 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.samples.petclinic.owner.model.Owner; +import org.springframework.samples.petclinic.owner.model.PetType; /** * Repository class for Owner domain objects All method names are compliant diff --git a/src/main/java/org/springframework/samples/petclinic/owner/repository/PetAttributesRepository.java b/src/main/java/org/springframework/samples/petclinic/owner/repository/PetAttributesRepository.java new file mode 100644 index 000000000..f49221593 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/repository/PetAttributesRepository.java @@ -0,0 +1,24 @@ +package org.springframework.samples.petclinic.owner.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.samples.petclinic.owner.model.PetAttributes; + +/** + * Repository class for PetAttributes domain objects. Provides CRUD + * operations and custom query methods related to a pet's attributes. + * + * @see org.springframework.samples.petclinic.owner.model.PetAttributes + */ +public interface PetAttributesRepository extends JpaRepository { + + /** + * Find pet attributes by pet ID. + * + * @param petId the ID of the pet + * @return an Optional containing the PetAttributes if found + */ + Optional findByPetId(Integer petId); + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/repository/PetRepository.java b/src/main/java/org/springframework/samples/petclinic/owner/repository/PetRepository.java new file mode 100644 index 000000000..98c6f5c89 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/repository/PetRepository.java @@ -0,0 +1,8 @@ +package org.springframework.samples.petclinic.owner.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.samples.petclinic.owner.model.Pet; + +public interface PetRepository extends JpaRepository { + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/service/PetAttributesService.java b/src/main/java/org/springframework/samples/petclinic/owner/service/PetAttributesService.java new file mode 100644 index 000000000..8465a95c8 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/service/PetAttributesService.java @@ -0,0 +1,47 @@ +package org.springframework.samples.petclinic.owner.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.owner.dto.PetAttributesDTO; +import org.springframework.samples.petclinic.owner.expection.PetNotFoundException; +import org.springframework.samples.petclinic.owner.model.Pet; +import org.springframework.samples.petclinic.owner.model.PetAttributes; +import org.springframework.samples.petclinic.owner.repository.PetAttributesRepository; +import org.springframework.samples.petclinic.owner.repository.PetRepository; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +public class PetAttributesService { + + private final PetAttributesRepository petAttributesRepository; + + private final PetRepository petRepository; + + @Autowired + public PetAttributesService(PetAttributesRepository petAttributesRepository, PetRepository petRepository) { + this.petAttributesRepository = petAttributesRepository; + this.petRepository = petRepository; + } + + public Optional findByPetId(Integer petId) { + return petAttributesRepository.findByPetId(petId); + } + + public void savePetAttributes(PetAttributesDTO dto) { + Optional pet = petRepository.findById(dto.getPetId()); + + if (pet.isEmpty()) { + throw new PetNotFoundException("Pet not found"); + } + + PetAttributes attributes = petAttributesRepository.findByPetId(dto.getPetId()).orElse(new PetAttributes()); + + attributes.setPet(pet.get()); + attributes.setTemperament(dto.getTemperament()); + attributes.setLengthCm(dto.getLengthCm()); + attributes.setWeightKg(dto.getWeightKg()); + petAttributesRepository.save(attributes); + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/validation/PetIdExistsValidator.java b/src/main/java/org/springframework/samples/petclinic/owner/validation/PetIdExistsValidator.java new file mode 100644 index 000000000..84462979e --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/validation/PetIdExistsValidator.java @@ -0,0 +1,49 @@ +package org.springframework.samples.petclinic.owner.validation; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.owner.repository.PetRepository; +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; + +@Component +public class PetIdExistsValidator implements Validator { + + private final PetRepository petRepository; + + @Autowired + public PetIdExistsValidator(PetRepository petRepository) { + this.petRepository = petRepository; + } + + @Override + public boolean supports(Class clazz) { + // This validator supports Integer class, for validating petId field + return Integer.class.equals(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + if (target == null) { + errors.reject("petId.null", "Pet ID is required"); + return; + } + + if (!(target instanceof Integer)) { + errors.reject("petId.type", "Pet ID must be an integer"); + return; + } + + Integer petId = (Integer) target; + + if (petId <= 0) { + errors.rejectValue("", "petId.positive", "Pet ID must be a positive number"); + return; + } + + if (!petRepository.existsById(petId)) { + errors.rejectValue("", "petId.notFound", "Pet with ID " + petId + " does not exist"); + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetValidator.java b/src/main/java/org/springframework/samples/petclinic/owner/validation/PetValidator.java similarity index 93% rename from src/main/java/org/springframework/samples/petclinic/owner/PetValidator.java rename to src/main/java/org/springframework/samples/petclinic/owner/validation/PetValidator.java index 6422aa8db..0f8443f84 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/PetValidator.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/validation/PetValidator.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.owner; +package org.springframework.samples.petclinic.owner.validation; +import org.springframework.samples.petclinic.owner.model.Pet; import org.springframework.util.StringUtils; import org.springframework.validation.Errors; import org.springframework.validation.Validator; diff --git a/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java b/src/main/java/org/springframework/samples/petclinic/system/config/CacheConfiguration.java similarity index 96% rename from src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java rename to src/main/java/org/springframework/samples/petclinic/system/config/CacheConfiguration.java index f90679094..d42e1f1b5 100644 --- a/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java +++ b/src/main/java/org/springframework/samples/petclinic/system/config/CacheConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.samples.petclinic.system; +package org.springframework.samples.petclinic.system.config; import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer; import org.springframework.cache.annotation.EnableCaching; diff --git a/src/main/java/org/springframework/samples/petclinic/system/WebConfiguration.java b/src/main/java/org/springframework/samples/petclinic/system/config/WebConfiguration.java similarity index 96% rename from src/main/java/org/springframework/samples/petclinic/system/WebConfiguration.java rename to src/main/java/org/springframework/samples/petclinic/system/config/WebConfiguration.java index 1ef32e4dc..07c1653fa 100644 --- a/src/main/java/org/springframework/samples/petclinic/system/WebConfiguration.java +++ b/src/main/java/org/springframework/samples/petclinic/system/config/WebConfiguration.java @@ -1,4 +1,4 @@ -package org.springframework.samples.petclinic.system; +package org.springframework.samples.petclinic.system.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/org/springframework/samples/petclinic/system/CrashController.java b/src/main/java/org/springframework/samples/petclinic/system/controller/CrashController.java similarity index 92% rename from src/main/java/org/springframework/samples/petclinic/system/CrashController.java rename to src/main/java/org/springframework/samples/petclinic/system/controller/CrashController.java index 2b28600fd..4d686fcef 100644 --- a/src/main/java/org/springframework/samples/petclinic/system/CrashController.java +++ b/src/main/java/org/springframework/samples/petclinic/system/controller/CrashController.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.system; +package org.springframework.samples.petclinic.system.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @@ -26,7 +26,7 @@ import org.springframework.web.bind.annotation.GetMapping; * Also see how a view that resolves to "error" has been added ("error.html"). */ @Controller -class CrashController { +public class CrashController { @GetMapping("/oups") public String triggerException() { diff --git a/src/main/java/org/springframework/samples/petclinic/system/WelcomeController.java b/src/main/java/org/springframework/samples/petclinic/system/controller/WelcomeController.java similarity index 92% rename from src/main/java/org/springframework/samples/petclinic/system/WelcomeController.java rename to src/main/java/org/springframework/samples/petclinic/system/controller/WelcomeController.java index 9224015bc..7b51b4fb6 100644 --- a/src/main/java/org/springframework/samples/petclinic/system/WelcomeController.java +++ b/src/main/java/org/springframework/samples/petclinic/system/controller/WelcomeController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.samples.petclinic.system; +package org.springframework.samples.petclinic.system.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/org/springframework/samples/petclinic/vet/VetController.java b/src/main/java/org/springframework/samples/petclinic/vet/controller/VetController.java similarity index 90% rename from src/main/java/org/springframework/samples/petclinic/vet/VetController.java rename to src/main/java/org/springframework/samples/petclinic/vet/controller/VetController.java index 29fcecc7a..2121f4e7b 100644 --- a/src/main/java/org/springframework/samples/petclinic/vet/VetController.java +++ b/src/main/java/org/springframework/samples/petclinic/vet/controller/VetController.java @@ -13,13 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.vet; +package org.springframework.samples.petclinic.vet.controller; import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.samples.petclinic.vet.model.Vet; +import org.springframework.samples.petclinic.vet.repository.VetRepository; +import org.springframework.samples.petclinic.vet.model.Vets; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -33,7 +36,7 @@ import org.springframework.web.bind.annotation.ResponseBody; * @author Arjen Poutsma */ @Controller -class VetController { +public class VetController { private final VetRepository vetRepository; diff --git a/src/main/java/org/springframework/samples/petclinic/vet/Specialty.java b/src/main/java/org/springframework/samples/petclinic/vet/model/Specialty.java similarity index 87% rename from src/main/java/org/springframework/samples/petclinic/vet/Specialty.java rename to src/main/java/org/springframework/samples/petclinic/vet/model/Specialty.java index b0b6315fc..91fc9c8ef 100644 --- a/src/main/java/org/springframework/samples/petclinic/vet/Specialty.java +++ b/src/main/java/org/springframework/samples/petclinic/vet/model/Specialty.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.vet; +package org.springframework.samples.petclinic.vet.model; -import org.springframework.samples.petclinic.model.NamedEntity; +import org.springframework.samples.petclinic.common.model.NamedEntity; import jakarta.persistence.Entity; import jakarta.persistence.Table; diff --git a/src/main/java/org/springframework/samples/petclinic/vet/Vet.java b/src/main/java/org/springframework/samples/petclinic/vet/model/Vet.java similarity index 91% rename from src/main/java/org/springframework/samples/petclinic/vet/Vet.java rename to src/main/java/org/springframework/samples/petclinic/vet/model/Vet.java index 00c7ec1c8..dd30033ce 100644 --- a/src/main/java/org/springframework/samples/petclinic/vet/Vet.java +++ b/src/main/java/org/springframework/samples/petclinic/vet/model/Vet.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.vet; +package org.springframework.samples.petclinic.vet.model; import java.util.Comparator; import java.util.HashSet; @@ -21,8 +21,8 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import org.springframework.samples.petclinic.model.NamedEntity; -import org.springframework.samples.petclinic.model.Person; +import org.springframework.samples.petclinic.common.model.NamedEntity; +import org.springframework.samples.petclinic.common.model.Person; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; diff --git a/src/main/java/org/springframework/samples/petclinic/vet/Vets.java b/src/main/java/org/springframework/samples/petclinic/vet/model/Vets.java similarity index 95% rename from src/main/java/org/springframework/samples/petclinic/vet/Vets.java rename to src/main/java/org/springframework/samples/petclinic/vet/model/Vets.java index 9b672d25e..d792d9fd2 100644 --- a/src/main/java/org/springframework/samples/petclinic/vet/Vets.java +++ b/src/main/java/org/springframework/samples/petclinic/vet/model/Vets.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.vet; +package org.springframework.samples.petclinic.vet.model; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/org/springframework/samples/petclinic/vet/VetRepository.java b/src/main/java/org/springframework/samples/petclinic/vet/repository/VetRepository.java similarity index 94% rename from src/main/java/org/springframework/samples/petclinic/vet/VetRepository.java rename to src/main/java/org/springframework/samples/petclinic/vet/repository/VetRepository.java index 8b9e0823c..b0c872313 100644 --- a/src/main/java/org/springframework/samples/petclinic/vet/VetRepository.java +++ b/src/main/java/org/springframework/samples/petclinic/vet/repository/VetRepository.java @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.vet; +package org.springframework.samples.petclinic.vet.repository; import org.springframework.cache.annotation.Cacheable; import org.springframework.dao.DataAccessException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.repository.Repository; +import org.springframework.samples.petclinic.vet.model.Vet; import org.springframework.transaction.annotation.Transactional; import java.util.Collection; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6ed985654..af0c0c9be 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,7 +1,22 @@ # database init, supports mysql too -database=h2 -spring.sql.init.schema-locations=classpath*:db/${database}/schema.sql -spring.sql.init.data-locations=classpath*:db/${database}/data.sql +#database=jdbc:mysql://localhost:3306/petclinic?useSSL=false&serverTimezone=UTC +#spring.datasource.username=root +#spring.datasource.password=testA@123 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +# database init, supports mysql too +database=mysql +spring.datasource.url=${MYSQL_URL:jdbc:mysql://localhost/petclinic} +spring.datasource.username=root +spring.datasource.password=your-password +# SQL is written to be idempotent so this is safe +spring.sql.init.mode=always + +# Hibernate Dialect (important for compatibility) +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect + +spring.sql.init.schema-locations=classpath*:db/mysql/schema.sql +spring.sql.init.data-locations=classpath*:db/mysql/data.sql # Web spring.thymeleaf.mode=HTML diff --git a/src/main/resources/db/mysql/schema.sql b/src/main/resources/db/mysql/schema.sql index 2591a516d..8fe95dce9 100644 --- a/src/main/resources/db/mysql/schema.sql +++ b/src/main/resources/db/mysql/schema.sql @@ -53,3 +53,13 @@ CREATE TABLE IF NOT EXISTS visits ( description VARCHAR(255), FOREIGN KEY (pet_id) REFERENCES pets(id) ) engine=InnoDB; + +CREATE TABLE IF NOT EXISTS pet_attributes ( + id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + pet_id INT UNSIGNED NOT NULL, + temperament VARCHAR(100), + length_cm DECIMAL(5,2), + weight_kg DECIMAL(5,2), + additional_attributes JSON, + FOREIGN KEY (pet_id) REFERENCES pets(id) + ); diff --git a/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java b/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java index d67e449dc..4c388bfa5 100644 --- a/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java +++ b/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java @@ -29,7 +29,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.HttpStatus; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; -import org.springframework.samples.petclinic.vet.VetRepository; +import org.springframework.samples.petclinic.vet.repository.VetRepository; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.aot.DisabledInAotMode; import org.springframework.web.client.RestTemplate; diff --git a/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java b/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java index 6d9820696..ba587b1fe 100644 --- a/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java +++ b/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java @@ -28,7 +28,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.HttpStatus; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; -import org.springframework.samples.petclinic.vet.VetRepository; +import org.springframework.samples.petclinic.vet.repository.VetRepository; import org.springframework.web.client.RestTemplate; @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) diff --git a/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java b/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java index 0b9e4f93a..9a1af03cd 100644 --- a/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java +++ b/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java @@ -43,7 +43,7 @@ import org.springframework.core.env.PropertySource; import org.springframework.http.HttpStatus; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; -import org.springframework.samples.petclinic.vet.VetRepository; +import org.springframework.samples.petclinic.vet.repository.VetRepository; import org.springframework.test.context.ActiveProfiles; import org.springframework.web.client.RestTemplate; import org.testcontainers.DockerClientFactory; diff --git a/src/test/java/org/springframework/samples/petclinic/model/ValidatorTests.java b/src/test/java/org/springframework/samples/petclinic/model/ValidatorTests.java index c75328522..70d347a6b 100644 --- a/src/test/java/org/springframework/samples/petclinic/model/ValidatorTests.java +++ b/src/test/java/org/springframework/samples/petclinic/model/ValidatorTests.java @@ -23,6 +23,7 @@ import java.util.Set; import org.junit.jupiter.api.Test; import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.samples.petclinic.common.model.Person; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import jakarta.validation.ConstraintViolation; diff --git a/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java b/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java index 46e7e87ce..bdaea7fa2 100644 --- a/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/owner/OwnerControllerTests.java @@ -24,6 +24,12 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; +import org.springframework.samples.petclinic.owner.controller.OwnerController; +import org.springframework.samples.petclinic.owner.model.Owner; +import org.springframework.samples.petclinic.owner.model.Pet; +import org.springframework.samples.petclinic.owner.model.PetType; +import org.springframework.samples.petclinic.owner.model.Visit; +import org.springframework.samples.petclinic.owner.repository.OwnerRepository; import org.springframework.test.context.aot.DisabledInAotMode; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; diff --git a/src/test/java/org/springframework/samples/petclinic/owner/PetAttributesControllerTest.java b/src/test/java/org/springframework/samples/petclinic/owner/PetAttributesControllerTest.java new file mode 100644 index 000000000..cbaea8dac --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/owner/PetAttributesControllerTest.java @@ -0,0 +1,139 @@ +package org.springframework.samples.petclinic.owner; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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.samples.petclinic.owner.controller.PetAttributesController; +import org.springframework.samples.petclinic.owner.dto.PetAttributesDTO; +import org.springframework.samples.petclinic.owner.expection.PetNotFoundException; +import org.springframework.samples.petclinic.owner.model.PetAttributes; +import org.springframework.samples.petclinic.owner.repository.PetRepository; +import org.springframework.samples.petclinic.owner.service.PetAttributesService; +import org.springframework.samples.petclinic.owner.validation.PetIdExistsValidator; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.validation.BindingResult; + +import java.math.BigDecimal; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(PetAttributesController.class) +public class PetAttributesControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private PetAttributesService petAttributesService; + + @MockBean + private PetIdExistsValidator petIdExistsValidator; + + @MockBean + private PetRepository petRepository; + + @Autowired + private ObjectMapper objectMapper; + + @Test + void testGetPetAttributes_Found() throws Exception { + int petId = 1; + PetAttributes attributes = new PetAttributes(); + + Mockito.when(petRepository.existsById(petId)).thenReturn(true); + Mockito.when(petAttributesService.findByPetId(petId)).thenReturn(Optional.of(attributes)); + + mockMvc.perform(get("/pets/{petId}/attributes", petId)).andExpect(status().isOk()); + } + + @Test + void testGetPetAttributes_PetNotFound() throws Exception { + int petId = 1; + + Mockito.when(petRepository.existsById(petId)).thenReturn(false); + + mockMvc.perform(get("/pets/{petId}/attributes", petId)) + .andExpect(status().isNotFound()) + .andExpect(content().string("Pet not found")); + } + + @Test + void testGetPetAttributes_AttributesNotFound() throws Exception { + int petId = 1; + + Mockito.when(petRepository.existsById(petId)).thenReturn(true); + Mockito.when(petAttributesService.findByPetId(petId)).thenReturn(Optional.empty()); + + mockMvc.perform(get("/pets/{petId}/attributes", petId)) + .andExpect(status().isNotFound()) + .andExpect(content().string("Attributes not found")); + } + + @Test + void testSavePetAttributes_Success() throws Exception { + int petId = 1; + PetAttributesDTO dto = new PetAttributesDTO(); + dto.setTemperament("Calm"); + dto.setWeightKg(BigDecimal.valueOf(12.5)); + dto.setLengthCm(BigDecimal.valueOf(60.0)); + + Mockito.doNothing().when(petIdExistsValidator).validate(eq(petId), any(BindingResult.class)); + Mockito.doNothing().when(petAttributesService).savePetAttributes(any(PetAttributesDTO.class)); + + mockMvc + .perform(post("/pets/{petId}/attributes", petId).contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto))) + .andExpect(status().isCreated()) + .andExpect(content().string("Pet attributes saved")); + } + + @Test + void testSavePetAttributes_PetNotFoundException() throws Exception { + int petId = 1; + PetAttributesDTO dto = new PetAttributesDTO(); + dto.setTemperament("Aggressive"); + dto.setWeightKg(BigDecimal.valueOf(12.5)); + dto.setLengthCm(BigDecimal.valueOf(60.0)); + + Mockito.doNothing().when(petIdExistsValidator).validate(eq(petId), any(BindingResult.class)); + Mockito.doThrow(new PetNotFoundException("Pet not found")) + .when(petAttributesService) + .savePetAttributes(any(PetAttributesDTO.class)); + + mockMvc + .perform(post("/pets/{petId}/attributes", petId).contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto))) + .andExpect(status().isNotFound()) + .andExpect(content().string("Pet not found")); + } + + @Test + void testSavePetAttributes_ValidationErrors() throws Exception { + int petId = 1; + PetAttributesDTO dto = new PetAttributesDTO(); + dto.setTemperament("Friendly"); + dto.setWeightKg(BigDecimal.valueOf(10.0)); + dto.setLengthCm(BigDecimal.valueOf(50.0)); + + // Simulate petId validation failure + Mockito.doAnswer(invocation -> { + BindingResult result = invocation.getArgument(1); + result.reject("petId", "Validation failed"); + return null; + }).when(petIdExistsValidator).validate(eq(petId), any(BindingResult.class)); + + mockMvc + .perform(post("/pets/{petId}/attributes", petId).contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$[0].code").value("petId")); + } + +} diff --git a/src/test/java/org/springframework/samples/petclinic/owner/PetControllerTests.java b/src/test/java/org/springframework/samples/petclinic/owner/PetControllerTests.java index 6fc9a849e..e5c8e2cc3 100644 --- a/src/test/java/org/springframework/samples/petclinic/owner/PetControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/owner/PetControllerTests.java @@ -24,6 +24,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; +import org.springframework.samples.petclinic.common.formatter.PetTypeFormatter; +import org.springframework.samples.petclinic.owner.controller.PetController; +import org.springframework.samples.petclinic.owner.controller.PetController; +import org.springframework.samples.petclinic.owner.model.Owner; +import org.springframework.samples.petclinic.owner.model.Pet; +import org.springframework.samples.petclinic.owner.model.PetType; +import org.springframework.samples.petclinic.owner.repository.OwnerRepository; import org.springframework.test.context.aot.DisabledInAotMode; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; diff --git a/src/test/java/org/springframework/samples/petclinic/owner/PetTypeFormatterTests.java b/src/test/java/org/springframework/samples/petclinic/owner/PetTypeFormatterTests.java index 0295b4788..8c893c87e 100644 --- a/src/test/java/org/springframework/samples/petclinic/owner/PetTypeFormatterTests.java +++ b/src/test/java/org/springframework/samples/petclinic/owner/PetTypeFormatterTests.java @@ -32,6 +32,9 @@ import org.junit.jupiter.api.condition.DisabledInNativeImage; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.samples.petclinic.common.formatter.PetTypeFormatter; +import org.springframework.samples.petclinic.owner.model.PetType; +import org.springframework.samples.petclinic.owner.repository.OwnerRepository; /** * Test class for {@link PetTypeFormatter} diff --git a/src/test/java/org/springframework/samples/petclinic/owner/PetValidatorTests.java b/src/test/java/org/springframework/samples/petclinic/owner/PetValidatorTests.java index 1a153bcbc..4edcbf157 100644 --- a/src/test/java/org/springframework/samples/petclinic/owner/PetValidatorTests.java +++ b/src/test/java/org/springframework/samples/petclinic/owner/PetValidatorTests.java @@ -22,6 +22,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledInNativeImage; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.samples.petclinic.owner.model.Pet; +import org.springframework.samples.petclinic.owner.model.PetType; +import org.springframework.samples.petclinic.owner.validation.PetValidator; import org.springframework.validation.Errors; import org.springframework.validation.MapBindingResult; diff --git a/src/test/java/org/springframework/samples/petclinic/owner/VisitControllerTests.java b/src/test/java/org/springframework/samples/petclinic/owner/VisitControllerTests.java index e42e75034..20ea2f26a 100644 --- a/src/test/java/org/springframework/samples/petclinic/owner/VisitControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/owner/VisitControllerTests.java @@ -28,6 +28,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledInNativeImage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.samples.petclinic.owner.controller.VisitController; +import org.springframework.samples.petclinic.owner.model.Owner; +import org.springframework.samples.petclinic.owner.model.Pet; +import org.springframework.samples.petclinic.owner.repository.OwnerRepository; import org.springframework.test.context.aot.DisabledInAotMode; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; diff --git a/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java b/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java index 17360278f..6d2084e00 100644 --- a/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java +++ b/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java @@ -29,13 +29,13 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabas import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.samples.petclinic.owner.Owner; -import org.springframework.samples.petclinic.owner.OwnerRepository; -import org.springframework.samples.petclinic.owner.Pet; -import org.springframework.samples.petclinic.owner.PetType; -import org.springframework.samples.petclinic.owner.Visit; -import org.springframework.samples.petclinic.vet.Vet; -import org.springframework.samples.petclinic.vet.VetRepository; +import org.springframework.samples.petclinic.owner.model.Owner; +import org.springframework.samples.petclinic.owner.repository.OwnerRepository; +import org.springframework.samples.petclinic.owner.model.Pet; +import org.springframework.samples.petclinic.owner.model.PetType; +import org.springframework.samples.petclinic.owner.model.Visit; +import org.springframework.samples.petclinic.vet.model.Vet; +import org.springframework.samples.petclinic.vet.repository.VetRepository; import org.springframework.transaction.annotation.Transactional; /** diff --git a/src/test/java/org/springframework/samples/petclinic/service/EntityUtils.java b/src/test/java/org/springframework/samples/petclinic/service/EntityUtils.java index 7b7a5e64a..accedcd74 100644 --- a/src/test/java/org/springframework/samples/petclinic/service/EntityUtils.java +++ b/src/test/java/org/springframework/samples/petclinic/service/EntityUtils.java @@ -17,7 +17,7 @@ package org.springframework.samples.petclinic.service; import org.springframework.orm.ObjectRetrievalFailureException; -import org.springframework.samples.petclinic.model.BaseEntity; +import org.springframework.samples.petclinic.common.model.BaseEntity; import java.util.Collection; @@ -27,7 +27,7 @@ import java.util.Collection; * * @author Juergen Hoeller * @author Sam Brannen - * @see org.springframework.samples.petclinic.model.BaseEntity + * @see BaseEntity * @since 29.10.2003 */ public abstract class EntityUtils { diff --git a/src/test/java/org/springframework/samples/petclinic/service/PetAttributesServiceTest.java b/src/test/java/org/springframework/samples/petclinic/service/PetAttributesServiceTest.java new file mode 100644 index 000000000..4350b8d79 --- /dev/null +++ b/src/test/java/org/springframework/samples/petclinic/service/PetAttributesServiceTest.java @@ -0,0 +1,119 @@ +package org.springframework.samples.petclinic.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.*; +import org.springframework.samples.petclinic.owner.dto.PetAttributesDTO; +import org.springframework.samples.petclinic.owner.expection.PetNotFoundException; +import org.springframework.samples.petclinic.owner.model.Pet; +import org.springframework.samples.petclinic.owner.model.PetAttributes; +import org.springframework.samples.petclinic.owner.repository.PetAttributesRepository; +import org.springframework.samples.petclinic.owner.repository.PetRepository; +import org.springframework.samples.petclinic.owner.service.PetAttributesService; + +import java.math.BigDecimal; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class PetAttributesServiceTest { + + @Mock + private PetAttributesRepository petAttributesRepository; + + @Mock + private PetRepository petRepository; + + @InjectMocks + private PetAttributesService petAttributesService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void testFindByPetId_ReturnsAttributes() { + PetAttributes attributes = new PetAttributes(); + when(petAttributesRepository.findByPetId(1)).thenReturn(Optional.of(attributes)); + Optional result = petAttributesService.findByPetId(1); + assertTrue(result.isPresent()); + assertEquals(attributes, result.get()); + } + + @Test + void testFindByPetId_ReturnsEmpty() { + when(petAttributesRepository.findByPetId(1)).thenReturn(Optional.empty()); + Optional result = petAttributesService.findByPetId(1); + assertFalse(result.isPresent()); + } + + @Test + void testSavePetAttributes_Success_NewRecord() { + Pet pet = new Pet(); + pet.setId(1); + + PetAttributesDTO dto = new PetAttributesDTO(); + dto.setPetId(1); + dto.setTemperament("Calm"); + dto.setLengthCm(BigDecimal.valueOf(40.0)); + dto.setWeightKg(BigDecimal.valueOf(8.5)); + + when(petRepository.findById(1)).thenReturn(Optional.of(pet)); + when(petAttributesRepository.findByPetId(1)).thenReturn(Optional.empty()); + + petAttributesService.savePetAttributes(dto); + + // Expect a new PetAttributes to be created and saved + ArgumentCaptor captor = ArgumentCaptor.forClass(PetAttributes.class); + verify(petAttributesRepository).save(captor.capture()); + + PetAttributes saved = captor.getValue(); + assertEquals("Calm", saved.getTemperament()); + assertEquals(BigDecimal.valueOf(40.0), saved.getLengthCm()); + assertEquals(BigDecimal.valueOf(8.5), saved.getWeightKg()); + assertEquals(pet, saved.getPet()); + } + + @Test + void testSavePetAttributes_Success_UpdateExisting() { + Pet pet = new Pet(); + pet.setId(1); + + PetAttributes existing = new PetAttributes(); + existing.setPet(pet); + + PetAttributesDTO dto = new PetAttributesDTO(); + dto.setPetId(1); + dto.setTemperament("Playful"); + dto.setLengthCm(BigDecimal.valueOf(45.0)); + dto.setWeightKg(BigDecimal.valueOf(9.0)); + + when(petRepository.findById(1)).thenReturn(Optional.of(pet)); + when(petAttributesRepository.findByPetId(1)).thenReturn(Optional.of(existing)); + + petAttributesService.savePetAttributes(dto); + + verify(petAttributesRepository).save(existing); + assertEquals("Playful", existing.getTemperament()); + assertEquals(BigDecimal.valueOf(45.0), existing.getLengthCm()); + assertEquals(BigDecimal.valueOf(9.0), existing.getWeightKg()); + } + + @Test + void testSavePetAttributes_PetNotFound_ThrowsException() { + PetAttributesDTO dto = new PetAttributesDTO(); + dto.setPetId(1); + + when(petRepository.findById(1)).thenReturn(Optional.empty()); + + PetNotFoundException ex = assertThrows(PetNotFoundException.class, () -> { + petAttributesService.savePetAttributes(dto); + }); + + assertEquals("Pet not found", ex.getMessage()); + verify(petAttributesRepository, never()).save(any()); + } + +} diff --git a/src/test/java/org/springframework/samples/petclinic/system/CrashControllerIntegrationTests.java b/src/test/java/org/springframework/samples/petclinic/system/CrashControllerIntegrationTests.java index 3cdca47bf..f3de0c0ba 100644 --- a/src/test/java/org/springframework/samples/petclinic/system/CrashControllerIntegrationTests.java +++ b/src/test/java/org/springframework/samples/petclinic/system/CrashControllerIntegrationTests.java @@ -41,7 +41,8 @@ import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; /** - * Integration Test for {@link CrashController}. + * Integration Test for + * {@link org.springframework.samples.petclinic.system.controller.CrashController}. * * @author Alex Lutz */ diff --git a/src/test/java/org/springframework/samples/petclinic/system/CrashControllerTests.java b/src/test/java/org/springframework/samples/petclinic/system/CrashControllerTests.java index cc2ad6746..7e3f19e08 100644 --- a/src/test/java/org/springframework/samples/petclinic/system/CrashControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/system/CrashControllerTests.java @@ -19,9 +19,11 @@ package org.springframework.samples.petclinic.system; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import org.springframework.samples.petclinic.system.controller.CrashController; /** - * Test class for {@link CrashController} + * Test class for + * {@link org.springframework.samples.petclinic.system.controller.CrashController} * * @author Colin But * @author Alex Lutz diff --git a/src/test/java/org/springframework/samples/petclinic/vet/VetControllerTests.java b/src/test/java/org/springframework/samples/petclinic/vet/VetControllerTests.java index 5fffeea47..54ac38311 100644 --- a/src/test/java/org/springframework/samples/petclinic/vet/VetControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/vet/VetControllerTests.java @@ -25,6 +25,10 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.http.MediaType; +import org.springframework.samples.petclinic.vet.controller.VetController; +import org.springframework.samples.petclinic.vet.model.Specialty; +import org.springframework.samples.petclinic.vet.model.Vet; +import org.springframework.samples.petclinic.vet.repository.VetRepository; import org.springframework.test.context.aot.DisabledInAotMode; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; diff --git a/src/test/java/org/springframework/samples/petclinic/vet/VetTests.java b/src/test/java/org/springframework/samples/petclinic/vet/VetTests.java index b29c6d100..2bdc79ab8 100644 --- a/src/test/java/org/springframework/samples/petclinic/vet/VetTests.java +++ b/src/test/java/org/springframework/samples/petclinic/vet/VetTests.java @@ -16,6 +16,7 @@ package org.springframework.samples.petclinic.vet; import org.junit.jupiter.api.Test; +import org.springframework.samples.petclinic.vet.model.Vet; import org.springframework.util.SerializationUtils; import static org.assertj.core.api.Assertions.assertThat;