getCoffeeTypes() {
37 | return coffeeTypes;
38 | }
39 |
40 | @Override
41 | public String toString() {
42 | return "Origin{" +
43 | "name='" + name + '\'' +
44 | ", coffeeTypes=" + coffeeTypes +
45 | '}';
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/coffee-shop/src/main/java/com/sebastian_daschner/coffee_shop/orders/entity/ValidOrder.java:
--------------------------------------------------------------------------------
1 | package com.sebastian_daschner.coffee_shop.orders.entity;
2 |
3 | import com.sebastian_daschner.coffee_shop.orders.control.OrderValidator;
4 |
5 | import javax.validation.Constraint;
6 | import javax.validation.Payload;
7 | import javax.validation.constraints.NotNull;
8 | import java.lang.annotation.Documented;
9 | import java.lang.annotation.Retention;
10 | import java.lang.annotation.Target;
11 |
12 | import static java.lang.annotation.ElementType.*;
13 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
14 |
15 | @Target({FIELD, METHOD, ANNOTATION_TYPE, PARAMETER})
16 | @Retention(RUNTIME)
17 | @NotNull
18 | @Constraint(validatedBy = OrderValidator.class)
19 | @Documented
20 | public @interface ValidOrder {
21 |
22 | String message() default "";
23 |
24 | Class>[] groups() default {};
25 |
26 | Class extends Payload>[] payload() default {};
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/coffee-shop/src/main/resources/META-INF/resources/form.js:
--------------------------------------------------------------------------------
1 | const typeSelect = document.querySelector('select[name=type]');
2 | const originSelect = document.querySelector('select[name=origin]');
3 | const submitButton = document.querySelector('button[type=submit]');
4 |
5 | function init() {
6 | typeSelect.addEventListener('change', ev => {
7 | const type = ev.target.value;
8 | if (type) updateOrigins(type);
9 | });
10 | }
11 |
12 | function updateOrigins(type) {
13 | fetch(`${window.location.origin}/types`)
14 | .then(res => res.json())
15 | .then(json => {
16 | const url = json.filter(t => t.type === type)
17 | .map(t => t['_links']['origins']);
18 | fetch(url)
19 | .then(res => res.json())
20 | .then(json => {
21 | originSelect.querySelectorAll('option').forEach(el => {
22 | if (el.value) el.remove();
23 | });
24 | originSelect.removeAttribute('disabled');
25 | submitButton.removeAttribute('disabled');
26 | json.map(t => t.origin)
27 | .forEach(origin => {
28 | const option = document.createElement('option');
29 | option.value = option.innerText = origin;
30 | originSelect.appendChild(option);
31 | })
32 | });
33 | });
34 | }
35 |
36 | init();
--------------------------------------------------------------------------------
/coffee-shop/src/main/resources/META-INF/resources/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 1rem auto;
3 | width: 40rem;
4 | font-family: 'IBM Plex Sans', Arial, sans-serif;
5 | }
6 |
7 | a {
8 | color: #000;
9 | }
10 |
11 | th {
12 | text-align: left
13 | }
14 |
15 | td, th {
16 | padding-right: 0.8rem;
17 | padding-bottom: 0.4rem
18 | }
19 |
20 | .grid {
21 | display: grid;
22 | grid-auto-flow: column;
23 | grid-column-gap: 1rem;
24 | grid-auto-columns: max-content;
25 | }
26 |
27 | .error {
28 | color: red;
29 | font-size: 0.9rem;
30 | }
--------------------------------------------------------------------------------
/coffee-shop/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | quarkus.datasource.db-kind=postgresql
2 | quarkus.datasource.jdbc.url=jdbc:postgresql://coffee-shop-db:5432/postgres
3 | #%dev.quarkus.datasource.jdbc.url=jdbc:postgresql://coffee-shop-db:5432/postgres
4 | quarkus.datasource.username=postgres
5 | quarkus.datasource.password=postgres
6 | quarkus.hibernate-orm.database.generation=drop-and-create
7 | quarkus.hibernate-orm.sql-load-script=load-data.sql
8 |
9 | barista.url=http://barista:8080/processes
10 | #%dev.barista.url=http://barista:8002/processes
11 |
--------------------------------------------------------------------------------
/coffee-shop/src/main/resources/load-data.sql:
--------------------------------------------------------------------------------
1 | --
2 | -- insert data
3 |
4 | INSERT INTO origins VALUES ('Ethiopia');
5 | INSERT INTO origin_coffee_types VALUES ('Ethiopia', 'ESPRESSO');
6 | INSERT INTO origin_coffee_types VALUES ('Ethiopia', 'LATTE');
7 | INSERT INTO origin_coffee_types VALUES ('Ethiopia', 'POUR_OVER');
8 |
9 | INSERT INTO origins VALUES ('Colombia');
10 | INSERT INTO origin_coffee_types VALUES ('Colombia', 'ESPRESSO');
11 | INSERT INTO origin_coffee_types VALUES ('Colombia', 'POUR_OVER');
--------------------------------------------------------------------------------
/coffee-shop/src/main/resources/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Coffee orders
8 |
9 |
10 |
11 |
12 | All coffee orders
13 |
14 | Create coffee order
15 |
16 |
17 |
18 | Status |
19 | Type |
20 | Origin |
21 |
22 | {#for order in orders}
23 |
24 | {string:capitalize(order.status.name)} |
25 | {order.type.description} |
26 | {order.origin.name} |
27 |
28 | {/for}
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/coffee-shop/src/main/resources/templates/order.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Order coffee
8 |
9 |
10 |
11 |
12 | Order coffee
13 |
14 | Show all orders
15 |
16 | {#if failed}Could not create the order. Please select all values properly.
{/if}
17 |
18 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/coffee-shop/src/test/java/com/sebastian_daschner/coffee_shop/it/CoffeeOrderSystem.java:
--------------------------------------------------------------------------------
1 | package com.sebastian_daschner.coffee_shop.it;
2 |
3 | import javax.json.JsonArray;
4 | import javax.json.JsonObject;
5 | import javax.ws.rs.client.Client;
6 | import javax.ws.rs.client.ClientBuilder;
7 | import javax.ws.rs.client.WebTarget;
8 | import javax.ws.rs.core.MediaType;
9 | import javax.ws.rs.core.UriBuilder;
10 | import java.net.URI;
11 | import java.util.List;
12 | import java.util.stream.Collectors;
13 |
14 | class CoffeeOrderSystem {
15 |
16 | private final Client client;
17 | private final WebTarget baseTarget;
18 |
19 | CoffeeOrderSystem() {
20 | client = ClientBuilder.newClient();
21 | baseTarget = client.target(buildBaseUri());
22 | }
23 |
24 | private URI buildBaseUri() {
25 | String host = System.getProperty("coffee-shop.test.host", "localhost");
26 | String port = System.getProperty("coffee-shop.test.port", "8001");
27 | return UriBuilder.fromUri("http://{host}:{port}/").build(host, port);
28 | }
29 |
30 | boolean isSystemUp() {
31 | JsonObject healthJson = retrieveHealthStatus();
32 |
33 | String status = healthJson.getString("status");
34 | if (!"UP".equalsIgnoreCase(status))
35 | return false;
36 |
37 | return "UP".equalsIgnoreCase(extractStatus(healthJson, "coffee-shop"));
38 | }
39 |
40 | private JsonObject retrieveHealthStatus() {
41 | return baseTarget.path("health")
42 | .request(MediaType.APPLICATION_JSON_TYPE)
43 | .get(JsonObject.class);
44 | }
45 |
46 | private String extractStatus(JsonObject healthJson, String name) {
47 | return healthJson.getJsonArray("checks")
48 | .getValuesAs(JsonObject.class)
49 | .stream()
50 | .filter(o -> o.getString("name").equalsIgnoreCase(name))
51 | .map(o -> o.getString("status"))
52 | .findAny().orElse(null);
53 | }
54 |
55 | List getTypes() {
56 | return baseTarget.path("types")
57 | .request(MediaType.APPLICATION_JSON_TYPE)
58 | .get(JsonArray.class).getValuesAs(JsonObject.class).stream()
59 | .map(o -> o.getString("type"))
60 | .collect(Collectors.toList());
61 | }
62 |
63 | List getOrigins(String type) {
64 | URI typeOriginsUri = retrieveTypeOriginsLink(type);
65 |
66 | return retrieveOriginsForType(typeOriginsUri);
67 | }
68 |
69 | private URI retrieveTypeOriginsLink(String type) {
70 | return baseTarget.path("types")
71 | .request(MediaType.APPLICATION_JSON_TYPE)
72 | .get(JsonArray.class).getValuesAs(JsonObject.class).stream()
73 | .filter(o -> o.getString("type").equalsIgnoreCase(type))
74 | .map(o -> o.getJsonObject("_links").getString("origins"))
75 | .map(URI::create)
76 | .findAny().orElseThrow(() -> new IllegalStateException("Could not get link to origins for " + type));
77 | }
78 |
79 | private List retrieveOriginsForType(URI typeOriginsUri) {
80 | return client.target(typeOriginsUri)
81 | .request(MediaType.APPLICATION_JSON_TYPE)
82 | .get(JsonArray.class).getValuesAs(JsonObject.class).stream()
83 | .map(o -> o.getString("origin"))
84 | .collect(Collectors.toList());
85 | }
86 |
87 | void close() {
88 | client.close();
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/coffee-shop/src/test/java/com/sebastian_daschner/coffee_shop/it/CreateOrderQuarkusSmokeIT.java:
--------------------------------------------------------------------------------
1 | package com.sebastian_daschner.coffee_shop.it;
2 |
3 | import io.quarkus.test.junit.QuarkusTest;
4 | import org.junit.jupiter.api.AfterEach;
5 | import org.junit.jupiter.api.BeforeEach;
6 | import org.junit.jupiter.api.Disabled;
7 | import org.junit.jupiter.api.Test;
8 |
9 | import static org.assertj.core.api.Assertions.assertThat;
10 |
11 | @QuarkusTest
12 | @Disabled("too slow")
13 | class CreateOrderQuarkusSmokeIT {
14 |
15 | private CoffeeOrderSystem coffeeOrderSystem;
16 |
17 | @BeforeEach
18 | void setUp() {
19 | System.setProperty("coffee-shop.test.port", "8081");
20 | coffeeOrderSystem = new CoffeeOrderSystem();
21 | }
22 |
23 | @Test
24 | void testIsSystemUp() {
25 | assertThat(coffeeOrderSystem.isSystemUp()).isTrue();
26 | }
27 |
28 | @Test
29 | void testGetTypes() {
30 | assertThat(coffeeOrderSystem.getTypes()).containsExactlyInAnyOrder("Espresso", "Pour_over", "Latte");
31 | }
32 |
33 | @Test
34 | void testGetTypeOrigins() {
35 | assertThat(coffeeOrderSystem.getOrigins("Espresso")).containsExactlyInAnyOrder("Colombia", "Ethiopia");
36 | }
37 |
38 | @AfterEach
39 | void tearDown() {
40 | coffeeOrderSystem.close();
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/coffee-shop/src/test/java/com/sebastian_daschner/coffee_shop/it/CreateOrderSmokeIT.java:
--------------------------------------------------------------------------------
1 | package com.sebastian_daschner.coffee_shop.it;
2 |
3 | import org.junit.jupiter.api.AfterEach;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import static org.assertj.core.api.Assertions.assertThat;
8 |
9 | class CreateOrderSmokeIT {
10 |
11 | private CoffeeOrderSystem coffeeOrderSystem;
12 |
13 | @BeforeEach
14 | void setUp() {
15 | coffeeOrderSystem = new CoffeeOrderSystem();
16 | }
17 |
18 | @Test
19 | void testIsSystemUp() {
20 | assertThat(coffeeOrderSystem.isSystemUp()).isTrue();
21 | }
22 |
23 | @Test
24 | void testGetTypes() {
25 | assertThat(coffeeOrderSystem.getTypes()).containsExactlyInAnyOrder("Espresso", "Pour_over", "Latte");
26 | }
27 |
28 | @Test
29 | void testGetTypeOrigins() {
30 | assertThat(coffeeOrderSystem.getOrigins("Espresso")).containsExactlyInAnyOrder("Colombia", "Ethiopia");
31 | }
32 |
33 | @AfterEach
34 | void tearDown() {
35 | coffeeOrderSystem.close();
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/coffee-shop/src/test/java/com/sebastian_daschner/coffee_shop/orders/TestData.java:
--------------------------------------------------------------------------------
1 | package com.sebastian_daschner.coffee_shop.orders;
2 |
3 | import com.sebastian_daschner.coffee_shop.orders.entity.CoffeeType;
4 | import com.sebastian_daschner.coffee_shop.orders.entity.Order;
5 | import com.sebastian_daschner.coffee_shop.orders.entity.Origin;
6 |
7 | import java.util.EnumSet;
8 | import java.util.List;
9 | import java.util.Set;
10 | import java.util.UUID;
11 | import java.util.stream.Collectors;
12 | import java.util.stream.Stream;
13 |
14 | public final class TestData {
15 |
16 | private TestData() {
17 | }
18 |
19 | public static List unfinishedOrders() {
20 | Origin colombia = new Origin("Colombia");
21 | return List.of(
22 | new Order(UUID.randomUUID(), CoffeeType.ESPRESSO, colombia),
23 | new Order(UUID.randomUUID(), CoffeeType.LATTE, colombia),
24 | new Order(UUID.randomUUID(), CoffeeType.POUR_OVER, colombia)
25 | );
26 | }
27 |
28 | public static Set validCoffeeTypes() {
29 | return EnumSet.allOf(CoffeeType.class);
30 | }
31 |
32 | public static List validOrigins() {
33 | Set coffeeTypes = validCoffeeTypes();
34 |
35 | return Stream.of("Colombia", "Ethiopia")
36 | .map(Origin::new)
37 | .peek(o -> o.getCoffeeTypes().addAll(coffeeTypes))
38 | .collect(Collectors.toList());
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/coffee-shop/src/test/java/com/sebastian_daschner/coffee_shop/orders/boundary/CoffeeShopMockitoTest.java:
--------------------------------------------------------------------------------
1 | package com.sebastian_daschner.coffee_shop.orders.boundary;
2 |
3 | import com.sebastian_daschner.coffee_shop.orders.TestData;
4 | import com.sebastian_daschner.coffee_shop.orders.control.OrderProcessor;
5 | import com.sebastian_daschner.coffee_shop.orders.entity.Order;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.jupiter.api.extension.ExtendWith;
8 | import org.mockito.ArgumentCaptor;
9 | import org.mockito.Captor;
10 | import org.mockito.InjectMocks;
11 | import org.mockito.Mock;
12 | import org.mockito.junit.jupiter.MockitoExtension;
13 |
14 | import javax.persistence.EntityManager;
15 | import javax.persistence.TypedQuery;
16 | import java.util.List;
17 |
18 | import static org.assertj.core.api.Assertions.assertThat;
19 | import static org.mockito.Mockito.*;
20 |
21 | @ExtendWith(MockitoExtension.class)
22 | class CoffeeShopMockitoTest {
23 |
24 | @InjectMocks
25 | CoffeeShop testObject;
26 |
27 | @Captor
28 | ArgumentCaptor orderCaptor;
29 |
30 | private EntityManager entityManager;
31 | private OrderProcessor orderProcessor;
32 |
33 | CoffeeShopMockitoTest(@Mock EntityManager entityManager, @Mock OrderProcessor orderProcessor) {
34 | this.entityManager = entityManager;
35 | this.orderProcessor = orderProcessor;
36 | }
37 |
38 | @Test
39 | void testProcessUnfinishedOrders(@Mock TypedQuery mockQuery) {
40 | when(entityManager.createNamedQuery(Order.FIND_UNFINISHED, Order.class)).thenReturn(mockQuery);
41 | List desiredOrders = TestData.unfinishedOrders();
42 | when(mockQuery.getResultList()).thenReturn(desiredOrders);
43 |
44 | testObject.processUnfinishedOrders();
45 |
46 | verify(entityManager).createNamedQuery(Order.FIND_UNFINISHED, Order.class);
47 | verify(orderProcessor, times(desiredOrders.size())).processOrder(orderCaptor.capture());
48 |
49 | assertThat(orderCaptor.getAllValues()).containsExactlyElementsOf(desiredOrders);
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/coffee-shop/src/test/java/com/sebastian_daschner/coffee_shop/orders/boundary/CoffeeShopNaiveUseCaseTest.java:
--------------------------------------------------------------------------------
1 | package com.sebastian_daschner.coffee_shop.orders.boundary;
2 |
3 | import com.sebastian_daschner.coffee_shop.orders.control.Barista;
4 | import com.sebastian_daschner.coffee_shop.orders.control.OrderProcessor;
5 | import com.sebastian_daschner.coffee_shop.orders.entity.CoffeeType;
6 | import com.sebastian_daschner.coffee_shop.orders.entity.Order;
7 | import com.sebastian_daschner.coffee_shop.orders.entity.OrderStatus;
8 | import com.sebastian_daschner.coffee_shop.orders.entity.Origin;
9 | import org.junit.jupiter.api.BeforeEach;
10 | import org.junit.jupiter.api.Test;
11 | import org.mockito.ArgumentCaptor;
12 |
13 | import javax.persistence.EntityManager;
14 | import javax.persistence.TypedQuery;
15 | import java.lang.reflect.Field;
16 | import java.util.Arrays;
17 | import java.util.List;
18 | import java.util.UUID;
19 |
20 | import static org.assertj.core.api.Assertions.assertThat;
21 | import static org.mockito.ArgumentMatchers.anyString;
22 | import static org.mockito.ArgumentMatchers.eq;
23 | import static org.mockito.Mockito.*;
24 |
25 | class CoffeeShopNaiveUseCaseTest {
26 |
27 | private CoffeeShop coffeeShop;
28 | private EntityManager entityManager;
29 | private OrderProcessor orderProcessor;
30 | private Barista barista;
31 | private ArgumentCaptor orderCaptor;
32 |
33 | @BeforeEach
34 | void setUp() {
35 | coffeeShop = new CoffeeShop();
36 | orderProcessor = new OrderProcessor();
37 |
38 | coffeeShop.orderProcessor = orderProcessor;
39 | entityManager = mock(EntityManager.class);
40 | coffeeShop.entityManager = entityManager;
41 |
42 | barista = mock(Barista.class);
43 | setReflectiveField(orderProcessor, "entityManager", entityManager);
44 | setReflectiveField(orderProcessor, "barista", barista);
45 | orderCaptor = ArgumentCaptor.forClass(Order.class);
46 |
47 | when(barista.retrieveOrderStatus(orderCaptor.capture())).thenReturn(OrderStatus.PREPARING);
48 | }
49 |
50 | @Test
51 | void testCreateOrder() {
52 | Order order = new Order();
53 | coffeeShop.createOrder(order);
54 | verify(entityManager).merge(order);
55 | }
56 |
57 | @Test
58 | @SuppressWarnings("unchecked")
59 | void testProcessUnfinishedOrders() {
60 | List orders = Arrays.asList(new Order(UUID.randomUUID(), CoffeeType.ESPRESSO, new Origin("Colombia")),
61 | new Order(UUID.randomUUID(), CoffeeType.ESPRESSO, new Origin("Ethiopia")));
62 |
63 | // answer for unfinished orders
64 | TypedQuery queryMock = mock(TypedQuery.class);
65 | when(entityManager.createNamedQuery(anyString(), eq(Order.class))).thenReturn(queryMock);
66 | when(queryMock.getResultList()).thenReturn(orders);
67 |
68 | coffeeShop.processUnfinishedOrders();
69 |
70 | verify(entityManager).createNamedQuery(Order.FIND_UNFINISHED, Order.class);
71 |
72 | verify(barista, times(orders.size())).retrieveOrderStatus(any());
73 | assertThat(orderCaptor.getAllValues()).containsExactlyElementsOf(orders);
74 | }
75 |
76 | private static void setReflectiveField(Object object, String fieldName, Object value) {
77 | try {
78 | Field f1 = object.getClass().getDeclaredField(fieldName);
79 | f1.setAccessible(true);
80 | f1.set(object, value);
81 | } catch (ReflectiveOperationException e) {
82 | throw new RuntimeException(e);
83 | }
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/coffee-shop/src/test/java/com/sebastian_daschner/coffee_shop/orders/boundary/CoffeeShopTest.java:
--------------------------------------------------------------------------------
1 | package com.sebastian_daschner.coffee_shop.orders.boundary;
2 |
3 | import com.sebastian_daschner.coffee_shop.orders.TestData;
4 | import com.sebastian_daschner.coffee_shop.orders.control.OrderProcessor;
5 | import com.sebastian_daschner.coffee_shop.orders.entity.Order;
6 | import org.junit.jupiter.api.BeforeEach;
7 | import org.junit.jupiter.api.Test;
8 | import org.mockito.ArgumentCaptor;
9 |
10 | import javax.persistence.EntityManager;
11 | import javax.persistence.TypedQuery;
12 | import java.util.List;
13 |
14 | import static com.sebastian_daschner.coffee_shop.orders.entity.OrderAssert.assertThat;
15 | import static org.assertj.core.api.Assertions.assertThat;
16 | import static org.mockito.Mockito.*;
17 |
18 | class CoffeeShopTest {
19 |
20 | private CoffeeShop testObject;
21 |
22 | @BeforeEach
23 | void setUp() {
24 | testObject = new CoffeeShop();
25 | testObject.entityManager = mock(EntityManager.class);
26 | testObject.orderProcessor = mock(OrderProcessor.class);
27 | }
28 |
29 | @Test
30 | @SuppressWarnings("unchecked")
31 | void testProcessUnfinishedOrders() {
32 | List desiredOrders = TestData.unfinishedOrders();
33 |
34 | TypedQuery mockQuery = mock(TypedQuery.class);
35 | when(testObject.entityManager.createNamedQuery(Order.FIND_UNFINISHED, Order.class)).thenReturn(mockQuery);
36 | when(mockQuery.getResultList()).thenReturn(desiredOrders);
37 | ArgumentCaptor orderCaptor = ArgumentCaptor.forClass(Order.class);
38 |
39 | testObject.processUnfinishedOrders();
40 |
41 | verify(testObject.entityManager).createNamedQuery(Order.FIND_UNFINISHED, Order.class);
42 | verify(testObject.orderProcessor, times(desiredOrders.size())).processOrder(orderCaptor.capture());
43 |
44 | assertThat(orderCaptor.getAllValues()).containsExactlyElementsOf(desiredOrders);
45 |
46 | orderCaptor.getAllValues().forEach(o -> assertThat(o).isPreparing());
47 | }
48 |
49 | }
--------------------------------------------------------------------------------
/coffee-shop/src/test/java/com/sebastian_daschner/coffee_shop/orders/boundary/CoffeeShopTestDouble.java:
--------------------------------------------------------------------------------
1 | package com.sebastian_daschner.coffee_shop.orders.boundary;
2 |
3 | import com.sebastian_daschner.coffee_shop.orders.control.OrderProcessorTestDouble;
4 | import com.sebastian_daschner.coffee_shop.orders.entity.Order;
5 |
6 | import javax.persistence.EntityManager;
7 | import javax.persistence.TypedQuery;
8 | import java.util.List;
9 |
10 | import static org.mockito.ArgumentMatchers.anyString;
11 | import static org.mockito.Mockito.*;
12 |
13 | public class CoffeeShopTestDouble extends CoffeeShop {
14 |
15 | public CoffeeShopTestDouble(OrderProcessorTestDouble orderProcessorTestDouble) {
16 | entityManager = mock(EntityManager.class);
17 | orderProcessor = orderProcessorTestDouble;
18 | }
19 |
20 | public void verifyCreateOrder(Order order) {
21 | verify(entityManager).merge(order);
22 | }
23 |
24 | public void verifyProcessUnfinishedOrders() {
25 | verify(entityManager).createNamedQuery(Order.FIND_UNFINISHED, Order.class);
26 | }
27 |
28 | public void answerForUnfinishedOrders(List orders) {
29 | @SuppressWarnings("unchecked")
30 | TypedQuery queryMock = mock(TypedQuery.class);
31 | when(entityManager.createNamedQuery(anyString(), eq(Order.class))).thenReturn(queryMock);
32 | when(queryMock.getResultList()).thenReturn(orders);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/coffee-shop/src/test/java/com/sebastian_daschner/coffee_shop/orders/boundary/CoffeeShopUseCaseTest.java:
--------------------------------------------------------------------------------
1 | package com.sebastian_daschner.coffee_shop.orders.boundary;
2 |
3 | import com.sebastian_daschner.coffee_shop.orders.control.OrderProcessorTestDouble;
4 | import com.sebastian_daschner.coffee_shop.orders.entity.CoffeeType;
5 | import com.sebastian_daschner.coffee_shop.orders.entity.Order;
6 | import com.sebastian_daschner.coffee_shop.orders.entity.Origin;
7 | import org.junit.jupiter.api.BeforeEach;
8 | import org.junit.jupiter.api.Test;
9 |
10 | import java.util.Arrays;
11 | import java.util.List;
12 | import java.util.UUID;
13 |
14 | class CoffeeShopUseCaseTest {
15 |
16 | private CoffeeShopTestDouble coffeeShop;
17 | private OrderProcessorTestDouble orderProcessor;
18 |
19 | @BeforeEach
20 | void setUp() {
21 | orderProcessor = new OrderProcessorTestDouble();
22 | coffeeShop = new CoffeeShopTestDouble(orderProcessor);
23 | }
24 |
25 | @Test
26 | void testCreateOrder() {
27 | Order order = new Order();
28 | coffeeShop.createOrder(order);
29 | coffeeShop.verifyCreateOrder(order);
30 | }
31 |
32 | @Test
33 | void testProcessUnfinishedOrders() {
34 | List orders = Arrays.asList(new Order(UUID.randomUUID(), CoffeeType.ESPRESSO, new Origin("Colombia")),
35 | new Order(UUID.randomUUID(), CoffeeType.ESPRESSO, new Origin("Ethiopia")));
36 | coffeeShop.answerForUnfinishedOrders(orders);
37 |
38 | coffeeShop.processUnfinishedOrders();
39 |
40 | coffeeShop.verifyProcessUnfinishedOrders();
41 | orderProcessor.verifyProcessOrders(orders);
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/coffee-shop/src/test/java/com/sebastian_daschner/coffee_shop/orders/control/OrderProcessorTestDouble.java:
--------------------------------------------------------------------------------
1 | package com.sebastian_daschner.coffee_shop.orders.control;
2 |
3 | import com.sebastian_daschner.coffee_shop.orders.entity.Order;
4 | import com.sebastian_daschner.coffee_shop.orders.entity.OrderStatus;
5 | import org.assertj.core.api.Assertions;
6 | import org.mockito.ArgumentCaptor;
7 | import org.mockito.ArgumentMatcher;
8 | import org.mockito.Mockito;
9 |
10 | import javax.persistence.EntityManager;
11 | import java.util.List;
12 |
13 | import static org.assertj.core.api.Assertions.assertThat;
14 | import static org.mockito.ArgumentMatchers.any;
15 | import static org.mockito.Mockito.*;
16 |
17 | public class OrderProcessorTestDouble extends OrderProcessor {
18 |
19 | private final ArgumentCaptor orderCaptor;
20 |
21 | public OrderProcessorTestDouble() {
22 | entityManager = mock(EntityManager.class);
23 | barista = mock(Barista.class);
24 | orderCaptor = ArgumentCaptor.forClass(Order.class);
25 |
26 | when(barista.retrieveOrderStatus(orderCaptor.capture())).thenReturn(OrderStatus.PREPARING);
27 | }
28 |
29 | public void verifyProcessOrders(List orders) {
30 | verify(barista, times(orders.size())).retrieveOrderStatus(any());
31 | assertThat(orderCaptor.getAllValues()).containsExactlyElementsOf(orders);
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/coffee-shop/src/test/java/com/sebastian_daschner/coffee_shop/orders/control/OrderValidatorTest.java:
--------------------------------------------------------------------------------
1 | package com.sebastian_daschner.coffee_shop.orders.control;
2 |
3 | import com.sebastian_daschner.coffee_shop.orders.boundary.CoffeeShop;
4 | import com.sebastian_daschner.coffee_shop.orders.entity.CoffeeType;
5 | import com.sebastian_daschner.coffee_shop.orders.entity.Origin;
6 | import org.junit.jupiter.api.BeforeEach;
7 | import org.junit.jupiter.params.ParameterizedTest;
8 | import org.junit.jupiter.params.provider.MethodSource;
9 |
10 | import javax.json.Json;
11 | import javax.json.JsonObject;
12 | import javax.validation.ConstraintValidatorContext;
13 | import java.io.StringReader;
14 | import java.util.Collection;
15 | import java.util.EnumSet;
16 | import java.util.List;
17 | import java.util.Set;
18 |
19 | import static org.assertj.core.api.Assertions.assertThat;
20 | import static org.mockito.Mockito.mock;
21 | import static org.mockito.Mockito.when;
22 |
23 | class OrderValidatorTest {
24 |
25 | private OrderValidator testObject;
26 | private ConstraintValidatorContext context;
27 |
28 | @BeforeEach
29 | void setUp() {
30 | testObject = new OrderValidator();
31 | testObject.coffeeShop = mock(CoffeeShop.class);
32 | context = mock(ConstraintValidatorContext.class);
33 |
34 | Set coffeeTypes = EnumSet.allOf(CoffeeType.class);
35 | Origin colombia = new Origin("Colombia");
36 | colombia.getCoffeeTypes().addAll(coffeeTypes);
37 |
38 | when(testObject.coffeeShop.getCoffeeTypes()).thenReturn(coffeeTypes);
39 | when(testObject.coffeeShop.getOrigin("Colombia")).thenReturn(colombia);
40 | }
41 |
42 | @ParameterizedTest
43 | @MethodSource("validData")
44 | void testIsValid(String json) {
45 | JsonObject jsonObject = Json.createReader(new StringReader(json)).readObject();
46 |
47 | assertThat(testObject.isValid(jsonObject, context)).isTrue();
48 | }
49 |
50 | private static Collection validData() {
51 | return List.of(
52 | "{\"type\":\"ESPRESSO\",\"origin\":\"Colombia\"}",
53 | "{\"type\":\"Espresso\",\"origin\":\"Colombia\"}",
54 | "{\"type\":\"LATTE\",\"origin\":\"Colombia\"}",
55 | "{\"type\":\"Latte\",\"origin\":\"Colombia\"}",
56 | "{\"type\":\"POUR_OVER\",\"origin\":\"Colombia\"}",
57 | "{\"type\":\"Pour_over\",\"origin\":\"Colombia\"}");
58 | }
59 |
60 | @ParameterizedTest
61 | @MethodSource("invalidData")
62 | void testIsInvalid(String json) {
63 | JsonObject jsonObject = Json.createReader(new StringReader(json)).readObject();
64 |
65 | assertThat(testObject.isValid(jsonObject, context)).isFalse();
66 | }
67 |
68 | private static Collection invalidData() {
69 | return List.of(
70 | "{\"type\":\"SIPHON\",\"origin\":\"Colombia\"}",
71 | "{\"type\":null,\"origin\":\"Colombia\"}",
72 | "{\"origin\":\"Colombia\"}",
73 | "{\"type\":\"ESPRESSO\",\"origin\":\"Ethiopia\"}");
74 | }
75 |
76 | }
--------------------------------------------------------------------------------
/coffee-shop/src/test/java/com/sebastian_daschner/coffee_shop/orders/control/RunCucumberTest.java:
--------------------------------------------------------------------------------
1 | package com.sebastian_daschner.coffee_shop.orders.control;
2 |
3 | import cucumber.api.CucumberOptions;
4 | import cucumber.api.junit.Cucumber;
5 | import org.junit.runner.RunWith;
6 |
7 | @RunWith(Cucumber.class)
8 | @CucumberOptions
9 | public class RunCucumberTest {
10 |
11 |
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/coffee-shop/src/test/java/com/sebastian_daschner/coffee_shop/orders/control/StepDefs.java:
--------------------------------------------------------------------------------
1 | package com.sebastian_daschner.coffee_shop.orders.control;
2 |
3 | import com.sebastian_daschner.coffee_shop.orders.TestData;
4 | import com.sebastian_daschner.coffee_shop.orders.boundary.CoffeeShop;
5 | import com.sebastian_daschner.coffee_shop.orders.entity.CoffeeType;
6 | import com.sebastian_daschner.coffee_shop.orders.entity.Origin;
7 | import cucumber.api.java.Before;
8 | import cucumber.api.java.en.Then;
9 | import cucumber.api.java.en.When;
10 |
11 | import javax.json.Json;
12 | import javax.json.JsonObject;
13 | import javax.validation.ConstraintValidatorContext;
14 | import java.util.List;
15 | import java.util.Set;
16 |
17 | import static org.assertj.core.api.Assertions.assertThat;
18 | import static org.mockito.ArgumentMatchers.anyString;
19 | import static org.mockito.Mockito.mock;
20 | import static org.mockito.Mockito.when;
21 |
22 | public class StepDefs {
23 |
24 | private OrderValidator testObject;
25 | private ConstraintValidatorContext context;
26 | private JsonObject jsonObject;
27 |
28 | @Before
29 | public void setUp() {
30 | testObject = new OrderValidator();
31 | testObject.coffeeShop = mock(CoffeeShop.class);
32 | context = mock(ConstraintValidatorContext.class);
33 |
34 | List origins = TestData.validOrigins();
35 | Set coffeeTypes = TestData.validCoffeeTypes();
36 |
37 | when(testObject.coffeeShop.getCoffeeTypes()).thenReturn(coffeeTypes);
38 | when(testObject.coffeeShop.getOrigin(anyString())).then(invocation -> origins.stream()
39 | .filter(o -> o.getName().equals(invocation.getArgument(0)))
40 | .findAny()
41 | .orElse(null));
42 | }
43 |
44 | @When("^I create an order with ([^ ]*) from ([^ ]*)$")
45 | public void i_create_an_order(String type, String origin) {
46 | jsonObject = Json.createObjectBuilder()
47 | .add("type", type)
48 | .add("origin", origin)
49 | .build();
50 | }
51 |
52 | @Then("^The order should be accepted$")
53 | public void accepted_order() {
54 | assertThat(testObject.isValid(jsonObject, context)).isTrue();
55 | }
56 |
57 | @Then("^The order should be rejected$")
58 | public void rejected_order() {
59 | assertThat(testObject.isValid(jsonObject, context)).isFalse();
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/coffee-shop/src/test/java/com/sebastian_daschner/coffee_shop/orders/entity/OrderAssert.java:
--------------------------------------------------------------------------------
1 | package com.sebastian_daschner.coffee_shop.orders.entity;
2 |
3 | import org.assertj.core.api.AbstractAssert;
4 |
5 | public class OrderAssert extends AbstractAssert {
6 |
7 | public OrderAssert(Order task) {
8 | super(task, OrderAssert.class);
9 | }
10 |
11 | public static OrderAssert assertThat(Order actual) {
12 | return new OrderAssert(actual);
13 | }
14 |
15 | public OrderAssert isPreparing() {
16 | isNotNull();
17 | if (actual.getStatus() != OrderStatus.PREPARING) {
18 | failWithMessage("Expected the order to be in status PREPARING but was %s", actual.getStatus());
19 | }
20 | return this;
21 | }
22 |
23 | public OrderAssert containsMilk() {
24 | isNotNull();
25 | if (actual.getType() != CoffeeType.LATTE) {
26 | failWithMessage("Expected the coffee order to contain milk but the coffee type was %s", actual.getType());
27 | }
28 | return this;
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/coffee-shop/src/test/resources/com/sebastian_daschner/coffee_shop/orders/control/validating-coffee-orders.feature:
--------------------------------------------------------------------------------
1 | Feature: Validating coffee order
2 |
3 | Scenario Outline: Creating from , will be
4 | When I create an order with from
5 | Then The order should be
6 |
7 | Examples:
8 | | type | origin | result |
9 | | Espresso | Colombia | accepted |
10 | | Pour_over | Colombia | accepted |
11 | | Espresso | Ethiopia | accepted |
12 | | Latte | Ethiopia | accepted |
13 | | Pour_over | Ethiopia | accepted |
14 | | Espresso | Germany | rejected |
15 | | Siphon | Colombia | rejected |
16 | | Siphon | Germany | rejected |
17 |
--------------------------------------------------------------------------------
/nexus_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | nexus
6 | Nexus Public Mirror
7 | https://nexus-nexus.OCP_APPS_DOMAIN/repository/maven-all-public/
8 | *
9 |
10 |
11 |
12 |
13 | nexus
14 | admin
15 | redhat
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/nexus_settings_openshift.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | nexus
6 | Nexus Public Mirror
7 | http://nexus.nexus.svc:8081/repository/maven-all-public/
8 | *
9 |
10 |
11 |
12 |
13 | nexus
14 | admin
15 | redhat
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/systemtest-run-dev-env.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | cd ${0%/*}/coffee-shop
5 |
6 | trap cleanup EXIT
7 |
8 | function cleanup() {
9 | echo stopping containers
10 | docker stop coffee-shop coffee-shop-db barista &> /dev/null || true
11 | }
12 |
13 |
14 | cleanup
15 |
16 | docker run -d --rm \
17 | --name coffee-shop-db \
18 | --network dkrnet \
19 | -p 5432:5432 \
20 | -e POSTGRES_USER=postgres \
21 | -e POSTGRES_PASSWORD=postgres \
22 | postgres:9.5
23 |
24 | docker run -d --rm \
25 | --name barista \
26 | --network dkrnet \
27 | -p 8002:8080 \
28 | rodolpheche/wiremock:2.6.0
29 |
30 |
31 | # coffee-shop
32 | mvn clean package -Dquarkus.package.type=mutable-jar
33 | docker build -f Dockerfile.dev -t tmp-builder .
34 |
35 | docker run -d --rm \
36 | --name coffee-shop \
37 | --network dkrnet \
38 | -p 8001:8080 \
39 | -p 5005:5005 \
40 | tmp-builder
41 |
42 | # wait for app startup
43 | while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' http://localhost:8001/health)" != "200" ]]; do
44 | sleep 2;
45 | done
46 |
47 | mvn quarkus:remote-dev -Ddebug=false -Dquarkus.live-reload.url=http://localhost:8001 -Dquarkus.live-reload.password=123 -Dquarkus.package.type=mutable-jar
--------------------------------------------------------------------------------
/systemtest-run-env.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 | cd ${0%/*}/coffee-shop
4 |
5 | docker stop coffee-shop coffee-shop-db barista &> /dev/null || true
6 |
7 | docker run -d --rm \
8 | --name coffee-shop-db \
9 | --network dkrnet \
10 | -p 5432:5432 \
11 | -e POSTGRES_USER=postgres \
12 | -e POSTGRES_PASSWORD=postgres \
13 | postgres:9.5
14 |
15 | docker run -d --rm \
16 | --name barista \
17 | --network dkrnet \
18 | -p 8002:8080 \
19 | rodolpheche/wiremock:2.6.0
20 |
21 | # wait for db startup
22 | sleep 5
23 |
24 | docker run -d --rm \
25 | --name coffee-shop \
26 | --network dkrnet \
27 | -p 8001:8080 \
28 | coffee-shop
29 |
--------------------------------------------------------------------------------