mirror of
https://github.com/spring-projects/spring-petclinic.git
synced 2025-07-16 12:45:48 +00:00
persistent_code_challenge
This commit is contained in:
parent
30aab0ae76
commit
0ad7b66308
21 changed files with 695 additions and 4 deletions
51
README.md
51
README.md
|
@ -31,6 +31,57 @@ Or you can run it from Maven directly using the Spring Boot Maven plugin. If you
|
|||
|
||||
> NOTE: If you prefer to use Gradle, you can build the app using `./gradlew build` and look for the jar file in `build/libs`.
|
||||
|
||||
## 🔧 New Features Added
|
||||
|
||||
### ✅ PetAttribute Module
|
||||
|
||||
- Added a new model `PetAttribute` linked to `PetType` to capture details like temperament or breed.
|
||||
- REST endpoints:
|
||||
- `POST /api/pettypes/{petTypeId}/attributes` – Add attribute
|
||||
- `GET /api/pettypes/{petTypeId}/attributes` – Get attributes by pet type
|
||||
|
||||
### ⚡ gRPC Integration
|
||||
|
||||
- Introduced gRPC support using the proto definition `pet-attribute.proto`.
|
||||
- Sample proto file:
|
||||
```proto
|
||||
syntax = "proto3";
|
||||
option java_multiple_files = true;
|
||||
option java_package = "org.springframework.samples.petclinic.grpc";
|
||||
option java_outer_classname = "PetAttributeProto";
|
||||
|
||||
service PetAttributeService {
|
||||
rpc GetAttributes(PetAttributeRequest) returns (PetAttributeList);
|
||||
rpc AddAttribute(NewPetAttribute) returns (PetAttributeResponse);
|
||||
}
|
||||
|
||||
### ⚙️ gRPC Java Classes
|
||||
|
||||
- Auto-generated under:
|
||||
|
||||
target/generated-sources/grpc
|
||||
|
||||
|
||||
### 🔗 Spring HATEOAS Support
|
||||
|
||||
Hypermedia links are now included in `PetAttribute` responses.
|
||||
|
||||
**Example Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"temperament": "Energetic",
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "http://localhost:8080/api/pettypes/1/attributes/3"
|
||||
},
|
||||
"petType": {
|
||||
"href": "http://localhost:8080/api/pettypes/1"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Building a Container
|
||||
|
||||
There is no `Dockerfile` in this project. You can build a container image (if you have a docker daemon) using the Spring Boot build plugin:
|
||||
|
|
57
pom.xml
57
pom.xml
|
@ -36,7 +36,7 @@
|
|||
<maven-checkstyle.version>3.6.0</maven-checkstyle.version>
|
||||
<nohttp-checkstyle.version>0.0.11</nohttp-checkstyle.version>
|
||||
<spring-format.version>0.0.46</spring-format.version>
|
||||
|
||||
<os.detected.classifier>windows-x86_64</os.detected.classifier>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
@ -146,10 +146,65 @@
|
|||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/net.devh/grpc-server-spring-boot-starter -->
|
||||
<dependency>
|
||||
<groupId>net.devh</groupId>
|
||||
<artifactId>grpc-server-spring-boot-starter</artifactId>
|
||||
<version>3.1.0.RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-hateoas</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
<extensions>
|
||||
<extension>
|
||||
<groupId>kr.motd.maven</groupId>
|
||||
<artifactId>os-maven-plugin</artifactId>
|
||||
<version>1.6.2</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.xolstice.maven.plugins</groupId>
|
||||
<artifactId>protobuf-maven-plugin</artifactId>
|
||||
<version>0.6.1</version>
|
||||
<configuration>
|
||||
<protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
|
||||
<protocArtifact>com.google.protobuf:protoc:3.21.12:exe:${os.detected.classifier}</protocArtifact>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>compile-grpc</id>
|
||||
<goals>
|
||||
<goal>compile-custom</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<pluginId>grpc-java</pluginId>
|
||||
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.58.0:exe:${os.detected.classifier}</pluginArtifact>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
|
|
|
@ -15,10 +15,15 @@
|
|||
*/
|
||||
package org.springframework.samples.petclinic.owner;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import org.springframework.samples.petclinic.model.NamedEntity;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import org.springframework.samples.petclinic.pet.model.PetAttribute;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller Can be Cat, Dog, Hamster...
|
||||
|
@ -27,4 +32,7 @@ import jakarta.persistence.Table;
|
|||
@Table(name = "types")
|
||||
public class PetType extends NamedEntity {
|
||||
|
||||
@OneToMany(mappedBy = "petType", cascade = CascadeType.ALL)
|
||||
private Set<PetAttribute> attributes;
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package org.springframework.samples.petclinic.pet.controller;
|
||||
|
||||
import org.springframework.hateoas.CollectionModel;
|
||||
import org.springframework.hateoas.EntityModel;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.samples.petclinic.owner.PetType;
|
||||
import org.springframework.samples.petclinic.pet.model.PetAttribute;
|
||||
import org.springframework.samples.petclinic.pet.service.PetAttributeService;
|
||||
import org.springframework.samples.petclinic.pet.service.PetTypeService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
|
||||
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
|
||||
|
||||
/**
|
||||
* @author Rohit Lalwani
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/petTypes/{typeId}/attributes")
|
||||
public class PetAttributeController {
|
||||
|
||||
private final PetTypeService petTypeService;
|
||||
|
||||
private final PetAttributeService petAttributeService;
|
||||
|
||||
private final PetAttributeModelAssembler assembler;
|
||||
|
||||
public PetAttributeController(PetTypeService petTypeService, PetAttributeService attrService,
|
||||
PetAttributeModelAssembler assembler) {
|
||||
this.petTypeService = petTypeService;
|
||||
this.petAttributeService = attrService;
|
||||
this.assembler = assembler;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<EntityModel<PetAttribute>> createAttribute(@PathVariable Integer typeId,
|
||||
@RequestBody PetAttribute attr) {
|
||||
PetType type = petTypeService.findPetTypeById(typeId);
|
||||
if (type == null)
|
||||
return ResponseEntity.notFound().build();
|
||||
|
||||
attr.setPetType(type);
|
||||
PetAttribute saved = petAttributeService.save(attr);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(assembler.toModel(saved));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<CollectionModel<EntityModel<PetAttribute>>> getAttributes(@PathVariable Integer typeId) {
|
||||
List<PetAttribute> attrs = petAttributeService.findByPetTypeId(typeId);
|
||||
|
||||
List<EntityModel<PetAttribute>> attrModels = attrs.stream().map(assembler::toModel).toList();
|
||||
|
||||
return ResponseEntity.ok(CollectionModel.of(attrModels,
|
||||
linkTo(methodOn(PetAttributeController.class).getAttributes(typeId)).withSelfRel()));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package org.springframework.samples.petclinic.pet.controller;
|
||||
|
||||
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
|
||||
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
|
||||
|
||||
import org.springframework.hateoas.EntityModel;
|
||||
import org.springframework.hateoas.server.RepresentationModelAssembler;
|
||||
import org.springframework.samples.petclinic.pet.model.PetAttribute;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Rohit Lalwani
|
||||
*/
|
||||
@Component
|
||||
public class PetAttributeModelAssembler
|
||||
implements RepresentationModelAssembler<PetAttribute, EntityModel<PetAttribute>> {
|
||||
|
||||
@Override
|
||||
public EntityModel<PetAttribute> toModel(PetAttribute attr) {
|
||||
Integer typeId = attr.getPetType().getId();
|
||||
return EntityModel.of(attr,
|
||||
linkTo(methodOn(PetAttributeController.class).getAttributes(typeId)).withRel("allAttributes"),
|
||||
linkTo(methodOn(PetAttributeController.class).createAttribute(typeId, attr))
|
||||
.withRel("createAttribute"));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package org.springframework.samples.petclinic.pet.grpc;
|
||||
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import net.devh.boot.grpc.server.service.GrpcService;
|
||||
import org.springframework.samples.petclinic.grpc.NewPetAttribute;
|
||||
import org.springframework.samples.petclinic.grpc.PetAttributeList;
|
||||
import org.springframework.samples.petclinic.grpc.PetAttributeRequest;
|
||||
import org.springframework.samples.petclinic.grpc.PetAttributeResponse;
|
||||
import org.springframework.samples.petclinic.grpc.PetAttributeServiceGrpc;
|
||||
import org.springframework.samples.petclinic.owner.PetType;
|
||||
import org.springframework.samples.petclinic.pet.model.PetAttribute;
|
||||
import org.springframework.samples.petclinic.pet.service.PetAttributeService;
|
||||
import org.springframework.samples.petclinic.pet.service.PetTypeService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Rohit Lalwani
|
||||
*/
|
||||
@GrpcService
|
||||
public class PetAttributeGrpcService extends PetAttributeServiceGrpc.PetAttributeServiceImplBase {
|
||||
|
||||
private final PetAttributeService petAttributeService;
|
||||
|
||||
private final PetTypeService petTypeService;
|
||||
|
||||
public PetAttributeGrpcService(PetAttributeService petAttributeService, PetTypeService petTypeService) {
|
||||
this.petAttributeService = petAttributeService;
|
||||
this.petTypeService = petTypeService;
|
||||
}
|
||||
|
||||
public void GetAttributes(PetAttributeRequest request, StreamObserver<PetAttributeList> responseObserver) {
|
||||
List<PetAttribute> attributes = petAttributeService.findByPetTypeId(request.getTypeId());
|
||||
|
||||
PetAttributeList.Builder listBuilder = PetAttributeList.newBuilder();
|
||||
for (PetAttribute attr : attributes) {
|
||||
PetAttributeResponse response = PetAttributeResponse.newBuilder()
|
||||
.setId(attr.getId())
|
||||
.setTemperament(attr.getTemperament())
|
||||
.setWeight(attr.getWeight())
|
||||
.setLength(attr.getLength())
|
||||
.build();
|
||||
listBuilder.addAttributes(response);
|
||||
}
|
||||
|
||||
responseObserver.onNext(listBuilder.build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
|
||||
public void AddAttribute(NewPetAttribute request, StreamObserver<PetAttributeResponse> responseObserver) {
|
||||
PetType petType = petTypeService.findPetTypeById(request.getTypeId());
|
||||
if (petType == null) {
|
||||
responseObserver.onError(new IllegalArgumentException("Pet type not found"));
|
||||
return;
|
||||
}
|
||||
|
||||
PetAttribute attr = new PetAttribute();
|
||||
attr.setTemperament(request.getTemperament());
|
||||
attr.setWeight(request.getWeight());
|
||||
attr.setLength(request.getLength());
|
||||
attr.setPetType(petType);
|
||||
|
||||
PetAttribute saved = petAttributeService.save(attr);
|
||||
|
||||
PetAttributeResponse response = PetAttributeResponse.newBuilder()
|
||||
.setId(saved.getId())
|
||||
.setTemperament(saved.getTemperament())
|
||||
.setWeight(saved.getWeight())
|
||||
.setLength(saved.getLength())
|
||||
.build();
|
||||
|
||||
responseObserver.onNext(response);
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package org.springframework.samples.petclinic.pet.model;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.springframework.samples.petclinic.owner.PetType;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author Rohit Lalwani
|
||||
*/
|
||||
@Entity
|
||||
public class PetAttribute implements Serializable {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Integer id;
|
||||
|
||||
@NotNull
|
||||
private String temperament;
|
||||
|
||||
@NotNull
|
||||
private Double weight;
|
||||
|
||||
@NotNull
|
||||
private Double length;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "type_id")
|
||||
private PetType petType;
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTemperament() {
|
||||
return temperament;
|
||||
}
|
||||
|
||||
public void setTemperament(String temperament) {
|
||||
this.temperament = temperament;
|
||||
}
|
||||
|
||||
public Double getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
public void setWeight(Double weight) {
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public Double getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void setLength(Double length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public PetType getPetType() {
|
||||
return petType;
|
||||
}
|
||||
|
||||
public void setPetType(PetType petType) {
|
||||
this.petType = petType;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package org.springframework.samples.petclinic.pet.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.samples.petclinic.pet.model.PetAttribute;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Rohit Lalwani
|
||||
*/
|
||||
public interface PetAttributeRepository extends CrudRepository<PetAttribute, Integer> {
|
||||
|
||||
@EntityGraph(attributePaths = "petType")
|
||||
List<PetAttribute> findByPetTypeId(Integer typeId);
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package org.springframework.samples.petclinic.pet.service;
|
||||
|
||||
import org.springframework.samples.petclinic.pet.model.PetAttribute;
|
||||
import org.springframework.samples.petclinic.pet.repository.PetAttributeRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Rohit Lalwani
|
||||
*/
|
||||
@Service
|
||||
public class PetAttributeService {
|
||||
|
||||
private final PetAttributeRepository petAttributeRepository;
|
||||
|
||||
public PetAttributeService(PetAttributeRepository repo) {
|
||||
this.petAttributeRepository = repo;
|
||||
}
|
||||
|
||||
public PetAttribute save(PetAttribute attr) {
|
||||
return petAttributeRepository.save(attr);
|
||||
}
|
||||
|
||||
public List<PetAttribute> findByPetTypeId(Integer petTypeId) {
|
||||
return petAttributeRepository.findByPetTypeId(petTypeId);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package org.springframework.samples.petclinic.pet.service;
|
||||
|
||||
import org.springframework.samples.petclinic.owner.PetType;
|
||||
import org.springframework.samples.petclinic.owner.PetTypeRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author Rohit Lalwani
|
||||
*/
|
||||
@Service
|
||||
public class PetTypeService {
|
||||
|
||||
private final PetTypeRepository petTypeRepository;
|
||||
|
||||
public PetTypeService(PetTypeRepository petTypeRepository) {
|
||||
this.petTypeRepository = petTypeRepository;
|
||||
}
|
||||
|
||||
public PetType findPetTypeById(Integer id) {
|
||||
Optional<PetType> petType = petTypeRepository.findById(id);
|
||||
return petType.orElse(null);
|
||||
}
|
||||
|
||||
}
|
32
src/main/proto/petattribute.proto
Normal file
32
src/main/proto/petattribute.proto
Normal file
|
@ -0,0 +1,32 @@
|
|||
syntax = "proto3";
|
||||
|
||||
option java_multiple_files = true;
|
||||
option java_package = "org.springframework.samples.petclinic.grpc";
|
||||
option java_outer_classname = "PetAttributeProto";
|
||||
|
||||
message PetAttributeRequest {
|
||||
int32 typeId = 1;
|
||||
}
|
||||
|
||||
message NewPetAttribute {
|
||||
int32 typeId = 1;
|
||||
string temperament = 2;
|
||||
double weight = 3;
|
||||
double length = 4;
|
||||
}
|
||||
|
||||
message PetAttributeResponse {
|
||||
int32 id = 1;
|
||||
string temperament = 2;
|
||||
double weight = 3;
|
||||
double length = 4;
|
||||
}
|
||||
|
||||
message PetAttributeList {
|
||||
repeated PetAttributeResponse attributes = 1;
|
||||
}
|
||||
|
||||
service PetAttributeService {
|
||||
rpc GetAttributes (PetAttributeRequest) returns (PetAttributeList);
|
||||
rpc AddAttribute (NewPetAttribute) returns (PetAttributeResponse);
|
||||
}
|
|
@ -23,3 +23,5 @@ logging.level.org.springframework=INFO
|
|||
|
||||
# Maximum time static resources should be cached
|
||||
spring.web.resources.cache.cachecontrol.max-age=12h
|
||||
|
||||
grpc.server.port=9090
|
||||
|
|
|
@ -62,3 +62,12 @@ CREATE TABLE visits (
|
|||
);
|
||||
ALTER TABLE visits ADD CONSTRAINT fk_visits_pets FOREIGN KEY (pet_id) REFERENCES pets (id);
|
||||
CREATE INDEX visits_pet_id ON visits (pet_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pet_attribute (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
temperament VARCHAR(50) NOT NULL,
|
||||
weight DOUBLE NOT NULL,
|
||||
length DOUBLE NOT NULL,
|
||||
type_id INT,
|
||||
FOREIGN KEY (type_id) REFERENCES types(id)
|
||||
);
|
||||
|
|
|
@ -62,3 +62,12 @@ CREATE TABLE visits (
|
|||
);
|
||||
ALTER TABLE visits ADD CONSTRAINT fk_visits_pets FOREIGN KEY (pet_id) REFERENCES pets (id);
|
||||
CREATE INDEX visits_pet_id ON visits (pet_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pet_attribute (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
temperament VARCHAR(50) NOT NULL,
|
||||
weight DOUBLE NOT NULL,
|
||||
length DOUBLE NOT NULL,
|
||||
type_id INT,
|
||||
FOREIGN KEY (type_id) REFERENCES types(id)
|
||||
);
|
||||
|
|
|
@ -53,3 +53,12 @@ CREATE TABLE IF NOT EXISTS visits (
|
|||
description VARCHAR(255),
|
||||
FOREIGN KEY (pet_id) REFERENCES pets(id)
|
||||
) engine=InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pet_attribute (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
temperament VARCHAR(50) NOT NULL,
|
||||
weight DOUBLE NOT NULL,
|
||||
length DOUBLE NOT NULL,
|
||||
type_id INT,
|
||||
FOREIGN KEY (type_id) REFERENCES types(id)
|
||||
) engine=InnoDB;
|
||||
|
|
|
@ -50,3 +50,12 @@ CREATE TABLE IF NOT EXISTS visits (
|
|||
description TEXT
|
||||
);
|
||||
CREATE INDEX ON visits (pet_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pet_attribute (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
temperament VARCHAR(50) NOT NULL,
|
||||
weight DOUBLE NOT NULL,
|
||||
length DOUBLE NOT NULL,
|
||||
type_id INT,
|
||||
FOREIGN KEY (type_id) REFERENCES types(id)
|
||||
);
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
package org.springframework.samples.petclinic.pet.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.hateoas.CollectionModel;
|
||||
import org.springframework.hateoas.EntityModel;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.samples.petclinic.owner.PetType;
|
||||
import org.springframework.samples.petclinic.pet.model.PetAttribute;
|
||||
import org.springframework.samples.petclinic.pet.service.PetAttributeService;
|
||||
import org.springframework.samples.petclinic.pet.service.PetTypeService;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PetAttributeControllerTest {
|
||||
|
||||
@Mock
|
||||
private PetTypeService petTypeService;
|
||||
|
||||
@Mock
|
||||
private PetAttributeService petAttributeService;
|
||||
|
||||
@Mock
|
||||
private PetAttributeModelAssembler assembler;
|
||||
|
||||
@InjectMocks
|
||||
private PetAttributeController controller;
|
||||
|
||||
@Test
|
||||
void testCreateAttribute_returnsCreated() {
|
||||
PetType type = new PetType();
|
||||
PetAttribute attr = new PetAttribute();
|
||||
attr.setTemperament("Energetic");
|
||||
|
||||
when(petTypeService.findPetTypeById(1)).thenReturn(type);
|
||||
when(petAttributeService.save(attr)).thenReturn(attr);
|
||||
when(assembler.toModel(attr)).thenReturn(EntityModel.of(attr));
|
||||
|
||||
ResponseEntity<EntityModel<PetAttribute>> response = controller.createAttribute(1, attr);
|
||||
|
||||
assertEquals(201, response.getStatusCode().value());
|
||||
assertNotNull(response.getBody());
|
||||
assertEquals("Energetic", Objects.requireNonNull(response.getBody().getContent()).getTemperament());
|
||||
verify(petAttributeService).save(attr);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateAttribute_petTypeNotFound() {
|
||||
PetAttribute attr = new PetAttribute();
|
||||
|
||||
when(petTypeService.findPetTypeById(999)).thenReturn(null);
|
||||
|
||||
ResponseEntity<EntityModel<PetAttribute>> response = controller.createAttribute(999, attr);
|
||||
|
||||
assertEquals(404, response.getStatusCode().value());
|
||||
assertNull(response.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAttributes_returnsList() {
|
||||
PetAttribute attr = new PetAttribute();
|
||||
attr.setTemperament("Curious");
|
||||
|
||||
List<PetAttribute> attributes = Collections.singletonList(attr);
|
||||
EntityModel<PetAttribute> model = EntityModel.of(attr);
|
||||
|
||||
when(petAttributeService.findByPetTypeId(1)).thenReturn(attributes);
|
||||
when(assembler.toModel(attr)).thenReturn(model);
|
||||
|
||||
ResponseEntity<CollectionModel<EntityModel<PetAttribute>>> response = controller.getAttributes(1);
|
||||
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
assertNotNull(response.getBody());
|
||||
assertEquals(1, response.getBody().getContent().size());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package org.springframework.samples.petclinic.pet.service;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.samples.petclinic.pet.model.PetAttribute;
|
||||
import org.springframework.samples.petclinic.pet.repository.PetAttributeRepository;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PetAttributeServiceTest {
|
||||
|
||||
@Mock
|
||||
private PetAttributeRepository repository;
|
||||
|
||||
@InjectMocks
|
||||
private PetAttributeService service;
|
||||
|
||||
@Test
|
||||
void testSave() {
|
||||
PetAttribute attr = new PetAttribute();
|
||||
attr.setTemperament("Calm");
|
||||
attr.setWeight(10.0);
|
||||
attr.setLength(20.0);
|
||||
|
||||
when(repository.save(attr)).thenReturn(attr);
|
||||
|
||||
PetAttribute saved = service.save(attr);
|
||||
assertEquals("Calm", saved.getTemperament());
|
||||
verify(repository, times(1)).save(attr);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindByPetTypeId() {
|
||||
PetAttribute attr = new PetAttribute();
|
||||
attr.setTemperament("Playful");
|
||||
when(repository.findByPetTypeId(1)).thenReturn(Arrays.asList(attr));
|
||||
|
||||
List<PetAttribute> result = service.findByPetTypeId(1);
|
||||
assertEquals(1, result.size());
|
||||
verify(repository, times(1)).findByPetTypeId(1);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package org.springframework.samples.petclinic.pet.service;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.samples.petclinic.owner.PetType;
|
||||
import org.springframework.samples.petclinic.owner.PetTypeRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PetTypeServiceTest {
|
||||
|
||||
@Mock
|
||||
private PetTypeRepository petTypeRepository;
|
||||
|
||||
@InjectMocks
|
||||
private PetTypeService petTypeService;
|
||||
|
||||
@Test
|
||||
void testFindPetTypeById_found() {
|
||||
PetType type = new PetType();
|
||||
type.setId(1);
|
||||
when(petTypeRepository.findById(1)).thenReturn(Optional.of(type));
|
||||
|
||||
PetType found = petTypeService.findPetTypeById(1);
|
||||
assertNotNull(found);
|
||||
assertEquals(found.getId(), Optional.of(1).get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindPetTypeById_notFound() {
|
||||
when(petTypeRepository.findById(2)).thenReturn(Optional.empty());
|
||||
PetType found = petTypeService.findPetTypeById(2);
|
||||
assertNull(found);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -46,8 +46,8 @@ import org.springframework.http.ResponseEntity;
|
|||
* @author Alex Lutz
|
||||
*/
|
||||
// NOT Waiting https://github.com/spring-projects/spring-boot/issues/5574
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT,
|
||||
properties = { "server.error.include-message=ALWAYS", "management.endpoints.enabled-by-default=false" })
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT, properties = { "server.error.include-message=ALWAYS",
|
||||
"management.endpoints.enabled-by-default=false", "grpc.server.port=0", "grpc.server.address=127.0.0.1" })
|
||||
class CrashControllerIntegrationTests {
|
||||
|
||||
@Value(value = "${local.server.port}")
|
||||
|
|
Loading…
Reference in a new issue