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