Skip to content

Commit

Permalink
#244 Migrate Coffee API calls to graphQL
Browse files Browse the repository at this point in the history
  • Loading branch information
conorheffron committed Jan 18, 2025
1 parent 24b715b commit 8abf27c
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 7 deletions.
4 changes: 2 additions & 2 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>conorheffron</groupId>
<artifactId>ironoc</artifactId>
<version>7.0.1</version>
<version>7.0.2</version>
<packaging>war</packaging>

<distributionManagement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public ResponseEntity<List<CoffeeDomain>> getCoffeeDetailsGraphQl() {
List<CoffeeDomain> cachedResults = coffeesCache.get();
if (cachedResults == null || cachedResults.isEmpty()) {
try {
// fetch brew details
Map<String, Object> response = graphQLClientService.fetchCoffeeDetails();
List<Map<String, Object>> hot = graphQLClientService.getAllHotCoffees(response);
List<Map<String, Object>> ice = graphQLClientService.getAllIcedCoffees(response);
Expand All @@ -69,8 +70,12 @@ public ResponseEntity<List<CoffeeDomain>> getCoffeeDetailsGraphQl() {
// map results
List<CoffeeDomain> coffeeDomains = new ArrayList<>();
for (Map<String, Object> coffeeMap : mergedCoffees) {
CoffeeDomain coffeeDomain = new ObjectMapper().convertValue(coffeeMap, CoffeeDomain.class);
coffeeDomains.add(coffeeDomain);
try {
CoffeeDomain coffeeDomain = new ObjectMapper().convertValue(coffeeMap, CoffeeDomain.class);
coffeeDomains.add(coffeeDomain);
} catch (Exception e) {
error("Error occurred mapping coffee domain object", e.getMessage());
}
}

// cache result set
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/net/ironoc/portfolio/domain/CoffeeDomain.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.ironoc.portfolio.domain;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand All @@ -24,6 +25,7 @@ public class CoffeeDomain {

@Schema(name= "ingredients", description = "Main Ingredients.", example = "Long steeped coffee, Ice",
requiredMode = Schema.RequiredMode.REQUIRED)
@JsonDeserialize(using = IngredientsDeserializer.class)
private List<String> ingredients;

@Schema(name= "image", description = "Image URL.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package net.ironoc.portfolio.domain;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import net.ironoc.portfolio.exception.IronocJsonException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class IngredientsDeserializer extends JsonDeserializer<List<String>> {

@Override
public List<String> deserialize(JsonParser p, DeserializationContext context) throws IOException, IronocJsonException {
JsonNode node = p.getCodec().readTree(p);
List<String> ingredients = new ArrayList<>();

if (node.isArray()) {
for (JsonNode element : node) {
ingredients.add(element.asText());
}
} else {
throw new IronocJsonException("Unexpected exception occurred deserializing ingredients");
}
return ingredients;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package net.ironoc.portfolio.exception;

import com.fasterxml.jackson.core.JsonProcessingException;

public class IronocJsonException extends JsonProcessingException {

public IronocJsonException(String message) {
super(message);
}

public IronocJsonException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.ironoc.portfolio.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import net.ironoc.portfolio.domain.CoffeeDomain;
import net.ironoc.portfolio.service.GraphQLClientService;
import net.ironoc.portfolio.service.CoffeesCache;
Expand All @@ -17,12 +18,17 @@
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import java.util.Arrays;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
Expand Down Expand Up @@ -112,4 +118,111 @@ public void test_getCoffeeDetails_cacheResult_success() throws Exception {
verify(coffeesCacheMock).get();
verify(coffeesServiceMock, never()).getCoffeeDetails();
}

@Test
public void testGetCoffeeDetailsGraphQl_CachedResults() throws Exception {
// Given
List<CoffeeDomain> cachedResults = getSampleCoffeeDomainList();
when(coffeesCacheMock.get()).thenReturn(cachedResults);

// When
// When & Then
mockMvc.perform(get("/coffees-graph-ql")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json("[{'title':'Iced Coffee','ingredients':['Coffee'," +
"'Ice']," +
"'image':'image_url_1'},{'title':'Latte'," +
"'ingredients':['Espresso', 'Milk']," +
"'image':'image_url_2'}]"));

// Then
verify(coffeesCacheMock, times(1)).get();
verify(graphQLClientServiceMock, never()).fetchCoffeeDetails();
}

@Test
public void testGetCoffeeDetailsGraphQl_NoCachedResults() throws Exception {
// Given
when(coffeesCacheMock.get()).thenReturn(null);
Map<String, Object> response = getSampleResponse();
when(graphQLClientServiceMock.fetchCoffeeDetails()).thenReturn(response);
when(graphQLClientServiceMock.getAllHotCoffees(response)).thenReturn((List<Map<String, Object>>) response.get("allHots"));
when(graphQLClientServiceMock.getAllIcedCoffees(response)).thenReturn((List<Map<String, Object>>) response.get("allIceds"));

// When & Then
mockMvc.perform(get("/coffees-graph-ql")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json("[{'title':'Black Coffee','ingredients':['Coffee']," +
"'image':'image_url_1'},{'title':'Latte'," +
"'ingredients':['Espresso', 'Milk']," +
"'image':'image_url_2'}]"));

verify(graphQLClientServiceMock, times(1)).fetchCoffeeDetails();
verify(graphQLClientServiceMock, times(1)).getAllHotCoffees(response);
verify(graphQLClientServiceMock, times(1)).getAllIcedCoffees(response);
verify(coffeesCacheMock, times(1)).put(anyList());
}

@Test
public void testGetCoffeeDetailsGraphQl_JsonProcessingException() throws Exception {
// Given
when(coffeesCacheMock.get()).thenReturn(null);
when(graphQLClientServiceMock.fetchCoffeeDetails()).thenThrow(JsonProcessingException.class);

// When & Then
mockMvc.perform(get("/coffees-graph-ql")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());

verify(graphQLClientServiceMock, times(1)).fetchCoffeeDetails();
verify(coffeesCacheMock).get();
}

private List<CoffeeDomain> getSampleCoffeeDomainList() {
List<CoffeeDomain> coffeeDomains = new ArrayList<>();
coffeeDomains.add(CoffeeDomain.builder()
.id(1)
.ingredients(List.of("Coffee", "Ice"))
.image("image_url_1")
.title("Iced Coffee").build());
coffeeDomains.add(CoffeeDomain.builder()
.id(2)
.ingredients(List.of("Espresso", "Milk"))
.image("image_url_2")
.title("Latte").build());
return coffeeDomains;
}

private Map<String, Object> getSampleResponse() {
Map<String, Object> response = new HashMap<>();
List<Map<String, Object>> allHots = new ArrayList<>();
List<Map<String, Object>> allIceds = new ArrayList<>();

Map<String, Object> hotCoffee = new HashMap<>();
hotCoffee.put("id", "1");
hotCoffee.put("ingredients", List.of("Coffee"));
hotCoffee.put("image", "image_url_1");
hotCoffee.put("title", "Black Coffee");
allHots.add(hotCoffee);

Map<String, Object> icedCoffee = new HashMap<>();
icedCoffee.put("id", "2");
icedCoffee.put("ingredients", List.of("Espresso", "Milk"));
icedCoffee.put("image", "image_url_2");
icedCoffee.put("title", "Latte");
allIceds.add(icedCoffee);

Map<String, Object> icedCoffee2 = new HashMap<>();
icedCoffee2.put("id", "3");
icedCoffee2.put("ingredients", "invalid_array");
icedCoffee2.put("image", "image_url_3");
icedCoffee2.put("title", "Ice Black");
allIceds.add(icedCoffee2);

response.put("allHots", allHots);
response.put("allIceds", allIceds);
return response;
}
}

0 comments on commit 8abf27c

Please sign in to comment.