From 2d49478bf1ca96bc5b1acab5b4ad79f2b1db893c Mon Sep 17 00:00:00 2001 From: Colin DAMON <cdamon@ippon.fr> Date: Thu, 3 Dec 2020 08:10:42 +0100 Subject: [PATCH] Expose activity WebService --- borestop/README.md | 6 ++-- .../ActivitesApplicationService.java | 20 +++++++++++ .../activity/domain/ActivitiesFactory.java | 18 ++++++++++ .../primary/ActivitesResource.java | 26 ++++++++++++++ .../infrastructure/primary/RestActivity.java | 35 ++++++++++++++++++ .../infrastructure/primary/RestPartner.java | 27 ++++++++++++++ .../secondary/InMemoryPartnersRepository.java | 19 ++++++++++ .../config/SecurityConfiguration.java | 1 + .../test/features/activity/activites.feature | 8 +++++ ...ersFixture.java => ActivitiesFixture.java} | 24 +++++++++++-- .../activity/domain/ActivitiesUnitTest.java | 7 +--- .../activity/domain/ActivityUnitTest.java | 13 ++----- .../activity/domain/IdeasFixture.java | 14 -------- .../primary/ActivitiesStep.java | 36 +++++++++++++++++++ .../primary/RestActivityUnitTest.java | 28 +++++++++++++++ .../cucumber/CucumberConfiguration.java | 25 ++++++++++++- 16 files changed, 269 insertions(+), 38 deletions(-) create mode 100644 borestop/src/main/java/com/ippon/borestop/activity/application/ActivitesApplicationService.java create mode 100644 borestop/src/main/java/com/ippon/borestop/activity/domain/ActivitiesFactory.java create mode 100644 borestop/src/main/java/com/ippon/borestop/activity/infrastructure/primary/ActivitesResource.java create mode 100644 borestop/src/main/java/com/ippon/borestop/activity/infrastructure/primary/RestActivity.java create mode 100644 borestop/src/main/java/com/ippon/borestop/activity/infrastructure/primary/RestPartner.java create mode 100644 borestop/src/main/java/com/ippon/borestop/activity/infrastructure/secondary/InMemoryPartnersRepository.java create mode 100644 borestop/src/test/features/activity/activites.feature rename borestop/src/test/java/com/ippon/borestop/activity/domain/{PartnersFixture.java => ActivitiesFixture.java} (55%) delete mode 100644 borestop/src/test/java/com/ippon/borestop/activity/domain/IdeasFixture.java create mode 100644 borestop/src/test/java/com/ippon/borestop/activity/infrastructure/primary/ActivitiesStep.java create mode 100644 borestop/src/test/java/com/ippon/borestop/activity/infrastructure/primary/RestActivityUnitTest.java diff --git a/borestop/README.md b/borestop/README.md index c19e3819..89bda0da 100644 --- a/borestop/README.md +++ b/borestop/README.md @@ -12,9 +12,7 @@ Création en live d'une application pour montrer l'apport de valeur pas différe ## Partie 2 -API utilisée : - -- https://www.boredapi.com/api/activity/ +Utilisation de [BoredAPI](https://www.boredapi.com/api/activity/) : - **Auteurs** : Hippolyte DURIX && Colin DAMON - **Date** : 02/09/2020 @@ -24,7 +22,7 @@ API utilisée : ## Partie 3 -Appel à boredapi avec des [patterns anti-fragiles](https://github.com/resilience4j/resilience4j) +Appel à [BoredAPI](https://www.boredapi.com/api/activity/) avec des [patterns anti-fragiles](https://github.com/resilience4j/resilience4j) - **Auteurs** : Hippolyte DURIX && Colin DAMON - **Date** : 18/11/2020 diff --git a/borestop/src/main/java/com/ippon/borestop/activity/application/ActivitesApplicationService.java b/borestop/src/main/java/com/ippon/borestop/activity/application/ActivitesApplicationService.java new file mode 100644 index 00000000..fc3f9149 --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/activity/application/ActivitesApplicationService.java @@ -0,0 +1,20 @@ +package com.ippon.borestop.activity.application; + +import com.ippon.borestop.activity.domain.ActivitiesFactory; +import com.ippon.borestop.activity.domain.Activity; +import com.ippon.borestop.activity.domain.IdeasRepository; +import com.ippon.borestop.activity.domain.PartnersRepository; +import org.springframework.stereotype.Service; + +@Service +public class ActivitesApplicationService { + private final ActivitiesFactory activities; + + public ActivitesApplicationService(IdeasRepository ideas, PartnersRepository partners) { + activities = new ActivitiesFactory(ideas, partners); + } + + public Activity next() { + return activities.next(); + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/activity/domain/ActivitiesFactory.java b/borestop/src/main/java/com/ippon/borestop/activity/domain/ActivitiesFactory.java new file mode 100644 index 00000000..c34c1ff5 --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/activity/domain/ActivitiesFactory.java @@ -0,0 +1,18 @@ +package com.ippon.borestop.activity.domain; + +public class ActivitiesFactory { + private final IdeasRepository ideas; + private final PartnersRepository partners; + + public ActivitiesFactory(IdeasRepository ideas, PartnersRepository partners) { + this.ideas = ideas; + this.partners = partners; + } + + public Activity next() { + Idea idea = ideas.next(); + Partners partners = this.partners.find(idea.getCategory()); + + return new Activity(idea, partners); + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/activity/infrastructure/primary/ActivitesResource.java b/borestop/src/main/java/com/ippon/borestop/activity/infrastructure/primary/ActivitesResource.java new file mode 100644 index 00000000..9349398a --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/activity/infrastructure/primary/ActivitesResource.java @@ -0,0 +1,26 @@ +package com.ippon.borestop.activity.infrastructure.primary; + +import com.ippon.borestop.activity.application.ActivitesApplicationService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +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 +@Api(tags = "Activities") +@RequestMapping("/api/activity") +class ActivitesResource { + private final ActivitesApplicationService activites; + + public ActivitesResource(ActivitesApplicationService activites) { + this.activites = activites; + } + + @GetMapping + @ApiOperation("Get an activity") + public ResponseEntity<RestActivity> getActivity() { + return ResponseEntity.ok(RestActivity.from(activites.next())); + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/activity/infrastructure/primary/RestActivity.java b/borestop/src/main/java/com/ippon/borestop/activity/infrastructure/primary/RestActivity.java new file mode 100644 index 00000000..8ca71e1d --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/activity/infrastructure/primary/RestActivity.java @@ -0,0 +1,35 @@ +package com.ippon.borestop.activity.infrastructure.primary; + +import com.ippon.borestop.activity.domain.Activity; +import io.swagger.annotations.ApiModel; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +@ApiModel(value = "Activity", description = "An ideal activity for you!") +class RestActivity { + private final String label; + private final Collection<RestPartner> partners; + + private RestActivity(String label, Collection<RestPartner> partners) { + this.label = label; + this.partners = partners; + } + + public static RestActivity from(Activity activity) { + if (activity == null) { + return null; + } + + List<RestPartner> partners = activity.getPartners().stream().map(RestPartner::from).collect(Collectors.toList()); + return new RestActivity(activity.getLabel(), partners); + } + + public String getLabel() { + return label; + } + + public Collection<RestPartner> getPartners() { + return partners; + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/activity/infrastructure/primary/RestPartner.java b/borestop/src/main/java/com/ippon/borestop/activity/infrastructure/primary/RestPartner.java new file mode 100644 index 00000000..3f17f7d0 --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/activity/infrastructure/primary/RestPartner.java @@ -0,0 +1,27 @@ +package com.ippon.borestop.activity.infrastructure.primary; + +import com.ippon.borestop.activity.domain.Partner; +import io.swagger.annotations.ApiModel; + +@ApiModel(value = "Partner", description = "Partner for an idea") +class RestPartner { + private final String name; + private final String website; + + private RestPartner(String name, String website) { + this.name = name; + this.website = website; + } + + static RestPartner from(Partner partner) { + return new RestPartner(partner.getName(), partner.getWebsite()); + } + + public String getName() { + return name; + } + + public String getWebsite() { + return website; + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/activity/infrastructure/secondary/InMemoryPartnersRepository.java b/borestop/src/main/java/com/ippon/borestop/activity/infrastructure/secondary/InMemoryPartnersRepository.java new file mode 100644 index 00000000..9404e50a --- /dev/null +++ b/borestop/src/main/java/com/ippon/borestop/activity/infrastructure/secondary/InMemoryPartnersRepository.java @@ -0,0 +1,19 @@ +package com.ippon.borestop.activity.infrastructure.secondary; + +import com.ippon.borestop.activity.domain.Category; +import com.ippon.borestop.activity.domain.Partner; +import com.ippon.borestop.activity.domain.Partners; +import com.ippon.borestop.activity.domain.PartnersRepository; +import org.springframework.stereotype.Service; + +@Service +class InMemoryPartnersRepository implements PartnersRepository { + + @Override + public Partners find(Category category) { + // TODO: partners by category + Partner beerHunter = Partner.builder().name("Beer Hunter").website("https://beerhunter.fr").build(); + + return Partners.builder().add(beerHunter).build(); + } +} diff --git a/borestop/src/main/java/com/ippon/borestop/config/SecurityConfiguration.java b/borestop/src/main/java/com/ippon/borestop/config/SecurityConfiguration.java index 364d50ce..353cad7d 100644 --- a/borestop/src/main/java/com/ippon/borestop/config/SecurityConfiguration.java +++ b/borestop/src/main/java/com/ippon/borestop/config/SecurityConfiguration.java @@ -86,6 +86,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { .antMatchers("/api/activate").permitAll() .antMatchers("/api/account/reset-password/init").permitAll() .antMatchers("/api/account/reset-password/finish").permitAll() + .antMatchers("/api/activity").permitAll() .antMatchers("/api/**").authenticated() .antMatchers("/management/health").permitAll() .antMatchers("/management/info").permitAll() diff --git a/borestop/src/test/features/activity/activites.feature b/borestop/src/test/features/activity/activites.feature new file mode 100644 index 00000000..c97473f5 --- /dev/null +++ b/borestop/src/test/features/activity/activites.feature @@ -0,0 +1,8 @@ +Feature: Get an activity + + Scenario: Should get beer activity + When I get an activity + Then My activity should be "Beer" + And My partners should be + | Name | Website | + | Beer Hunter | https://beerhunter.fr | diff --git a/borestop/src/test/java/com/ippon/borestop/activity/domain/PartnersFixture.java b/borestop/src/test/java/com/ippon/borestop/activity/domain/ActivitiesFixture.java similarity index 55% rename from borestop/src/test/java/com/ippon/borestop/activity/domain/PartnersFixture.java rename to borestop/src/test/java/com/ippon/borestop/activity/domain/ActivitiesFixture.java index b253194c..14a45bc6 100644 --- a/borestop/src/test/java/com/ippon/borestop/activity/domain/PartnersFixture.java +++ b/borestop/src/test/java/com/ippon/borestop/activity/domain/ActivitiesFixture.java @@ -1,8 +1,20 @@ package com.ippon.borestop.activity.domain; -public final class PartnersFixture { +public final class ActivitiesFixture { - private PartnersFixture() {} + private ActivitiesFixture() {} + + public static Activity activity() { + return new Activity(idea(), threePartners()); + } + + public static Idea idea() { + return new Idea(label(), Category.RELAXATION); + } + + public static String label() { + return "This is my idea"; + } public static Partners threePartners() { return Partners.builder().add(firstPartner()).add(secondPartner()).add(thirdPartner()).build(); @@ -19,4 +31,12 @@ public final class PartnersFixture { public static Partner firstPartner() { return Partner.builder().name("name").website("http://name.com").build(); } + + public static Partners partners() { + return threePartners(); + } + + public static Partners emptyPartners() { + return Partners.empty(); + } } diff --git a/borestop/src/test/java/com/ippon/borestop/activity/domain/ActivitiesUnitTest.java b/borestop/src/test/java/com/ippon/borestop/activity/domain/ActivitiesUnitTest.java index 285d1c6f..d2cae92b 100644 --- a/borestop/src/test/java/com/ippon/borestop/activity/domain/ActivitiesUnitTest.java +++ b/borestop/src/test/java/com/ippon/borestop/activity/domain/ActivitiesUnitTest.java @@ -1,11 +1,6 @@ package com.ippon.borestop.activity.domain; -import static com.ippon.borestop.activity.domain.IdeasFixture.idea; -import static com.ippon.borestop.activity.domain.IdeasFixture.label; -import static com.ippon.borestop.activity.domain.PartnersFixture.firstPartner; -import static com.ippon.borestop.activity.domain.PartnersFixture.secondPartner; -import static com.ippon.borestop.activity.domain.PartnersFixture.thirdPartner; -import static com.ippon.borestop.activity.domain.PartnersFixture.threePartners; +import static com.ippon.borestop.activity.domain.ActivitiesFixture.*; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.*; diff --git a/borestop/src/test/java/com/ippon/borestop/activity/domain/ActivityUnitTest.java b/borestop/src/test/java/com/ippon/borestop/activity/domain/ActivityUnitTest.java index bdcdaa12..6e7abbb0 100644 --- a/borestop/src/test/java/com/ippon/borestop/activity/domain/ActivityUnitTest.java +++ b/borestop/src/test/java/com/ippon/borestop/activity/domain/ActivityUnitTest.java @@ -1,7 +1,6 @@ package com.ippon.borestop.activity.domain; -import static com.ippon.borestop.activity.domain.IdeasFixture.*; -import static com.ippon.borestop.activity.domain.PartnersFixture.*; +import static com.ippon.borestop.activity.domain.ActivitiesFixture.*; import static org.assertj.core.api.Assertions.*; import com.ippon.borestop.common.domain.error.MissingMandatoryValueException; @@ -33,19 +32,11 @@ class ActivityUnitTest { @Test void shouldBuildActivityWithThreePartnersInCategory() { - Activity activity = new Activity(idea(), threePartners()); + Activity activity = activity(); assertThat(activity.getLabel()).isEqualTo(label()); assertThat(activity.getPartners()) .usingFieldByFieldElementComparator() .containsExactly(firstPartner(), secondPartner(), thirdPartner()); } - - private Partners partners() { - return threePartners(); - } - - private Partners emptyPartners() { - return Partners.empty(); - } } diff --git a/borestop/src/test/java/com/ippon/borestop/activity/domain/IdeasFixture.java b/borestop/src/test/java/com/ippon/borestop/activity/domain/IdeasFixture.java deleted file mode 100644 index f8ca164e..00000000 --- a/borestop/src/test/java/com/ippon/borestop/activity/domain/IdeasFixture.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.ippon.borestop.activity.domain; - -public final class IdeasFixture { - - private IdeasFixture() {} - - public static Idea idea() { - return new Idea(label(), Category.RELAXATION); - } - - public static String label() { - return "This is my idea"; - } -} diff --git a/borestop/src/test/java/com/ippon/borestop/activity/infrastructure/primary/ActivitiesStep.java b/borestop/src/test/java/com/ippon/borestop/activity/infrastructure/primary/ActivitiesStep.java new file mode 100644 index 00000000..6813205a --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/activity/infrastructure/primary/ActivitiesStep.java @@ -0,0 +1,36 @@ +package com.ippon.borestop.activity.infrastructure.primary; + +import static org.assertj.core.api.Assertions.*; + +import com.ippon.borestop.common.infrastructure.primary.CucumberTestContext; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import java.util.List; +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.TestRestTemplate; + +public class ActivitiesStep { + @Autowired + private TestRestTemplate rest; + + @When("I get an activity") + public void getActivity() { + rest.getForEntity("/api/activity", Void.class); + } + + @Then("My activity should be {string}") + public void shouldGetActivity(String activity) { + assertThat(CucumberTestContext.getElement("$.label")).isEqualTo(activity); + } + + @Then("My partners should be") + public void shouldGetPartners(List<Map<String, String>> partners) { + for (int line = 0; line < partners.size(); line++) { + String partnerPath = "$.partners[" + line + "]"; + + assertThat(CucumberTestContext.getElement(partnerPath + ".name")).isEqualTo(partners.get(line).get("Name")); + assertThat(CucumberTestContext.getElement(partnerPath + ".website")).isEqualTo(partners.get(line).get("Website")); + } + } +} diff --git a/borestop/src/test/java/com/ippon/borestop/activity/infrastructure/primary/RestActivityUnitTest.java b/borestop/src/test/java/com/ippon/borestop/activity/infrastructure/primary/RestActivityUnitTest.java new file mode 100644 index 00000000..8a3d9c2d --- /dev/null +++ b/borestop/src/test/java/com/ippon/borestop/activity/infrastructure/primary/RestActivityUnitTest.java @@ -0,0 +1,28 @@ +package com.ippon.borestop.activity.infrastructure.primary; + +import static com.ippon.borestop.activity.domain.ActivitiesFixture.*; +import static org.assertj.core.api.Assertions.*; + +import com.ippon.borestop.common.infrastructure.primary.TestJson; +import org.junit.jupiter.api.Test; + +class RestActivityUnitTest { + + @Test + void shouldConvertToNullFromNullDomain() { + assertThat(RestActivity.from(null)).isNull(); + } + + @Test + void shouldSerializeToJson() { + assertThat(defaultActivity()).isEqualTo(json()); + } + + private String defaultActivity() { + return TestJson.writeAsString(RestActivity.from(activity())); + } + + private String json() { + return "{\"label\":\"This is my idea\",\"partners\":[{\"name\":\"name\",\"website\":\"http://name.com\"},{\"name\":\"second\",\"website\":\"http://second.com\"},{\"name\":\"third\",\"website\":\"http://third.com\"}]}"; + } +} diff --git a/borestop/src/test/java/com/ippon/borestop/cucumber/CucumberConfiguration.java b/borestop/src/test/java/com/ippon/borestop/cucumber/CucumberConfiguration.java index 3a758d7c..a1b4e9e5 100644 --- a/borestop/src/test/java/com/ippon/borestop/cucumber/CucumberConfiguration.java +++ b/borestop/src/test/java/com/ippon/borestop/cucumber/CucumberConfiguration.java @@ -1,7 +1,13 @@ package com.ippon.borestop.cucumber; +import static org.mockito.Mockito.*; + import com.ippon.borestop.BorestopApp; +import com.ippon.borestop.activity.domain.Category; +import com.ippon.borestop.activity.domain.Idea; +import com.ippon.borestop.activity.domain.IdeasRepository; import com.ippon.borestop.common.infrastructure.primary.CucumberTestContext; +import com.ippon.borestop.cucumber.CucumberConfiguration.CucumberMocksConfiguration; import com.ippon.borestop.cucumber.CucumberConfiguration.CucumberSecurityContextConfiguration; import io.cucumber.java.Before; import io.cucumber.spring.CucumberContextConfiguration; @@ -28,7 +34,10 @@ import org.springframework.web.client.RestTemplate; @CucumberContextConfiguration @ActiveProfiles(JHipsterConstants.SPRING_PROFILE_TEST) -@SpringBootTest(classes = { BorestopApp.class, CucumberSecurityContextConfiguration.class }, webEnvironment = WebEnvironment.RANDOM_PORT) +@SpringBootTest( + classes = { BorestopApp.class, CucumberSecurityContextConfiguration.class, CucumberMocksConfiguration.class }, + webEnvironment = WebEnvironment.RANDOM_PORT +) public class CucumberConfiguration { @Autowired private TestRestTemplate rest; @@ -61,6 +70,20 @@ public class CucumberConfiguration { }; } + @TestConfiguration + public static class CucumberMocksConfiguration { + + @Bean + @Primary + public IdeasRepository ideasRepository() { + IdeasRepository repository = mock(IdeasRepository.class); + + when(repository.next()).thenReturn(new Idea("Beer", Category.RELAXATION)); + + return repository; + } + } + @TestConfiguration public static class CucumberSecurityContextConfiguration { -- GitLab