Skip to content
Snippets Groups Projects
Commit 4db6946e authored by Hippolyte DURIX's avatar Hippolyte DURIX Committed by Colin DAMON
Browse files

Advancing in kata implementation

parent 32d3a134
No related branches found
No related tags found
1 merge request!25Resolve "Bored API integration"
Showing
with 362 additions and 21 deletions
...@@ -10,6 +10,28 @@ Création en live d'une application pour montrer l'apport de valeur pas différe ...@@ -10,6 +10,28 @@ Création en live d'une application pour montrer l'apport de valeur pas différe
- **Niveau** : Débutant - **Niveau** : Débutant
- **Replay** : [La pyramide de tests de Kheops (partie 1) - Hippolyte et Colin](https://www.youtube.com/watch?v=rfRgJk251pw) - **Replay** : [La pyramide de tests de Kheops (partie 1) - Hippolyte et Colin](https://www.youtube.com/watch?v=rfRgJk251pw)
## Partie 2
API utilisée :
- https://www.boredapi.com/api/activity/
- **Auteurs** : Hippolyte DURIX && Colin DAMON
- **Date** : 02/09/2020
- **Langage** : Java
- **Niveau** : Moyen
- **Replay** : [Appeler une API externe avec Hippolyte et Colin](https://www.youtube.com/watch?v=E-5mrsesZHk)
## Partie 3
Appel à boredapi avec des [patterns anti-fragiles](https://github.com/resilience4j/resilience4j)
- **Auteurs** : Hippolyte DURIX && Colin DAMON
- **Date** : 18/11/2020
- **Langage** : Java
- **Niveau** : Moyen
- **Replay** : TODO
## JHipster ## JHipster
This application was generated using JHipster 6.10.1, you can find documentation and help at [https://www.jhipster.tech/documentation-archive/v6.10.1](https://www.jhipster.tech/documentation-archive/v6.10.1). This application was generated using JHipster 6.10.1, you can find documentation and help at [https://www.jhipster.tech/documentation-archive/v6.10.1](https://www.jhipster.tech/documentation-archive/v6.10.1).
......
package com.ippon.borestop.activity.domain; package com.ippon.borestop.activity.domain;
public enum Category { public enum Category {
RELAXATION COOKING,
RELAXATION,
DEFAULT
} }
package com.ippon.borestop.activity.infrastructure.secondary;
import com.ippon.borestop.activity.domain.Category;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
enum BoredApiCategory {
COOKING("cooking"),
RELAXATION("relaxation"),
DEFAULT("default");
private static final Map<String, BoredApiCategory> categories = buildCategories();
private static Map<String, BoredApiCategory> buildCategories() {
return Arrays.stream(values()).collect(Collectors.toMap(BoredApiCategory::getLabel, Function.identity()));
}
private final String label;
BoredApiCategory(String label) {
this.label = label;
}
private String getLabel() {
return label;
}
static BoredApiCategory from(String type) {
return categories.getOrDefault(StringUtils.lowerCase(type), DEFAULT);
}
Category toDomain() {
return Category.valueOf(name());
}
}
package com.ippon.borestop.activity.infrastructure.secondary;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(BoredApiProperties.class)
class BoredApiConfiguration {}
package com.ippon.borestop.activity.infrastructure.secondary;
import com.ippon.borestop.activity.domain.Category;
import com.ippon.borestop.activity.domain.Idea;
import com.ippon.borestop.activity.domain.IdeasRepository;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
@Service
class BoredApiIdeasRepository implements IdeasRepository {
private final RestTemplate rest;
private final String url;
BoredApiIdeasRepository(RestTemplateBuilder restBuilder, BoredApiProperties properties) {
this.rest = restBuilder.setConnectTimeout(properties.getConnectTimeout()).setReadTimeout(properties.getReadTimeout()).build();
url = properties.getUrl();
}
@Override
public Idea next() {
try {
BoredApiResponse apiResponse = rest.getForEntity(url, BoredApiResponse.class).getBody();
return apiResponse.toDomain();
} catch (RestClientException e) {
return new Idea("Go grab a beer", Category.RELAXATION);
}
}
}
package com.ippon.borestop.activity.infrastructure.secondary;
import java.time.Duration;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@Validated
@ConfigurationProperties("bored-api")
class BoredApiProperties {
private String url;
private Duration connectTimeout;
private Duration readTimeout;
@NotBlank
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@NotNull
public Duration getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(Duration connectTimeout) {
this.connectTimeout = connectTimeout;
}
@NotNull
public Duration getReadTimeout() {
return readTimeout;
}
public void setReadTimeout(Duration readTimeout) {
this.readTimeout = readTimeout;
}
}
package com.ippon.borestop.activity.infrastructure.secondary;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ippon.borestop.activity.domain.Idea;
class BoredApiResponse {
private final String activity;
private final String type;
public BoredApiResponse(@JsonProperty("activity") String activity, @JsonProperty("type") String type) {
this.activity = activity;
this.type = type;
}
public String getActivity() {
return activity;
}
public String getType() {
return type;
}
public Idea toDomain() {
return new Idea(this.activity, BoredApiCategory.from(type).toDomain());
}
}
package com.ippon.borestop.activity.infrastructure.secondary;
import com.ippon.borestop.activity.domain.Idea;
import com.ippon.borestop.activity.domain.IdeasRepository;
import org.springframework.stereotype.Service;
@Service
class RestIdeasRepository implements IdeasRepository {
@Override
public Idea next() {
// TODO Auto-generated method stub
return null;
}
}
...@@ -169,3 +169,8 @@ jhipster: ...@@ -169,3 +169,8 @@ jhipster:
# =================================================================== # ===================================================================
# application: # application:
bored-api:
url: 'https://www.boredapi.com/api/activity/'
connect-timeout: 500ms
read-timeout: 1s
package com.ippon.borestop.activity.infrastructure.secondary;
import static org.assertj.core.api.Assertions.*;
import com.ippon.borestop.activity.domain.Category;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
class BoredApiCategoryUnitTest {
@Test
void shouldHaveValueForAllDomainCategories() {
Set<Category> mappedCategories = Arrays.stream(BoredApiCategory.values()).map(BoredApiCategory::toDomain).collect(Collectors.toSet());
assertThat(mappedCategories).containsExactlyInAnyOrder(Category.values());
}
@Test
void shouldConvertFromType() {
assertThat(BoredApiCategory.from("Cooking")).isEqualTo(BoredApiCategory.COOKING);
}
@Test
void shouldConvertFromUnknownType() {
assertThat(BoredApiCategory.from("unknown")).isEqualTo(BoredApiCategory.DEFAULT);
}
}
package com.ippon.borestop.activity.infrastructure.secondary;
import static com.ippon.borestop.activity.domain.Category.*;
import static org.assertj.core.api.Assertions.*;
import com.ippon.borestop.activity.domain.Idea;
import com.ippon.borestop.common.BorestopIntTest;
import java.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.server.LocalServerPort;
@BorestopIntTest
class BoredApiIdeasRepositoryCommunicationIntTest {
@LocalServerPort
private int port;
@Autowired
private FakeBoredApi fake;
@Autowired
private RestTemplateBuilder restBuilder;
private BoredApiIdeasRepository repository;
@BeforeEach
void loadRepository() {
fake.reset();
repository = new BoredApiIdeasRepository(restBuilder, properties());
}
private BoredApiProperties properties() {
BoredApiProperties properties = new BoredApiProperties();
properties.setUrl("http://localhost:" + port + "/fake-bored-api");
properties.setConnectTimeout(Duration.ofMillis(10));
properties.setReadTimeout(Duration.ofMillis(50));
return properties;
}
@Test
void shouldGracefullyHandleTimeouts() {
fake.slowDown();
Idea idea = repository.next();
assertThat(idea.getLabel()).isEqualTo("Go grab a beer");
assertThat(idea.getCategory()).isEqualTo(RELAXATION);
}
}
package com.ippon.borestop.activity.infrastructure.secondary;
import static org.assertj.core.api.Assertions.*;
import com.ippon.borestop.activity.domain.Idea;
import com.ippon.borestop.activity.domain.IdeasRepository;
import com.ippon.borestop.common.BorestopIntTest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@BorestopIntTest
class BoredApiIdeasRepositoryIntTest {
@Autowired
private IdeasRepository ideasRepository;
@Test
void shouldGetNextIdea() {
Idea idea = ideasRepository.next();
assertThat(idea.getCategory()).isNotNull();
assertThat(idea.getLabel()).isNotNull();
}
}
package com.ippon.borestop.activity.infrastructure.secondary;
import com.ippon.borestop.activity.domain.Category;
import com.ippon.borestop.activity.domain.Idea;
import com.ippon.borestop.common.infrastructure.primary.TestJson;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
class BoredApiResponseUnitTest {
@Test
void shouldDeserializeFromJson() {
BoredApiResponse response = TestJson.readFromJson(json(), BoredApiResponse.class);
assertThat(response.getActivity()).isEqualTo("Cook something together with someone");
assertThat(response.getType()).isEqualTo("cooking");
}
@Test
void shouldConvertToDomain() {
BoredApiResponse response = new BoredApiResponse("activity", "cooking");
Idea idea = response.toDomain();
assertThat(idea.getLabel()).isEqualTo("activity");
assertThat(idea.getCategory()).isEqualTo(Category.COOKING);
}
private String json() {
return "{\n" +
" \"activity\": \"Cook something together with someone\",\n" +
" \"type\": \"cooking\",\n" +
" \"participants\": 2,\n" +
" \"price\": 0.3,\n" +
" \"link\": \"\",\n" +
" \"key\": \"1799120\",\n" +
" \"accessibility\": 0.8\n" +
"}";
}
}
package com.ippon.borestop.activity.infrastructure.secondary;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/fake-bored-api")
class FakeBoredApi {
private boolean slow;
void reset() {
slow = false;
}
void slowDown() {
slow = true;
}
@GetMapping
ResponseEntity<BoredApiResponse> getResponse() throws InterruptedException {
if (slow) {
Thread.sleep(500);
}
BoredApiResponse response = new BoredApiResponse("Hey", "cooking");
return ResponseEntity.ok(response);
}
}
...@@ -2,16 +2,15 @@ package com.ippon.borestop.common; ...@@ -2,16 +2,15 @@ package com.ippon.borestop.common;
import com.ippon.borestop.BorestopApp; import com.ippon.borestop.BorestopApp;
import io.github.jhipster.config.JHipsterConstants; import io.github.jhipster.config.JHipsterConstants;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.ActiveProfiles;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import javax.transaction.Transactional;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.ActiveProfiles;
@Transactional
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@ActiveProfiles(JHipsterConstants.SPRING_PROFILE_TEST) @ActiveProfiles(JHipsterConstants.SPRING_PROFILE_TEST)
......
...@@ -115,3 +115,8 @@ jhipster: ...@@ -115,3 +115,8 @@ jhipster:
# =================================================================== # ===================================================================
# application: # application:
bored-api:
url: 'https://www.boredapi.com/api/activity/'
connect-timeout: 500ms
read-timeout: 1s
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment