From 078448a6a68fc58dbcc0ac375f3fcfb47b437cee Mon Sep 17 00:00:00 2001 From: Ralf Ueberfuhr Date: Tue, 18 Jun 2024 14:36:43 +0200 Subject: [PATCH 1/3] Split JAX-RS resource and service (separation of concerns) --- .../sample/quarkus/CustomersResource.java | 30 ++++------ .../sample/quarkus/CustomersService.java | 56 +++++++++++++++++++ 2 files changed, 67 insertions(+), 19 deletions(-) create mode 100644 customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersService.java diff --git a/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersResource.java b/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersResource.java index 604f3dd..4af6ec5 100644 --- a/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersResource.java +++ b/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersResource.java @@ -5,38 +5,30 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; +import lombok.RequiredArgsConstructor; -import java.time.LocalDate; -import java.time.Month; -import java.util.*; +import java.util.Collection; +import java.util.UUID; @Path("/customers") +@RequiredArgsConstructor public class CustomersResource { - private final Map customers = new HashMap<>(); - - { - var customer = new Customer( - UUID.randomUUID(), - "Tom", - LocalDate.of(2000, Month.DECEMBER, 6), - "active" - ); - customers.put(customer.getUuid(), customer); - } + private final CustomersService service; @GET @Produces(MediaType.APPLICATION_JSON) public Collection getCustomers() { - return customers.values(); + return service + .getAll() + .toList(); } @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response createCustomer(@Valid Customer customer) { - customer.setUuid(UUID.randomUUID()); - customers.put(customer.getUuid(), customer); + service.createCustomer(customer); final var location = UriBuilder.fromResource(CustomersResource.class) .path(CustomersResource.class, "findCustomerById") .build(customer.getUuid().toString()); @@ -58,14 +50,14 @@ public Response createCustomer(@Valid Customer customer) { @GET @Path("/{uuid}") public Customer findCustomerById(@PathParam("uuid") UUID uuid) { - return Optional.ofNullable(customers.get(uuid)) + return service.getByUuid(uuid) .orElseThrow(NotFoundException::new); } @DELETE @Path("/{uuid}") public Response deleteCustomerById(@PathParam("uuid") UUID uuid) { - if (customers.remove(uuid) == null) { + if (!service.delete(uuid)) { throw new NotFoundException(); } return Response diff --git a/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersService.java b/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersService.java new file mode 100644 index 0000000..ab80b24 --- /dev/null +++ b/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersService.java @@ -0,0 +1,56 @@ +package de.schulung.sample.quarkus; + +import jakarta.enterprise.context.ApplicationScoped; + +import java.time.LocalDate; +import java.time.Month; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Stream; + +@ApplicationScoped +public class CustomersService { + + private final Map customers = new HashMap<>(); + + { // TODO replace this? + var customer = new Customer( + UUID.randomUUID(), + "Tom", + LocalDate.of(2000, Month.DECEMBER, 6), + "active" + ); + customers.put(customer.getUuid(), customer); + } + + public Stream getAll() { + return this.customers + .values() + .stream(); + } + + public Stream getByState(String state) { + return this.getAll() + .filter(c -> c.getState().equals(state)); + } + + public void createCustomer(Customer customer) { + customer.setUuid(UUID.randomUUID()); + customers.put(customer.getUuid(), customer); + } + + public Optional getByUuid(UUID uuid) { + return Optional.ofNullable(customers.get(uuid)); + } + + public boolean exists(UUID uuid) { + return customers.containsKey(uuid); + } + + public boolean delete(UUID uuid) { + return customers.remove(uuid) != null; + } + +} From b6663ee8645ded1b0cb17de659902a40c612e3ad Mon Sep 17 00:00:00 2001 From: Ralf Ueberfuhr Date: Tue, 18 Jun 2024 15:51:30 +0200 Subject: [PATCH 2/3] Add tests --- customer-api-provider/pom.xml | 17 ++++++++ .../sample/quarkus/CustomersResource.java | 2 +- .../sample/quarkus/CustomersService.java | 6 ++- .../CustomerApiMockedServiceTests.java | 41 +++++++++++++++++++ .../CustomersServiceIntegrationTests.java | 14 +++++++ .../sample/quarkus/CustomersServiceTests.java | 41 +++++++++++++++++++ 6 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomerApiMockedServiceTests.java create mode 100644 customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomersServiceIntegrationTests.java create mode 100644 customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomersServiceTests.java diff --git a/customer-api-provider/pom.xml b/customer-api-provider/pom.xml index 8a725ee..198ea72 100644 --- a/customer-api-provider/pom.xml +++ b/customer-api-provider/pom.xml @@ -28,6 +28,13 @@ pom import + + org.assertj + assertj-bom + 3.26.0 + pom + import + @@ -56,6 +63,16 @@ rest-assured test + + org.assertj + assertj-core + test + + + io.quarkus + quarkus-junit5-mockito + test + io.quarkus quarkus-hibernate-validator diff --git a/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersResource.java b/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersResource.java index 4af6ec5..4f2eab4 100644 --- a/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersResource.java +++ b/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersResource.java @@ -51,7 +51,7 @@ public Response createCustomer(@Valid Customer customer) { @Path("/{uuid}") public Customer findCustomerById(@PathParam("uuid") UUID uuid) { return service.getByUuid(uuid) - .orElseThrow(NotFoundException::new); + .orElseThrow(NotFoundException::new); } @DELETE diff --git a/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersService.java b/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersService.java index ab80b24..125b741 100644 --- a/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersService.java +++ b/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersService.java @@ -1,6 +1,8 @@ package de.schulung.sample.quarkus; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import java.time.LocalDate; import java.time.Month; @@ -36,12 +38,12 @@ public Stream getByState(String state) { .filter(c -> c.getState().equals(state)); } - public void createCustomer(Customer customer) { + public void createCustomer(@Valid Customer customer) { customer.setUuid(UUID.randomUUID()); customers.put(customer.getUuid(), customer); } - public Optional getByUuid(UUID uuid) { + public Optional getByUuid(@NotNull UUID uuid) { return Optional.ofNullable(customers.get(uuid)); } diff --git a/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomerApiMockedServiceTests.java b/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomerApiMockedServiceTests.java new file mode 100644 index 0000000..5d4e78f --- /dev/null +++ b/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomerApiMockedServiceTests.java @@ -0,0 +1,41 @@ +package de.schulung.sample.quarkus; + +import io.quarkus.test.InjectMock; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.UUID; + +import static io.restassured.RestAssured.given; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@QuarkusTest +public class CustomerApiMockedServiceTests { + + @InjectMock + CustomersService service; + + @Test + void shouldReturn404WhenCustomerNotExists() { + + var uuid = UUID.randomUUID(); + when(service.getByUuid(uuid)) + .thenReturn(Optional.empty()); + + given() + .when() + .accept(ContentType.JSON) + .get("/customers/{uuid}", uuid) + .then() + .statusCode(404); + + verify(service).getByUuid(uuid); + + } + + // TODO: wenn invalider Kunde angelegt werden soll, dann 400 + kein Service-Aufruf + +} diff --git a/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomersServiceIntegrationTests.java b/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomersServiceIntegrationTests.java new file mode 100644 index 0000000..765d5ba --- /dev/null +++ b/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomersServiceIntegrationTests.java @@ -0,0 +1,14 @@ +package de.schulung.sample.quarkus; + +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; + +@QuarkusTest +public class CustomersServiceIntegrationTests { + + @Inject + CustomersService service; + + // TODO Testfall: Customer anlegen mit zu kurzem Namen -> Validierungsfehler + +} diff --git a/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomersServiceTests.java b/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomersServiceTests.java new file mode 100644 index 0000000..4dcab18 --- /dev/null +++ b/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomersServiceTests.java @@ -0,0 +1,41 @@ +package de.schulung.sample.quarkus; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.time.Month; + +import static org.assertj.core.api.Assertions.assertThat; + +class CustomersServiceTests { + + private final CustomersService service = new CustomersService(); + + @Test + void shouldAddUuidToNewCustomer() { + var customer = new Customer(); + customer.setName("Tom"); + customer.setBirthdate(LocalDate.of(2000, Month.FEBRUARY, 2)); + customer.setState("active"); + + service.createCustomer(customer); + + assertThat(customer.getUuid()) + .isNotNull(); + } + + @Test + void shouldFindNewCustomer() { + var customer = new Customer(); + customer.setName("Tom"); + customer.setBirthdate(LocalDate.of(2000, Month.FEBRUARY, 2)); + customer.setState("active"); + service.createCustomer(customer); + var uuid = customer.getUuid(); + + var result = service.getByUuid(uuid); + + assertThat(result).isNotEmpty(); + } + +} From 9d508374aad88326d0dc303b954352c420f36f96 Mon Sep 17 00:00:00 2001 From: Ralf Ueberfuhr Date: Tue, 18 Jun 2024 16:01:46 +0200 Subject: [PATCH 3/3] Add tests --- .../de/schulung/sample/quarkus/Customer.java | 3 +++ .../sample/quarkus/CustomersService.java | 11 ++++++--- .../CustomerApiMockedServiceTests.java | 24 +++++++++++++++++-- .../CustomersServiceIntegrationTests.java | 22 +++++++++++++++++ 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/customer-api-provider/src/main/java/de/schulung/sample/quarkus/Customer.java b/customer-api-provider/src/main/java/de/schulung/sample/quarkus/Customer.java index ff7165f..ba0a508 100644 --- a/customer-api-provider/src/main/java/de/schulung/sample/quarkus/Customer.java +++ b/customer-api-provider/src/main/java/de/schulung/sample/quarkus/Customer.java @@ -2,6 +2,7 @@ import jakarta.json.bind.annotation.JsonbProperty; import jakarta.json.bind.annotation.JsonbTransient; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; @@ -22,10 +23,12 @@ public class Customer { @Setter(onMethod_ = @JsonbTransient) private UUID uuid; @Size(min = 3, max = 100) + @NotNull private String name; @JsonbProperty("birth_date") // TODO -> use snake_case globally? private LocalDate birthdate; @Pattern(regexp = "active|locked|disabled") + @NotNull private String state; } diff --git a/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersService.java b/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersService.java index 125b741..d833329 100644 --- a/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersService.java +++ b/customer-api-provider/src/main/java/de/schulung/sample/quarkus/CustomersService.java @@ -3,6 +3,7 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import java.time.LocalDate; import java.time.Month; @@ -33,7 +34,11 @@ public Stream getAll() { .stream(); } - public Stream getByState(String state) { + public Stream getByState( + @NotNull + @Pattern(regexp = "active|locked|disabled") + String state + ) { return this.getAll() .filter(c -> c.getState().equals(state)); } @@ -47,11 +52,11 @@ public Optional getByUuid(@NotNull UUID uuid) { return Optional.ofNullable(customers.get(uuid)); } - public boolean exists(UUID uuid) { + public boolean exists(@NotNull UUID uuid) { return customers.containsKey(uuid); } - public boolean delete(UUID uuid) { + public boolean delete(@NotNull UUID uuid) { return customers.remove(uuid) != null; } diff --git a/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomerApiMockedServiceTests.java b/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomerApiMockedServiceTests.java index 5d4e78f..777728a 100644 --- a/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomerApiMockedServiceTests.java +++ b/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomerApiMockedServiceTests.java @@ -9,8 +9,7 @@ import java.util.UUID; import static io.restassured.RestAssured.given; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @QuarkusTest public class CustomerApiMockedServiceTests { @@ -38,4 +37,25 @@ void shouldReturn404WhenCustomerNotExists() { // TODO: wenn invalider Kunde angelegt werden soll, dann 400 + kein Service-Aufruf + @Test + void shouldNotInvokeServiceWhenInvalidCustomerIsCreated() { + given() + .when() + .contentType(ContentType.JSON) + .body(""" + { + "name": "T", + "birth_date": "2000-10-04", + "state": "active" + } + """) + .accept(ContentType.JSON) + .post("/customers") + .then() + .statusCode(400); + + verifyNoInteractions(service); + + } + } diff --git a/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomersServiceIntegrationTests.java b/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomersServiceIntegrationTests.java index 765d5ba..64b001d 100644 --- a/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomersServiceIntegrationTests.java +++ b/customer-api-provider/src/test/java/de/schulung/sample/quarkus/CustomersServiceIntegrationTests.java @@ -2,6 +2,9 @@ import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; @QuarkusTest public class CustomersServiceIntegrationTests { @@ -11,4 +14,23 @@ public class CustomersServiceIntegrationTests { // TODO Testfall: Customer anlegen mit zu kurzem Namen -> Validierungsfehler + @Test + void shouldNotCreateInvalidCustomer() { + var customer = new Customer(); + assertThatThrownBy(() -> service.createCustomer(customer)) + .isNotNull(); + } + + @Test + void shouldNotCreateNullCustomer() { + assertThatThrownBy(() -> service.createCustomer(null)) + .isNotNull(); + } + + @Test + void shouldNotInvokeSearchWithNullUuid() { + assertThatThrownBy(() -> service.getByUuid(null)) + .isNotNull(); + } + }