diff --git a/build-all.sh b/build-all.sh index 97feb72..e7a5706 100755 --- a/build-all.sh +++ b/build-all.sh @@ -88,6 +88,7 @@ then # (add new modules above the rest so you get quicker feedback if it fails) build maven_module "http-clients" build maven_module "spring-boot/spring-boot-i18n" + build_maven_module "testing/assertJ" build maven_module "spring-boot/spring-boot-scheduler" build maven_module "aws/springcloudwatch" build maven_module "aws/springcloudses" diff --git a/resilience4j/springboot-resilience4j/README.md b/resilience4j/springboot-resilience4j/README.md index 1c89564..7777d31 100644 --- a/resilience4j/springboot-resilience4j/README.md +++ b/resilience4j/springboot-resilience4j/README.md @@ -6,3 +6,4 @@ Run the SpringbootResilience4jApplication program * [Implementing Retry with Spring_Boot_Resilience4j](https://reflectoring.io/retry-with-springboot-resilience4j/) * [Implementing Rate Limiting with Spring_Boot_Resilience4j](https://reflectoring.io/rate-limiting-with-springboot-resilience4j/) +* [Implementing Time Limiting with Spring_Boot_Resilience4j](https://reflectoring.io/time-limiting-with-springboot-resilience4j/) diff --git a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/SpringbootResilience4jApplication.java b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/SpringbootResilience4jApplication.java index b0faf50..0dcf7c0 100644 --- a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/SpringbootResilience4jApplication.java +++ b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/SpringbootResilience4jApplication.java @@ -14,6 +14,9 @@ public class SpringbootResilience4jApplication { @Autowired private RateLimiterExamplesRunner rateLimiterExamplesRunner; + @Autowired + private TimeLimiterExamplesRunner timeLimiterExamplesRunner; + public static void main(String[] args) { SpringApplication.run(SpringbootResilience4jApplication.class, args); } @@ -22,5 +25,6 @@ public class SpringbootResilience4jApplication { public void runExamples() { retryExamplesRunner.run(); rateLimiterExamplesRunner.run(); + timeLimiterExamplesRunner.run(); } } \ No newline at end of file diff --git a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/TimeLimiterExamplesRunner.java b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/TimeLimiterExamplesRunner.java new file mode 100644 index 0000000..de4412f --- /dev/null +++ b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/TimeLimiterExamplesRunner.java @@ -0,0 +1,142 @@ +package io.reflectoring.resilience4j.springboot; + +import io.reflectoring.resilience4j.springboot.model.Flight; +import io.reflectoring.resilience4j.springboot.model.SearchRequest; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class TimeLimiterExamplesRunner { + + @Autowired + private TimeLimitingService service; + + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss SSS"); + + public static void main(String[] args) { + TimeLimiterExamplesRunner runner = new TimeLimiterExamplesRunner(); + runner.run(); + } + + static void delay(int seconds) { + // sleep to simulate delay + try { + Thread.sleep(seconds * 1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void run() { + System.out.println("Running timelimiter examples"); + + System.out.println( + "----------------------- basicExample ----------------------------------------------------"); + basicExample(); + + delay(2); // delay just to let the above async operation to complete + + System.out.println( + "----------------------------------------------------------------------------------------------------"); + + System.out.println("----------------------- timeoutExample ----------------------------------------------"); + timeoutExample(); + + delay(2); // delay just to let the above async operation to complete + + System.out.println("----------------------------------------------------------------------------------------------------"); + + System.out.println("----------------------- fallbackExample ----------------------------------------------"); + fallbackExample(); + + delay(2); // delay just to let the above async operation to complete + + System.out.println("----------------------------------------------------------------------------------------------------"); + + System.out.println( + "----------------------- eventsExample ----------------------------------------------------"); + eventsExample(); + delay(10); // delay just to let the above async operation to complete + System.out.println( + "----------------------------------------------------------------------------------------------------"); + } + + private void eventsExample() { + SearchRequest request = new SearchRequest("NYC", "LAX", "10/30/2021"); + for (int i = 0; i < 10; i++) { + int attempt = i; + service.eventsExample(request) + .whenComplete((r, t) -> { + if (t != null) { + System.out.println("Error occurred on search " + attempt + ": " + t.getMessage()); + } + if (r != null) { + System.out + .println("Search " + attempt + " successful, found " + r.size() + " flights"); + } + }); + } + } + + private void timeoutExample() { + SearchRequest request = new SearchRequest("NYC", "LAX", "10/30/2021"); + System.out.println("Calling search; current thread = " + Thread.currentThread().getName()); + CompletableFuture> results = service.timeoutExample(request); + results.whenComplete((result, ex) -> { + if (ex != null) { + System.out.println("Exception " + + ex.getMessage() + + " on thread " + + Thread.currentThread().getName() + + " at " + + LocalDateTime.now().format(formatter)); + ex.printStackTrace(); + } + if (result != null) { + System.out.println(result + " on thread " + Thread.currentThread().getName()); + } + }); + } + + private void basicExample() { + SearchRequest request = new SearchRequest("NYC", "LAX", "10/30/2021"); + System.out.println("Calling search; current thread = " + Thread.currentThread().getName()); + CompletableFuture> results = service.basicExample(request); + results.whenComplete((result, ex) -> { + if (ex != null) { + System.out.println("Exception " + + ex.getMessage() + + " on thread " + + Thread.currentThread().getName() + + " at " + + LocalDateTime.now().format(formatter)); + } + if (result != null) { + System.out.println(result + " on thread " + Thread.currentThread().getName()); + } + }); + } + + private void fallbackExample() { + SearchRequest request = new SearchRequest("NYC", "LAX", "10/30/2021"); + System.out.println("Calling search; current thread = " + Thread.currentThread().getName()); + CompletableFuture> results = service.fallbackExample(request); + results.whenComplete((result, ex) -> { + if (ex != null) { + System.out.println("Exception " + + ex.getMessage() + + " on thread " + + Thread.currentThread().getName() + + " at " + + LocalDateTime.now().format(formatter)); + } + if (result != null) { + System.out.println(result + " on thread " + Thread.currentThread().getName()); + } + }); + } +} \ No newline at end of file diff --git a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/TimeLimitingService.java b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/TimeLimitingService.java new file mode 100644 index 0000000..79c681a --- /dev/null +++ b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/TimeLimitingService.java @@ -0,0 +1,167 @@ +package io.reflectoring.resilience4j.springboot; + +import io.github.resilience4j.micrometer.tagged.TaggedTimeLimiterMetrics; +import io.github.resilience4j.ratelimiter.RequestNotPermitted; +import io.github.resilience4j.ratelimiter.annotation.RateLimiter; +import io.github.resilience4j.timelimiter.TimeLimiter.EventPublisher; +import io.github.resilience4j.timelimiter.TimeLimiterConfig; +import io.github.resilience4j.timelimiter.TimeLimiterRegistry; +import io.github.resilience4j.timelimiter.annotation.TimeLimiter; +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.reflectoring.resilience4j.springboot.model.Flight; +import io.reflectoring.resilience4j.springboot.model.SearchRequest; +import io.reflectoring.resilience4j.springboot.services.FlightSearchService; +import java.sql.Time; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.StreamSupport; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class TimeLimitingService { + @Autowired + private FlightSearchService remoteSearchService; + + @Autowired + private TimeLimiterRegistry timeLimiterRegistry; + +/* + void printDefaultValues() { + TimeLimiterConfig config = TimeLimiterConfig.ofDefaults(); + + System.out.println( + "getTimeoutDuration in ms = " + Duration.from(config.getTimeoutDuration()).toMillis()); + System.out.println("shouldCancelRunningFuture = " + config.shouldCancelRunningFuture()); + } */ + + + @TimeLimiter(name = "basicExample") + CompletableFuture> basicExample(SearchRequest request) { + return CompletableFuture.supplyAsync(() -> remoteSearchService.searchFlightsTakingOneSecond(request)); + } + + @TimeLimiter(name = "timeoutExample") + CompletableFuture> timeoutExample(SearchRequest request) { + return CompletableFuture.supplyAsync(() -> remoteSearchService.searchFlightsTakingOneSecond(request)); + } + + @TimeLimiter(name = "timeAndRateLimiter") + @RateLimiter(name = "timeAndRateLimiter") + CompletableFuture> aspectOrderExample(SearchRequest request) { + return CompletableFuture.supplyAsync(() -> remoteSearchService.searchFlightsTakingOneSecond(request)); + } + + /* + void basicExample_ExcecuteCompletionStage() { + TimeLimiterConfig config = TimeLimiterConfig.custom() + .timeoutDuration(Duration.ofMillis(500)) + .build(); + + TimeLimiterRegistry registry = TimeLimiterRegistry.of(config); + TimeLimiter limiter = registry.timeLimiter("flightSearch"); + + FlightSearchService service = new FlightSearchService(); + SearchRequest request = new SearchRequest("NYC", "LAX", "08/30/2020"); + + Supplier> flightSupplier = () -> service.searchFlightsTakingOneSecond(request); + Supplier>> origCompletionStageSupplier = () -> CompletableFuture + .supplyAsync(flightSupplier); + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + CompletionStage> decoratedCompletionStage = limiter + .executeCompletionStage(scheduler, origCompletionStageSupplier); + + decoratedCompletionStage.whenComplete((result, ex) -> { + if (ex != null) { + System.out.println("Exception " + + ex.getMessage() + + " on thread " + + Thread.currentThread().getName() + + " at " + + LocalDateTime.now().format(formatter)); + } + if (result != null) { + System.out.println(result + " on thread " + Thread.currentThread().getName()); + } + }); + + scheduler.shutdown(); + } + + + void whenToUseExample() { + CompletableFuture.supplyAsync(this::slowMethod).thenAccept(System.out::println); + } + + void whenToUseExample_Blocking() + throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture completableFuture = CompletableFuture + .supplyAsync(this::slowMethod); + Integer result = completableFuture.get(3000, TimeUnit.MILLISECONDS); + System.out.println(result); + } + + int slowMethod() { + System.out.println(Thread.currentThread().getName()); + // sleep to simulate delay + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return 0; + } + + static void delay(int seconds) { + // sleep to simulate delay + try { + Thread.sleep(seconds * 1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } */ + + @TimeLimiter(name = "eventsExample") + CompletableFuture> eventsExample(SearchRequest request) { + return CompletableFuture.supplyAsync(() -> remoteSearchService.searchFlightsTakingRandomTime(request)); + } + + @TimeLimiter(name = "fallbackExample", fallbackMethod = "localCacheFlightSearch") + CompletableFuture> fallbackExample(SearchRequest request) { + return CompletableFuture.supplyAsync(() -> remoteSearchService.searchFlightsTakingOneSecond(request)); + } + + private CompletableFuture> localCacheFlightSearch(SearchRequest request, TimeoutException rnp) { + System.out.println("Returning search results from cache"); + System.out.println(rnp.getMessage()); + CompletableFuture> result = new CompletableFuture<>(); + result.complete(Arrays.asList( + new Flight("XY 765", request.getFlightDate(), request.getFrom(), request.getTo()), + new Flight("XY 781", request.getFlightDate(), request.getFrom(), request.getTo()))); + return result; + } + + @PostConstruct + void postConstruct() { + EventPublisher eventPublisher = timeLimiterRegistry.timeLimiter("eventsExample").getEventPublisher(); + eventPublisher.onSuccess(System.out::println); + eventPublisher.onError(System.out::println); + eventPublisher.onTimeout(System.out::println); + } +} \ No newline at end of file diff --git a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/services/FlightSearchService.java b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/services/FlightSearchService.java index 973ecc6..69d380d 100644 --- a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/services/FlightSearchService.java +++ b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/services/FlightSearchService.java @@ -13,6 +13,7 @@ import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Random; import org.springframework.stereotype.Service; @Service @@ -20,6 +21,7 @@ public class FlightSearchService { PotentialFailure potentialFailure = new NoFailure(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss SSS"); + Random random = new Random(); PotentialFailureCheckedException potentialFailureCheckedException = new NoCheckedExceptionFailure(); @@ -108,4 +110,42 @@ public class FlightSearchService { response.setFlights(flights); return response; } + + public List searchFlightsTakingOneSecond(SearchRequest request) { + System.out.println("Searching for flights; " + + "current time = " + LocalDateTime.now().format(formatter) + + "; current thread = " + Thread.currentThread().getName()); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + List flights = Arrays.asList( + new Flight("XY 765", request.getFlightDate(), request.getFrom(), request.getTo()), + new Flight("XY 746", request.getFlightDate(), request.getFrom(), request.getTo()) + ); + System.out.println("Flight search successful at " + LocalDateTime.now().format(formatter)); + return flights; + } + + public List searchFlightsTakingRandomTime(SearchRequest request) { + long delay = random.nextInt(3000); + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("Searching for flights; " + + "current time = " + LocalDateTime.now().format(formatter) + + "; current thread = " + Thread.currentThread().getName()); + + List flights = Arrays.asList( + new Flight("XY 765", request.getFlightDate(), request.getFrom(), request.getTo()), + new Flight("XY 746", request.getFlightDate(), request.getFrom(), request.getTo()) + ); + System.out.println("Flight search successful"); + return flights; + } } \ No newline at end of file diff --git a/resilience4j/springboot-resilience4j/src/main/resources/application.yml b/resilience4j/springboot-resilience4j/src/main/resources/application.yml index 729a76f..9fb055a 100644 --- a/resilience4j/springboot-resilience4j/src/main/resources/application.yml +++ b/resilience4j/springboot-resilience4j/src/main/resources/application.yml @@ -99,6 +99,25 @@ resilience4j: limitRefreshPeriod: 1s timeoutDuration: 500ms + timelimiter: + instances: + + # TimeLimiter object used in TimeLimitingService.basicExample() + basicExample: + timeoutDuration: 2s + + # TimeLimiter object used in TimeLimitingService.timeoutExample() + timeoutExample: + timeoutDuration: 500ms + + # TimeLimiter object used in TimeLimitingService.eventsExample() + eventsExample: + timeoutDuration: 2s + + # TimeLimiter object used in TimeLimitingService.fallbackExample() + fallbackExample: + timeoutDuration: 500ms + management: endpoints: web: diff --git a/spring-boot/feature-flags/pom.xml b/spring-boot/feature-flags/pom.xml index 7c1bf86..3e864ca 100644 --- a/spring-boot/feature-flags/pom.xml +++ b/spring-boot/feature-flags/pom.xml @@ -21,10 +21,18 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-jdbc + org.springframework.boot spring-boot-starter-thymeleaf + + com.h2database + h2 + diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/FeatureFlagService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/FeatureFlagService.java index 50157a4..2cdaa9a 100644 --- a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/FeatureFlagService.java +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/FeatureFlagService.java @@ -22,4 +22,6 @@ public interface FeatureFlagService { */ Boolean isUserActionTargetedFeatureActive(); + Boolean isNewServiceEnabled(); + } diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/ff4j/FF4JFeatureFlagService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/ff4j/FF4JFeatureFlagService.java index 023c1d7..18c3fe0 100644 --- a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/ff4j/FF4JFeatureFlagService.java +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/ff4j/FF4JFeatureFlagService.java @@ -33,4 +33,9 @@ public class FF4JFeatureFlagService implements FeatureFlagService { return null; } + @Override + public Boolean isNewServiceEnabled() { + return null; + } + } diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/FeatureFlagService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/FeatureFlagService.java new file mode 100644 index 0000000..f6e5ae1 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/FeatureFlagService.java @@ -0,0 +1,9 @@ +package io.reflectoring.featureflags.implementations; + +public interface FeatureFlagService { + + Boolean featureOne(); + + Integer featureTwo(); + +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/code/CodeBackedFeatureFlagService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/code/CodeBackedFeatureFlagService.java new file mode 100644 index 0000000..aaeef0a --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/code/CodeBackedFeatureFlagService.java @@ -0,0 +1,15 @@ +package io.reflectoring.featureflags.implementations.code; + +import io.reflectoring.featureflags.implementations.FeatureFlagService; + +public class CodeBackedFeatureFlagService implements FeatureFlagService { + @Override + public Boolean featureOne() { + return true; + } + + @Override + public Integer featureTwo() { + return 42; + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/contextsensitive/ContextSensitiveFeatureFlagService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/contextsensitive/ContextSensitiveFeatureFlagService.java new file mode 100644 index 0000000..0f224fe --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/contextsensitive/ContextSensitiveFeatureFlagService.java @@ -0,0 +1,52 @@ +package io.reflectoring.featureflags.implementations.contextsensitive; + +import io.reflectoring.featureflags.implementations.FeatureFlagService; +import io.reflectoring.featureflags.implementations.contextsensitive.Feature.RolloutStrategy; +import io.reflectoring.featureflags.web.UserSession; +import org.jetbrains.annotations.Nullable; +import org.springframework.jdbc.core.JdbcTemplate; + +public class ContextSensitiveFeatureFlagService implements FeatureFlagService { + + private final JdbcTemplate jdbcTemplate; + private final UserSession userSession; + + public ContextSensitiveFeatureFlagService(JdbcTemplate jdbcTemplate, UserSession userSession) { + this.jdbcTemplate = jdbcTemplate; + this.userSession = userSession; + } + + @Override + public Boolean featureOne() { + Feature feature = getFeatureFromDatabase(); + if (feature == null) { + return Boolean.FALSE; + } + return feature.evaluateBoolean(userSession.getUsername()); + } + + @Override + public Integer featureTwo() { + Feature feature = getFeatureFromDatabase(); + if (feature == null) { + return null; + } + return feature.evaluateInt(userSession.getUsername()); + } + + @Nullable + private Feature getFeatureFromDatabase() { + return jdbcTemplate.query("select targeting, value, defaultValue, percentage from features where feature_key='FEATURE_ONE'", resultSet -> { + if (!resultSet.next()) { + return null; + } + + RolloutStrategy rolloutStrategy = Enum.valueOf(RolloutStrategy.class, resultSet.getString(1)); + String value = resultSet.getString(2); + String defaultValue = resultSet.getString(3); + int percentage = resultSet.getInt(4); + + return new Feature(rolloutStrategy, value, defaultValue, percentage); + }); + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/contextsensitive/Feature.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/contextsensitive/Feature.java new file mode 100644 index 0000000..b1d74e7 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/contextsensitive/Feature.java @@ -0,0 +1,102 @@ +package io.reflectoring.featureflags.implementations.contextsensitive; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +public class Feature { + + public enum RolloutStrategy { + GLOBAL, + PERCENTAGE; + } + + private final RolloutStrategy rolloutStrategy; + + private final int percentage; + private final String value; + private final String defaultValue; + + public Feature(RolloutStrategy rolloutStrategy, String value, String defaultValue, int percentage) { + this.rolloutStrategy = rolloutStrategy; + this.percentage = percentage; + this.value = value; + this.defaultValue = defaultValue; + } + + public boolean evaluateBoolean(String userId) { + switch (this.rolloutStrategy) { + case GLOBAL: + return this.getBooleanValue(); + case PERCENTAGE: + if (percentageHashCode(userId) <= this.percentage) { + return this.getBooleanValue(); + } else { + return this.getBooleanDefaultValue(); + } + } + + return this.getBooleanDefaultValue(); + } + + public Integer evaluateInt(String userId) { + switch (this.rolloutStrategy) { + case GLOBAL: + return this.getIntValue(); + case PERCENTAGE: + if (percentageHashCode(userId) <= this.percentage) { + return this.getIntValue(); + } else { + return this.getIntDefaultValue(); + } + } + + return this.getIntDefaultValue(); + } + + double percentageHashCode(String text) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] encodedhash = digest.digest( + text.getBytes(StandardCharsets.UTF_8)); + double INTEGER_RANGE = 1L << 32; + return (((long) Arrays.hashCode(encodedhash) - Integer.MIN_VALUE) / INTEGER_RANGE) * 100; + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + } + + public RolloutStrategy getTargeting() { + return rolloutStrategy; + } + + public int getPercentage() { + return percentage; + } + + public int getIntValue() { + return Integer.parseInt(this.value); + } + + public int getIntDefaultValue() { + return Integer.parseInt(this.defaultValue); + } + + + public boolean getBooleanValue() { + return Boolean.parseBoolean(this.value); + } + + public boolean getBooleanDefaultValue() { + return Boolean.parseBoolean(this.defaultValue); + } + + public String getStringValue() { + return value; + } + + public String getDefaultValue() { + return defaultValue; + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/database/DatabaseBackedFeatureFlagService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/database/DatabaseBackedFeatureFlagService.java new file mode 100644 index 0000000..2b4cb8b --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/database/DatabaseBackedFeatureFlagService.java @@ -0,0 +1,32 @@ +package io.reflectoring.featureflags.implementations.database; + +import io.reflectoring.featureflags.implementations.FeatureFlagService; +import org.springframework.jdbc.core.JdbcTemplate; + +public class DatabaseBackedFeatureFlagService implements FeatureFlagService { + + private JdbcTemplate jdbcTemplate; + + @Override + public Boolean featureOne() { + return jdbcTemplate.query("select value from features where feature_key='FEATURE_ONE'", resultSet -> { + if(!resultSet.next()){ + return false; + } + + boolean value = Boolean.parseBoolean(resultSet.getString(1)); + return value ? Boolean.TRUE : Boolean.FALSE; + }); + } + + @Override + public Integer featureTwo() { + return jdbcTemplate.query("select value from features where feature_key='FEATURE_TWO'", resultSet -> { + if(!resultSet.next()){ + return null; + } + + return Integer.valueOf(resultSet.getString(1)); + }); + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/launchdarkly/LaunchDarklyFeatureFlagService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/launchdarkly/LaunchDarklyFeatureFlagService.java new file mode 100644 index 0000000..f99bfce --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/launchdarkly/LaunchDarklyFeatureFlagService.java @@ -0,0 +1,32 @@ +package io.reflectoring.featureflags.implementations.launchdarkly; + +import com.launchdarkly.sdk.LDUser; +import com.launchdarkly.sdk.server.LDClient; +import io.reflectoring.featureflags.implementations.FeatureFlagService; +import io.reflectoring.featureflags.web.UserSession; + +public class LaunchDarklyFeatureFlagService implements FeatureFlagService { + + private final LDClient launchdarklyClient; + private final UserSession userSession; + + public LaunchDarklyFeatureFlagService(LDClient launchdarklyClient, UserSession userSession) { + this.launchdarklyClient = launchdarklyClient; + this.userSession = userSession; + } + + @Override + public Boolean featureOne() { + return launchdarklyClient.boolVariation("feature-one", getLaunchdarklyUserFromSession(), false); + } + + @Override + public Integer featureTwo() { + return launchdarklyClient.intVariation("feature-two", getLaunchdarklyUserFromSession(), 0); + } + + private LDUser getLaunchdarklyUserFromSession() { + return new LDUser.Builder(userSession.getUsername()) + .build(); + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/properties/FeatureProperties.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/properties/FeatureProperties.java new file mode 100644 index 0000000..7df00a4 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/properties/FeatureProperties.java @@ -0,0 +1,31 @@ +package io.reflectoring.featureflags.implementations.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("features") +public class FeatureProperties { + + private boolean featureOne; + private int featureTwo; + + public FeatureProperties() { + } + + public boolean getFeatureOne() { + return featureOne; + } + + public void setFeatureOne(boolean featureOne) { + this.featureOne = featureOne; + } + + public int getFeatureTwo() { + return featureTwo; + } + + public void setFeatureTwo(int featureTwo) { + this.featureTwo = featureTwo; + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/properties/PropertiesBackedFeatureFlagService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/properties/PropertiesBackedFeatureFlagService.java new file mode 100644 index 0000000..2d596b4 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/implementations/properties/PropertiesBackedFeatureFlagService.java @@ -0,0 +1,25 @@ +package io.reflectoring.featureflags.implementations.properties; + + +import io.reflectoring.featureflags.implementations.FeatureFlagService; +import org.springframework.stereotype.Component; + +@Component +public class PropertiesBackedFeatureFlagService implements FeatureFlagService { + + private final FeatureProperties featureProperties; + + public PropertiesBackedFeatureFlagService(FeatureProperties featureProperties) { + this.featureProperties = featureProperties; + } + + @Override + public Boolean featureOne() { + return featureProperties.getFeatureOne(); + } + + @Override + public Integer featureTwo() { + return featureProperties.getFeatureTwo(); + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/launchdarkly/LaunchDarklyFeatureFlagService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/launchdarkly/LaunchDarklyFeatureFlagService.java index e956c80..2593fd6 100644 --- a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/launchdarkly/LaunchDarklyFeatureFlagService.java +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/launchdarkly/LaunchDarklyFeatureFlagService.java @@ -4,11 +4,13 @@ import com.launchdarkly.sdk.LDUser; import com.launchdarkly.sdk.server.LDClient; import io.reflectoring.featureflags.FeatureFlagService; import io.reflectoring.featureflags.web.UserSession; +import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @Component("launchdarkly") +@Primary public class LaunchDarklyFeatureFlagService implements FeatureFlagService { private final LDClient launchdarklyClient; @@ -56,6 +58,11 @@ public class LaunchDarklyFeatureFlagService implements FeatureFlagService { return launchdarklyClient.boolVariation("user-clicked-flag", getLaunchdarklyUserFromSession(), false); } + @Override + public Boolean isNewServiceEnabled() { + return true; + } + private LDUser getLaunchdarklyUserFromSession() { return new LDUser.Builder(userSession.getUsername()) .custom("clicked", userSession.hasClicked()) diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/ifelse/Service.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/ifelse/Service.java new file mode 100644 index 0000000..0325052 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/ifelse/Service.java @@ -0,0 +1,22 @@ +package io.reflectoring.featureflags.patterns.ifelse; + +import io.reflectoring.featureflags.FeatureFlagService; +import org.springframework.stereotype.Component; + +@Component +class Service { + + private final FeatureFlagService featureFlagService; + + public Service(FeatureFlagService featureFlagService) { + this.featureFlagService = featureFlagService; + } + + public int doSomething() { + if (featureFlagService.isNewServiceEnabled()) { + return 42; + } else { + return 1; + } + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacebean/FeatureFlagFactoryBean.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacebean/FeatureFlagFactoryBean.java new file mode 100644 index 0000000..d7ec11f --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacebean/FeatureFlagFactoryBean.java @@ -0,0 +1,42 @@ +package io.reflectoring.featureflags.patterns.replacebean; + +import org.springframework.beans.factory.FactoryBean; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.util.function.Supplier; + +public class FeatureFlagFactoryBean implements FactoryBean { + + private final Class targetClass; + private final Supplier featureFlagEvaluation; + private final T beanWhenTrue; + private final T beanWhenFalse; + + public FeatureFlagFactoryBean(Class targetClass, Supplier featureFlagEvaluation, T beanWhenTrue, T beanWhenFalse) { + this.targetClass = targetClass; + this.featureFlagEvaluation = featureFlagEvaluation; + this.beanWhenTrue = beanWhenTrue; + this.beanWhenFalse = beanWhenFalse; + } + + @Override + public T getObject() { + InvocationHandler invocationHandler = (proxy, method, args) -> { + if (featureFlagEvaluation.get()) { + return method.invoke(beanWhenTrue, args); + } else { + return method.invoke(beanWhenFalse, args); + } + }; + + Object proxy = Proxy.newProxyInstance(targetClass.getClassLoader(), new Class[]{targetClass}, invocationHandler); + + return (T) proxy; + } + + @Override + public Class getObjectType() { + return targetClass; + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacebean/FeatureFlaggedService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacebean/FeatureFlaggedService.java new file mode 100644 index 0000000..3b95ee0 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacebean/FeatureFlaggedService.java @@ -0,0 +1,16 @@ +package io.reflectoring.featureflags.patterns.replacebean; + +import io.reflectoring.featureflags.FeatureFlagService; +import org.springframework.stereotype.Component; + +@Component("replaceBeanFeatureFlaggedService") +class FeatureFlaggedService extends FeatureFlagFactoryBean { + + public FeatureFlaggedService(FeatureFlagService featureFlagService) { + super( + Service.class, + featureFlagService::isNewServiceEnabled, + new NewService(), + new OldService()); + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacebean/NewService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacebean/NewService.java new file mode 100644 index 0000000..8f86362 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacebean/NewService.java @@ -0,0 +1,8 @@ +package io.reflectoring.featureflags.patterns.replacebean; + +class NewService implements Service { + @Override + public int doSomething() { + return 42; + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacebean/OldService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacebean/OldService.java new file mode 100644 index 0000000..b09fe2b --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacebean/OldService.java @@ -0,0 +1,8 @@ +package io.reflectoring.featureflags.patterns.replacebean; + +class OldService implements Service { + @Override + public int doSomething() { + return 1; + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacebean/Service.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacebean/Service.java new file mode 100644 index 0000000..1ea1096 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacebean/Service.java @@ -0,0 +1,7 @@ +package io.reflectoring.featureflags.patterns.replacebean; + +interface Service { + + int doSomething(); + +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemethod/FeatureFlaggedService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemethod/FeatureFlaggedService.java new file mode 100644 index 0000000..8502b29 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemethod/FeatureFlaggedService.java @@ -0,0 +1,30 @@ +package io.reflectoring.featureflags.patterns.replacemethod; + +import io.reflectoring.featureflags.FeatureFlagService; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +@Component("replaceMethodFeatureFlaggedService") +@Primary +class FeatureFlaggedService implements Service { + + private final FeatureFlagService featureFlagService; + private final NewService newService; + private final OldService oldService; + + public FeatureFlaggedService(FeatureFlagService featureFlagService, NewService newService, OldService oldService) { + this.featureFlagService = featureFlagService; + this.newService = newService; + this.oldService = oldService; + } + + @Override + public int doSomething() { + if (featureFlagService.isNewServiceEnabled()) { + return newService.doSomething(); + } else { + return oldService.doSomething(); + } + } + +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemethod/NewService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemethod/NewService.java new file mode 100644 index 0000000..5f2f982 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemethod/NewService.java @@ -0,0 +1,12 @@ +package io.reflectoring.featureflags.patterns.replacemethod; + +import org.springframework.stereotype.Component; + +@Component +class NewService implements Service { + @Override + public int doSomething() { + return 42; + } + +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemethod/OldService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemethod/OldService.java new file mode 100644 index 0000000..1037482 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemethod/OldService.java @@ -0,0 +1,16 @@ +package io.reflectoring.featureflags.patterns.replacemethod; + +import org.springframework.stereotype.Component; + +@Component +class OldService implements Service { + @Override + public int doSomething() { + return 1; + } + + public int doSomethingElse(){ + return 2; + } + +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemethod/Service.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemethod/Service.java new file mode 100644 index 0000000..2999df2 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemethod/Service.java @@ -0,0 +1,7 @@ +package io.reflectoring.featureflags.patterns.replacemethod; + +interface Service { + + int doSomething(); + +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/FeatureFlaggedServiceModule.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/FeatureFlaggedServiceModule.java new file mode 100644 index 0000000..72112d2 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/FeatureFlaggedServiceModule.java @@ -0,0 +1,31 @@ +package io.reflectoring.featureflags.patterns.replacemodule; + +import io.reflectoring.featureflags.FeatureFlagService; +import io.reflectoring.featureflags.patterns.replacebean.FeatureFlagFactoryBean; +import io.reflectoring.featureflags.patterns.replacemodule.newmodule.NewService1; +import io.reflectoring.featureflags.patterns.replacemodule.newmodule.NewService2; +import io.reflectoring.featureflags.patterns.replacemodule.oldmodule.OldService1; +import io.reflectoring.featureflags.patterns.replacemodule.oldmodule.OldService2; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +class FeatureFlaggedServiceModule { + + private final FeatureFlagService featureFlagService; + + public FeatureFlaggedServiceModule(FeatureFlagService featureFlagService) { + this.featureFlagService = featureFlagService; + } + + @Bean("replaceModuleService1") + FeatureFlagFactoryBean service1() { + return new FeatureFlagFactoryBean<>(Service1.class, featureFlagService::isNewServiceEnabled, new NewService1(), new OldService1()); + } + + @Bean("replaceModuleService2") + FeatureFlagFactoryBean service2() { + return new FeatureFlagFactoryBean<>(Service2.class, featureFlagService::isNewServiceEnabled, new NewService2(), new OldService2()); + } + +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/Service1.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/Service1.java new file mode 100644 index 0000000..d9dc7dc --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/Service1.java @@ -0,0 +1,7 @@ +package io.reflectoring.featureflags.patterns.replacemodule; + +public interface Service1 { + + int doSomething(); + +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/Service2.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/Service2.java new file mode 100644 index 0000000..e8c7c5d --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/Service2.java @@ -0,0 +1,7 @@ +package io.reflectoring.featureflags.patterns.replacemodule; + +public interface Service2 { + + int doSomethingElse(); + +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/ServiceModule.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/ServiceModule.java new file mode 100644 index 0000000..b2d9c02 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/ServiceModule.java @@ -0,0 +1,21 @@ +package io.reflectoring.featureflags.patterns.replacemodule; + +import io.reflectoring.featureflags.patterns.replacemodule.oldmodule.OldService1; +import io.reflectoring.featureflags.patterns.replacemodule.oldmodule.OldService2; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +class ServiceModule { + + @Bean + Service1 service1() { + return new OldService1(); + } + + @Bean + Service2 service2() { + return new OldService2(); + } + +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/newmodule/NewService1.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/newmodule/NewService1.java new file mode 100644 index 0000000..cea5efb --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/newmodule/NewService1.java @@ -0,0 +1,12 @@ +package io.reflectoring.featureflags.patterns.replacemodule.newmodule; + +import io.reflectoring.featureflags.patterns.replacemodule.Service1; +import org.springframework.stereotype.Component; + +@Component +public class NewService1 implements Service1 { + @Override + public int doSomething() { + return 42; + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/newmodule/NewService2.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/newmodule/NewService2.java new file mode 100644 index 0000000..a492333 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/newmodule/NewService2.java @@ -0,0 +1,13 @@ +package io.reflectoring.featureflags.patterns.replacemodule.newmodule; + +import io.reflectoring.featureflags.patterns.replacemodule.Service2; +import org.springframework.stereotype.Component; + +@Component +public class NewService2 implements Service2 { + + @Override + public int doSomethingElse() { + return 42; + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/oldmodule/OldService1.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/oldmodule/OldService1.java new file mode 100644 index 0000000..98b9dba --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/oldmodule/OldService1.java @@ -0,0 +1,12 @@ +package io.reflectoring.featureflags.patterns.replacemodule.oldmodule; + +import io.reflectoring.featureflags.patterns.replacemodule.Service1; +import org.springframework.stereotype.Component; + +@Component +public class OldService1 implements Service1 { + @Override + public int doSomething() { + return 1; + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/oldmodule/OldService2.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/oldmodule/OldService2.java new file mode 100644 index 0000000..7062df8 --- /dev/null +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/patterns/replacemodule/oldmodule/OldService2.java @@ -0,0 +1,13 @@ +package io.reflectoring.featureflags.patterns.replacemodule.oldmodule; + +import io.reflectoring.featureflags.patterns.replacemodule.Service2; +import org.springframework.stereotype.Component; + +@Component +public class OldService2 implements Service2 { + + @Override + public int doSomethingElse() { + return 1; + } +} diff --git a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/togglz/TooglzFeatureFlagService.java b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/togglz/TooglzFeatureFlagService.java index 15e6b98..857c6a1 100644 --- a/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/togglz/TooglzFeatureFlagService.java +++ b/spring-boot/feature-flags/src/main/java/io/reflectoring/featureflags/togglz/TooglzFeatureFlagService.java @@ -26,4 +26,9 @@ public class TooglzFeatureFlagService implements FeatureFlagService { return Features.USER_ACTION_TARGETED_FEATURE.isActive(); } + @Override + public Boolean isNewServiceEnabled() { + return false; + } + } diff --git a/spring-boot/feature-flags/src/main/resources/application.yml b/spring-boot/feature-flags/src/main/resources/application.yml index 091bb99..f2b0c7a 100644 --- a/spring-boot/feature-flags/src/main/resources/application.yml +++ b/spring-boot/feature-flags/src/main/resources/application.yml @@ -15,4 +15,8 @@ togglz: enabled: true secured: false path: /togglz - use-management-port: false \ No newline at end of file + use-management-port: false + +features: + featureOne: true + featureTwo: 42 \ No newline at end of file diff --git a/spring-boot/feature-flags/src/test/java/io/reflectoring/featureflags/implementations/contextsensitive/FeatureTest.java b/spring-boot/feature-flags/src/test/java/io/reflectoring/featureflags/implementations/contextsensitive/FeatureTest.java new file mode 100644 index 0000000..c86b0f6 --- /dev/null +++ b/spring-boot/feature-flags/src/test/java/io/reflectoring/featureflags/implementations/contextsensitive/FeatureTest.java @@ -0,0 +1,19 @@ +package io.reflectoring.featureflags.implementations.contextsensitive; + +import io.reflectoring.featureflags.implementations.contextsensitive.Feature.RolloutStrategy; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.offset; + +public class FeatureTest { + + @Test + void testHashCode(){ + Feature feature = new Feature(RolloutStrategy.PERCENTAGE, "true", "false", 50); + assertThat(feature.percentageHashCode("1")).isCloseTo(27.74d, offset(0.01d)); + assertThat(feature.percentageHashCode("2")).isCloseTo(81.12d, offset(0.01d)); + assertThat(feature.percentageHashCode("3")).isCloseTo(21.69d, offset(0.01d)); + } + +} diff --git a/spring-boot/feature-flags/src/test/java/io/reflectoring/featureflags/patterns/replacebean/ReplaceBeanTest.java b/spring-boot/feature-flags/src/test/java/io/reflectoring/featureflags/patterns/replacebean/ReplaceBeanTest.java new file mode 100644 index 0000000..c463502 --- /dev/null +++ b/spring-boot/feature-flags/src/test/java/io/reflectoring/featureflags/patterns/replacebean/ReplaceBeanTest.java @@ -0,0 +1,40 @@ +package io.reflectoring.featureflags.patterns.replacebean; + +import io.reflectoring.featureflags.FeatureFlagService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.BDDMockito.given; + +@SpringBootTest +public class ReplaceBeanTest { + + @MockBean + private FeatureFlagService featureFlagService; + + @Autowired + private Service service; + + @BeforeEach + void resetMocks() { + Mockito.reset(featureFlagService); + } + + @Test + void oldServiceTest() { + given(featureFlagService.isNewServiceEnabled()).willReturn(false); + assertThat(service.doSomething()).isEqualTo(1); + } + + @Test + void newServiceTest() { + given(featureFlagService.isNewServiceEnabled()).willReturn(true); + assertThat(service.doSomething()).isEqualTo(42); + } + +} diff --git a/spring-boot/feature-flags/src/test/java/io/reflectoring/featureflags/patterns/replacemethod/ReplaceMethodTest.java b/spring-boot/feature-flags/src/test/java/io/reflectoring/featureflags/patterns/replacemethod/ReplaceMethodTest.java new file mode 100644 index 0000000..039c83c --- /dev/null +++ b/spring-boot/feature-flags/src/test/java/io/reflectoring/featureflags/patterns/replacemethod/ReplaceMethodTest.java @@ -0,0 +1,46 @@ +package io.reflectoring.featureflags.patterns.replacemethod; + +import io.reflectoring.featureflags.FeatureFlagService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.BDDMockito.given; + +@SpringBootTest +public class ReplaceMethodTest { + + @MockBean + private FeatureFlagService featureFlagService; + + @Autowired + private Service service; + + @Autowired + private OldService oldService; + + @BeforeEach + void resetMocks() { + Mockito.reset(featureFlagService); + } + + @Test + void oldServiceTest() { + given(featureFlagService.isNewServiceEnabled()).willReturn(false); + assertThat(service.doSomething()).isEqualTo(1); + assertThat(oldService.doSomethingElse()).isEqualTo(2); + } + + @Test + void newServiceTest() { + given(featureFlagService.isNewServiceEnabled()).willReturn(true); + assertThat(service.doSomething()).isEqualTo(42); + // doSomethingElse() is not behind a feature flag, so it should return the same value independant of the feature flag + assertThat(oldService.doSomethingElse()).isEqualTo(2); + } + +} diff --git a/testing/assertJ/.mvn/wrapper/MavenWrapperDownloader.java b/testing/assertJ/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..b901097 --- /dev/null +++ b/testing/assertJ/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/testing/assertJ/.mvn/wrapper/maven-wrapper.jar b/testing/assertJ/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..2cc7d4a Binary files /dev/null and b/testing/assertJ/.mvn/wrapper/maven-wrapper.jar differ diff --git a/testing/assertJ/.mvn/wrapper/maven-wrapper.properties b/testing/assertJ/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/testing/assertJ/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/testing/assertJ/HELP.md b/testing/assertJ/HELP.md new file mode 100644 index 0000000..740263d --- /dev/null +++ b/testing/assertJ/HELP.md @@ -0,0 +1,25 @@ +# Getting Started + +### Reference Documentation + +For further reference, please consider the following sections: + +* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) +* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.5.4/maven-plugin/reference/html/) +* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.5.4/maven-plugin/reference/html/#build-image) +* [Spring Data JPA](https://docs.spring.io/spring-boot/docs/2.5.4/reference/htmlsingle/#boot-features-jpa-and-spring-data) +* [Spring Security](https://docs.spring.io/spring-boot/docs/2.5.4/reference/htmlsingle/#boot-features-security) +* [Spring Web](https://docs.spring.io/spring-boot/docs/2.5.4/reference/htmlsingle/#boot-features-developing-web-applications) + +### Guides + +The following guides illustrate how to use some features concretely: + +* [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/) +* [Securing a Web Application](https://spring.io/guides/gs/securing-web/) +* [Spring Boot and OAuth2](https://spring.io/guides/tutorials/spring-boot-oauth2/) +* [Authenticating a User with LDAP](https://spring.io/guides/gs/authenticating-ldap/) +* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) +* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) +* [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/) + diff --git a/testing/assertJ/README.md b/testing/assertJ/README.md new file mode 100644 index 0000000..61c8425 --- /dev/null +++ b/testing/assertJ/README.md @@ -0,0 +1,4 @@ +# Examples for [Verifying Lists Using AssertJ on reflectoring.io](https://reflectoring.io/verifying-lists-using-assertj) + +This repository contains the source code of the article's examples. +It's based on Spring Boot with really simple code for business logic and with focus on testing. \ No newline at end of file diff --git a/testing/assertJ/mvnw b/testing/assertJ/mvnw new file mode 100755 index 0000000..a16b543 --- /dev/null +++ b/testing/assertJ/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/testing/assertJ/mvnw.cmd b/testing/assertJ/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/testing/assertJ/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/testing/assertJ/pom.xml b/testing/assertJ/pom.xml new file mode 100644 index 0000000..ae9a9b6 --- /dev/null +++ b/testing/assertJ/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.4 + + + com.reflectoring + GymBuddy + 0.0.1-SNAPSHOT + GymBuddy + Demo project for Spring Boot + + 11 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-validation + + + junit + junit + 4.13.2 + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/GymBuddyApplication.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/GymBuddyApplication.java new file mode 100644 index 0000000..62a214c --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/GymBuddyApplication.java @@ -0,0 +1,13 @@ +package com.reflectoring.gymbuddy; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class GymBuddyApplication { + + public static void main(String[] args) { + SpringApplication.run(GymBuddyApplication.class, args); + } + +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/domain/Person.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/domain/Person.java new file mode 100644 index 0000000..b17a80e --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/domain/Person.java @@ -0,0 +1,184 @@ +package com.reflectoring.gymbuddy.domain; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Entity +@Table(name = "person") +public class Person { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private long id; + + @NotNull + @NotBlank + private String name; + + @NotNull + @NotBlank + private String lastname; + + @NotNull + @NotBlank + private String email; + + @NotNull + @NotBlank + private String password; + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable( + name = "friends", + joinColumns = @JoinColumn(name = "a", referencedColumnName = "id",table = "person"), + inverseJoinColumns = @JoinColumn(name = "b", referencedColumnName = "id", table = "person")) + private Set friends; + + @OneToMany(mappedBy = "person",fetch = FetchType.EAGER) + private List sessions; + + public Person(){} + + public Person(long id, String name, String lastname, String email, String password, Set friends, List sessions){ + this.id = id; + this.name = name; + this.lastname = lastname; + this.email = email; + this.password = password; + this.friends = friends; + this.sessions = sessions; + } + private Person(PersonBuilder builder){ + this.id = builder.id; + this.name = builder.name; + this.lastname = builder.lastname; + this.email = builder.email; + this.password = builder.password; + this.friends = builder.friends; + this.sessions = builder.sessions; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getLastname() { + return lastname; + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } + + public void setName(String name) { + this.name = name; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setPassword(String password) { + this.password = password; + } + + public Set getFriends() { + return friends; + } + + public List getSessions() { + return sessions; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Person person = (Person) o; + return id == person.id && email.equals(person.email); + } + + @Override + public int hashCode() { + return Objects.hash(id, email); + } + + public static class PersonBuilder{ + private long id; + private String name; + private String lastname; + private String email; + private String password; + private Set friends; + private List sessions; + + public PersonBuilder(){} + + public PersonBuilder id(long id){ + this.id = id; + return this; + } + + public PersonBuilder name(String name){ + this.name = name; + return this; + } + + public PersonBuilder lastname(String lastname){ + this.lastname = lastname; + return this; + } + + public PersonBuilder email(String email){ + this.email = email; + return this; + } + public PersonBuilder password(String password){ + this.password = password; + return this; + } + public PersonBuilder friends(Set friends){ + this.friends = friends; + return this; + } + + public PersonBuilder sessions(List sessions){ + this.sessions = sessions; + return this; + } + + public Person build(){ + return new Person(this); + } + } + +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/domain/Session.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/domain/Session.java new file mode 100644 index 0000000..b5e200e --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/domain/Session.java @@ -0,0 +1,126 @@ +package com.reflectoring.gymbuddy.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +@Entity +@Table(name = "session") +public class Session { + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private long id; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime start; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime end; + + @OneToMany(mappedBy = "session", fetch = FetchType.EAGER) + private List workouts; + + @ManyToOne + @JoinColumn(name = "person_id") + private Person person; + + public Session(){} + public Session(long id, LocalDateTime start, LocalDateTime end, List workouts, Person person) { + this.id = id; + this.start = start; + this.end = end; + this.workouts = workouts; + this.person = person; + } + + private Session(SessionBuilder builder){ + this.id = builder.id; + this.start = builder.start; + this.end = builder.end; + this.workouts = builder.workouts; + this.person = builder.person; + } + + public long getId() { + return id; + } + + public LocalDateTime getStart() { + return start; + } + + public LocalDateTime getEnd() { + return end; + } + + public void setStart(LocalDateTime start) { + this.start = start; + } + + public void setEnd(LocalDateTime end) { + this.end = end; + } + + public List getWorkouts() { + return workouts; + } + + public Person getPerson() { + return person; + } + + public long getDurationInMinutes(){ + return Duration.between(start, end).toMinutes(); + } + + public static class SessionBuilder{ + private long id; + private LocalDateTime start; + private LocalDateTime end; + private List workouts; + private Person person; + + public SessionBuilder(){} + + public SessionBuilder id(long id){ + this.id = id; + return this; + } + + public SessionBuilder start(LocalDateTime start){ + this.start = start; + return this; + } + + public SessionBuilder end(LocalDateTime end){ + this.end = end; + return this; + } + + public SessionBuilder workouts(List workouts){ + this.workouts = workouts; + return this; + } + + public SessionBuilder person(Person person){ + this.person = person; + return this; + } + + public Session build(){ + return new Session(this); + } + } +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/domain/Set.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/domain/Set.java new file mode 100644 index 0000000..500bc3d --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/domain/Set.java @@ -0,0 +1,117 @@ +package com.reflectoring.gymbuddy.domain; + +import java.util.Objects; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "set") +public class Set { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private long id; + + private long weight; + + private long reps; + + @ManyToOne + @JoinColumn(name = "workout_id") + Workout workout; + + public Set(){} + + public Set(long id, long weight, long reps){ + this.id = id; + this.weight = weight; + this.reps = reps; + } + + private Set(SetBuilder builder){ + this.id = builder.id; + this.weight = builder.weight; + this.reps = builder.reps; + this.workout = builder.workout; + } + + public long getId() { + return id; + } + + public long getWeight() { + return weight; + } + + public long getReps() { + return reps; + } + + public void setWeight(long weight) { + this.weight = weight; + } + + public void setReps(long reps) { + this.reps = reps; + } + + public Workout getWorkout() { + return workout; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Set set = (Set) o; + return id == set.id && weight == set.weight && reps == set.reps; + } + + @Override + public int hashCode() { + return Objects.hash(id, weight, reps); + } + + public static class SetBuilder{ + private long id; + private long weight; + private long reps; + private Workout workout; + + public SetBuilder(){} + + public SetBuilder id(long id){ + this.id = id; + return this; + } + + public SetBuilder weight(long weight){ + this.weight = weight; + return this; + } + + public SetBuilder reps(long reps){ + this.reps = reps; + return this; + } + + public SetBuilder workout(Workout workout){ + this.workout = workout; + return this; + } + + public Set build(){ + return new Set(this); + } + + } +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/domain/Workout.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/domain/Workout.java new file mode 100644 index 0000000..f32b0ed --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/domain/Workout.java @@ -0,0 +1,99 @@ +package com.reflectoring.gymbuddy.domain; + +import java.util.List; +import java.util.Objects; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +@Entity +@Table(name = "workout") +public class Workout { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private long id; + + @OneToMany(mappedBy = "workout", fetch = FetchType.EAGER) + private List sets; + + @ManyToOne + @JoinColumn(name = "session_id") + private Session session; + + public Workout(){} + + public Workout(long id, List sets, Session session){ + this.id = id; + this.sets = sets; + this.session = session; + } + + private Workout(WorkoutBuilder builder){ + this.id = builder.id; + this.sets = builder.sets; + this.session = builder.session; + } + + public long getId() { + return id; + } + + public List getSets() { + return sets; + } + + public Session getSession() { + return session; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Workout workout = (Workout) o; + return id == workout.id && sets.equals(workout.sets) && session.equals(workout.session); + } + + @Override + public int hashCode() { + return Objects.hash(id, sets, session); + } + + public static class WorkoutBuilder{ + private long id; + private List sets; + private Session session; + + public WorkoutBuilder(){} + + public WorkoutBuilder id(long id){ + this.id = id; + return this; + } + + public WorkoutBuilder sets(List sets){ + this.sets = sets; + return this; + } + + public WorkoutBuilder session(Session session){ + this.session = session; + return this; + } + + public Workout build(){ + return new Workout(this); + } + } +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/person/PersonAddRequest.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/person/PersonAddRequest.java new file mode 100644 index 0000000..0e4b972 --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/person/PersonAddRequest.java @@ -0,0 +1,73 @@ +package com.reflectoring.gymbuddy.dto.person; + + +public class PersonAddRequest { + + private String name; + private String lastname; + private String email; + private String password; + + public PersonAddRequest(String name, String lastname, String email, String password){ + this.name = name; + this.lastname = lastname; + this.email = email; + this.password = password; + } + + private PersonAddRequest(PersonAddRequestBuilder builder){ + this.name = builder.name; + this.lastname = builder.lastname; + this.email = builder.email; + this.password = builder.password; + } + + public String getName() { + return name; + } + + public String getLastname() { + return lastname; + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } + + public static class PersonAddRequestBuilder{ + private String name; + private String lastname; + private String email; + private String password; + + public PersonAddRequestBuilder(){} + + public PersonAddRequestBuilder name(String name){ + this.name = name; + return this; + } + + public PersonAddRequestBuilder lastname(String lastname){ + this.lastname = lastname; + return this; + } + + public PersonAddRequestBuilder email(String email){ + this.email = email; + return this; + } + + public PersonAddRequestBuilder password(String password){ + this.password = password; + return this; + } + + public PersonAddRequest build(){ + return new PersonAddRequest(this); + } + } +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/person/PersonUpdateRequest.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/person/PersonUpdateRequest.java new file mode 100644 index 0000000..7210112 --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/person/PersonUpdateRequest.java @@ -0,0 +1,73 @@ +package com.reflectoring.gymbuddy.dto.person; + + +public class PersonUpdateRequest { + + private String name; + private String lastname; + private String email; + private String password; + + public PersonUpdateRequest(String name, String lastname, String email, String password){ + this.name = name; + this.lastname = lastname; + this.email = email; + this.password = password; + } + + private PersonUpdateRequest(PersonUpdateRequestBuilder builder){ + this.name = builder.name; + this.lastname = builder.lastname; + this.email = builder.email; + this.password = builder.password; + } + + public String getName() { + return name; + } + + public String getLastname() { + return lastname; + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } + + public class PersonUpdateRequestBuilder{ + private String name; + private String lastname; + private String email; + private String password; + + public PersonUpdateRequestBuilder(){} + + public PersonUpdateRequestBuilder name(String name){ + this.name = name; + return this; + } + + public PersonUpdateRequestBuilder lastname(String lastname){ + this.lastname = lastname; + return this; + } + + public PersonUpdateRequestBuilder email(String email){ + this.email = email; + return this; + } + + public PersonUpdateRequestBuilder password(String password){ + this.password = password; + return this; + } + + public PersonUpdateRequest build(){ + return new PersonUpdateRequest(this); + } + } +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/session/SessionAddRequest.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/session/SessionAddRequest.java new file mode 100644 index 0000000..3135ae4 --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/session/SessionAddRequest.java @@ -0,0 +1,68 @@ +package com.reflectoring.gymbuddy.dto.session; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.reflectoring.gymbuddy.dto.workout.WorkoutAddRequest; +import java.time.LocalDateTime; +import java.util.List; + +public class SessionAddRequest { + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime start; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime end; + + private List workouts; + + public SessionAddRequest(LocalDateTime start, LocalDateTime end, List workouts){ + this.start = start; + this.end = end; + this.workouts = workouts; + } + + private SessionAddRequest(SessionAddRequestBuilder builder){ + this.start = builder.start; + this.end = builder.end; + this.workouts = builder.workouts; + } + + public LocalDateTime getStart() { + return start; + } + + public LocalDateTime getEnd() { + return end; + } + + public List getWorkouts() { + return workouts; + } + + public static class SessionAddRequestBuilder{ + private LocalDateTime start; + private LocalDateTime end; + private List workouts; + + public SessionAddRequestBuilder(){} + + public SessionAddRequestBuilder start(LocalDateTime start){ + this.start = start; + return this; + } + + public SessionAddRequestBuilder end(LocalDateTime end){ + this.end = end; + return this; + } + + public SessionAddRequestBuilder workouts(List workouts){ + this.workouts = workouts; + return this; + } + + public SessionAddRequest build(){ + return new SessionAddRequest(this); + } + } +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/session/SessionUpdateRequest.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/session/SessionUpdateRequest.java new file mode 100644 index 0000000..294c8b4 --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/session/SessionUpdateRequest.java @@ -0,0 +1,56 @@ +package com.reflectoring.gymbuddy.dto.session; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.reflectoring.gymbuddy.dto.workout.WorkoutAddRequest; +import java.time.LocalDateTime; +import java.util.List; + +public class SessionUpdateRequest { + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime start; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime end; + + + public SessionUpdateRequest(LocalDateTime start, LocalDateTime end){ + this.start = start; + this.end = end; + } + + private SessionUpdateRequest(SessionUpdateRequestBuilder builder){ + this.start = builder.start; + this.end = builder.end; + } + + public LocalDateTime getStart() { + return start; + } + + public LocalDateTime getEnd() { + return end; + } + + + public class SessionUpdateRequestBuilder{ + private LocalDateTime start; + private LocalDateTime end; + + public SessionUpdateRequestBuilder(){} + + public SessionUpdateRequestBuilder start(LocalDateTime start){ + this.start = start; + return this; + } + + public SessionUpdateRequestBuilder end(LocalDateTime end){ + this.end = end; + return this; + } + + public SessionUpdateRequest build(){ + return new SessionUpdateRequest(this); + } + } +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/set/SetAddRequest.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/set/SetAddRequest.java new file mode 100644 index 0000000..c0b2fc7 --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/set/SetAddRequest.java @@ -0,0 +1,47 @@ +package com.reflectoring.gymbuddy.dto.set; + +public class SetAddRequest { + + private long weight; + + private long reps; + + public SetAddRequest(long weight, long reps){ + this.weight = weight; + this.reps = reps; + } + + public SetAddRequest(SetAddRequestBuilder builder){ + this.weight = builder.weight; + this.reps = builder.reps; + } + + public long getWeight() { + return weight; + } + + public long getReps() { + return reps; + } + + public static class SetAddRequestBuilder{ + private long weight; + private long reps; + + public SetAddRequestBuilder(){} + + public SetAddRequestBuilder weight(long weight){ + this.weight = weight; + return this; + } + + public SetAddRequestBuilder reps(long reps){ + this.reps = reps; + return this; + } + + public SetAddRequest build(){ + return new SetAddRequest(this); + } + } +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/set/SetUpdateRequest.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/set/SetUpdateRequest.java new file mode 100644 index 0000000..46f2cb3 --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/set/SetUpdateRequest.java @@ -0,0 +1,46 @@ +package com.reflectoring.gymbuddy.dto.set; + +public class SetUpdateRequest { + private long weight; + + private long reps; + + public SetUpdateRequest(long weight, long reps){ + this.weight = weight; + this.reps = reps; + } + + public SetUpdateRequest(SetUpdateRequestBuilder builder){ + this.weight = builder.weight; + this.reps = builder.reps; + } + + public long getWeight() { + return weight; + } + + public long getReps() { + return reps; + } + + public static class SetUpdateRequestBuilder{ + private long weight; + private long reps; + + public SetUpdateRequestBuilder(){} + + public SetUpdateRequestBuilder weight(long weight){ + this.weight = weight; + return this; + } + + public SetUpdateRequestBuilder reps(long reps){ + this.reps = reps; + return this; + } + + public SetUpdateRequest build(){ + return new SetUpdateRequest(this); + } + } +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/workout/WorkoutAddRequest.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/workout/WorkoutAddRequest.java new file mode 100644 index 0000000..e95b518 --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/dto/workout/WorkoutAddRequest.java @@ -0,0 +1,37 @@ +package com.reflectoring.gymbuddy.dto.workout; + +import com.reflectoring.gymbuddy.dto.set.SetAddRequest; +import java.util.List; + +public class WorkoutAddRequest { + + private List sets; + + public List getSets(){ + return this.sets; + } + + public WorkoutAddRequest(List sets){ + this.sets = sets; + } + + public WorkoutAddRequest(WorkoutAddRequestBuilder builder){ + this.sets = builder.sets; + } + + public static class WorkoutAddRequestBuilder{ + private List sets; + + public WorkoutAddRequestBuilder(){} + + public WorkoutAddRequestBuilder sets(List sets){ + this.sets = sets; + return this; + } + + public WorkoutAddRequest build(){ + return new WorkoutAddRequest(this); + } + } + +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/repository/PersonRepository.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/repository/PersonRepository.java new file mode 100644 index 0000000..b8dbdb3 --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/repository/PersonRepository.java @@ -0,0 +1,15 @@ +package com.reflectoring.gymbuddy.repository; + +import com.reflectoring.gymbuddy.domain.Person; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PersonRepository extends JpaRepository { + + Optional findByEmail(String email); + + void deleteByEmail(String email); + +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/repository/SessionRepository.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/repository/SessionRepository.java new file mode 100644 index 0000000..407ff54 --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/repository/SessionRepository.java @@ -0,0 +1,13 @@ +package com.reflectoring.gymbuddy.repository; + +import com.reflectoring.gymbuddy.domain.Session; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface SessionRepository extends JpaRepository { + + List getAllByPersonEmail(String email); + +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/repository/SetRepository.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/repository/SetRepository.java new file mode 100644 index 0000000..ef4322a --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/repository/SetRepository.java @@ -0,0 +1,13 @@ +package com.reflectoring.gymbuddy.repository; + +import com.reflectoring.gymbuddy.domain.Set; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface SetRepository extends JpaRepository { + + List findAllByWorkout(@Param("workout") long id); +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/repository/WorkoutRepository.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/repository/WorkoutRepository.java new file mode 100644 index 0000000..c79a31e --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/repository/WorkoutRepository.java @@ -0,0 +1,12 @@ +package com.reflectoring.gymbuddy.repository; + +import com.reflectoring.gymbuddy.domain.Workout; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface WorkoutRepository extends JpaRepository { + List findAllBySession(@Param("session") long id); +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/PersonService.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/PersonService.java new file mode 100644 index 0000000..8143a3a --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/PersonService.java @@ -0,0 +1,27 @@ +package com.reflectoring.gymbuddy.services; + +import com.reflectoring.gymbuddy.domain.Person; +import com.reflectoring.gymbuddy.dto.person.PersonAddRequest; +import com.reflectoring.gymbuddy.dto.person.PersonUpdateRequest; +import java.util.List; + +public interface PersonService { + + Person add(PersonAddRequest request); + + Person update(String email, PersonUpdateRequest request); + + Person get(String email); + + List getAll(); + + void delete(String email); + + List getFriends(String email); + + Person addFriend(String email, String friendEmail); + + Person deleteFriend(String email, String friendEmail); + + Person getFriend(String email, String friendEmail); +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/SessionService.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/SessionService.java new file mode 100644 index 0000000..a85d676 --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/SessionService.java @@ -0,0 +1,21 @@ +package com.reflectoring.gymbuddy.services; + +import com.reflectoring.gymbuddy.domain.Person; +import com.reflectoring.gymbuddy.domain.Session; +import com.reflectoring.gymbuddy.dto.session.SessionAddRequest; +import com.reflectoring.gymbuddy.dto.session.SessionUpdateRequest; +import java.util.List; + +public interface SessionService { + + Session add(Person person, SessionAddRequest request); + + Session update(long id, SessionUpdateRequest request); + + Session get(long id); + + List getAll(); + + List getForPerson(String email); + +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/SetService.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/SetService.java new file mode 100644 index 0000000..26337d4 --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/SetService.java @@ -0,0 +1,19 @@ +package com.reflectoring.gymbuddy.services; + +import com.reflectoring.gymbuddy.domain.Set; +import com.reflectoring.gymbuddy.domain.Workout; +import com.reflectoring.gymbuddy.dto.set.SetAddRequest; +import com.reflectoring.gymbuddy.dto.set.SetUpdateRequest; +import java.util.List; + +public interface SetService { + + Set add(Workout workout, SetAddRequest request); + + Set update(long id,SetUpdateRequest request); + + List getSetsOfWorkout(long workoutId); + + void delete(long id); + +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/WorkoutService.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/WorkoutService.java new file mode 100644 index 0000000..d4b91dc --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/WorkoutService.java @@ -0,0 +1,18 @@ +package com.reflectoring.gymbuddy.services; + +import com.reflectoring.gymbuddy.domain.Session; +import com.reflectoring.gymbuddy.domain.Workout; +import com.reflectoring.gymbuddy.dto.workout.WorkoutAddRequest; +import java.util.List; + +public interface WorkoutService { + + Workout add(Session session, WorkoutAddRequest request); + + List getAll(long sessionId); + + Workout get(long id); + + void delete(long id); + +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/implementation/PersonServiceImpl.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/implementation/PersonServiceImpl.java new file mode 100644 index 0000000..a582fb8 --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/implementation/PersonServiceImpl.java @@ -0,0 +1,126 @@ +package com.reflectoring.gymbuddy.services.implementation; + +import com.reflectoring.gymbuddy.domain.Person; +import com.reflectoring.gymbuddy.domain.Person.PersonBuilder; +import com.reflectoring.gymbuddy.dto.person.PersonAddRequest; +import com.reflectoring.gymbuddy.dto.person.PersonUpdateRequest; +import com.reflectoring.gymbuddy.repository.PersonRepository; +import com.reflectoring.gymbuddy.services.PersonService; +import com.reflectoring.gymbuddy.services.SessionService; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import org.springframework.stereotype.Service; + +@Service +public class PersonServiceImpl implements PersonService { + + private PersonRepository personRepository; + + private SessionService sessionService; + + public PersonServiceImpl(PersonRepository personRepository, SessionService sessionService){ + this.personRepository = personRepository; + this.sessionService = sessionService; + } + + @Override + public Person add(PersonAddRequest request) { + Person person = new PersonBuilder() + .email(request.getEmail()) + .name(request.getName()) + .lastname(request.getLastname()) + .password(request.getPassword()) + .friends(new HashSet<>()) + .sessions(new ArrayList<>()) + .build(); + + person = personRepository.save(person); + + return person; + } + + @Override + public Person update(String email, PersonUpdateRequest request) { + Optional person = personRepository.findByEmail(email); + if(person.isPresent()){ + person.get().setEmail(request.getEmail()); + person.get().setName(request.getName()); + person.get().setLastname(request.getLastname()); + person.get().setPassword(request.getPassword()); + + return personRepository.save(person.get()); + }else{ + throw new RuntimeException(); + } + } + + @Override + public Person get(String email) { + Optional person = personRepository.findByEmail(email); + if(person.isPresent()){ + return personRepository.findByEmail(email).get(); + }else{ + throw new RuntimeException(); + } + } + + @Override + public List getAll() { + return personRepository.findAll(); + } + + @Override + public void delete(String email) { + personRepository.deleteByEmail(email); + } + + @Override + public List getFriends(String email) { + Optional person = personRepository.findByEmail(email); + if(person.isPresent()){ + return person.get().getFriends().stream().collect(Collectors.toList()); + }else{ + throw new RuntimeException(); + } + } + + @Override + public Person addFriend(String email, String friendEmail) { + Optional person = personRepository.findByEmail(email); + Optional friend = personRepository.findByEmail(friendEmail); + if (person.isPresent() && friend.isPresent()) { + person.get().getFriends().add(friend.get()); + return personRepository.save(person.get()); + } else { + throw new RuntimeException(); + } + } + + @Override + public Person deleteFriend(String email, String friendEmail) { + Optional person = personRepository.findByEmail(email); + if (person.isPresent() ) { + person.get().getFriends().removeIf(x -> x.getEmail().equals(friendEmail)); + return personRepository.save(person.get()); + } else { + throw new RuntimeException(); + } } + + @Override + public Person getFriend(String email, String friendEmail) { + Optional person = personRepository.findByEmail(email); + if (person.isPresent() ) { + Optional friend = person.get().getFriends().stream().filter(x -> x.getEmail().equals(friendEmail)).findFirst(); + if(friend.isPresent()) { + return friend.get(); + } else { + throw new RuntimeException(); + } + } else { + throw new RuntimeException(); + } + } +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/implementation/SessionServiceImpl.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/implementation/SessionServiceImpl.java new file mode 100644 index 0000000..f050036 --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/implementation/SessionServiceImpl.java @@ -0,0 +1,78 @@ +package com.reflectoring.gymbuddy.services.implementation; + +import com.reflectoring.gymbuddy.domain.Person; +import com.reflectoring.gymbuddy.domain.Session; +import com.reflectoring.gymbuddy.domain.Session.SessionBuilder; +import com.reflectoring.gymbuddy.dto.session.SessionAddRequest; +import com.reflectoring.gymbuddy.dto.session.SessionUpdateRequest; +import com.reflectoring.gymbuddy.dto.workout.WorkoutAddRequest; +import com.reflectoring.gymbuddy.repository.SessionRepository; +import com.reflectoring.gymbuddy.services.SessionService; +import com.reflectoring.gymbuddy.services.WorkoutService; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import org.springframework.stereotype.Service; + +@Service +public class SessionServiceImpl implements SessionService { + + private SessionRepository sessionRepository; + + private WorkoutService workoutService; + + public SessionServiceImpl(SessionRepository sessionRepository, WorkoutService workoutService){ + this.sessionRepository = sessionRepository; + this.workoutService = workoutService; + } + + @Override + public Session add(Person person, SessionAddRequest request) { + + Session session = new SessionBuilder() + .start(request.getStart()) + .end(request.getEnd()) + .person(person) + .workouts(new ArrayList<>()) + .build(); + + session = sessionRepository.save(session); + + for(WorkoutAddRequest workoutRequest : request.getWorkouts()){ + session.getWorkouts().add(workoutService.add(session, workoutRequest)); + } + + return session; + + } + + @Override + public Session update(long id, SessionUpdateRequest request) { + + Optional session = sessionRepository.findById(id); + if(session.isPresent()){ + session.get().setStart(request.getStart()); + session.get().setEnd(request.getEnd()); + + return sessionRepository.save(session.get()); + }else{ + throw new RuntimeException(); + } + + } + + @Override + public Session get(long id) { + return sessionRepository.getById(id); + } + + @Override + public List getAll() { + return sessionRepository.findAll(); + } + + @Override + public List getForPerson(String email) { + return sessionRepository.getAllByPersonEmail(email); + } +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/implementation/SetServiceImpl.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/implementation/SetServiceImpl.java new file mode 100644 index 0000000..5031f84 --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/implementation/SetServiceImpl.java @@ -0,0 +1,56 @@ +package com.reflectoring.gymbuddy.services.implementation; + +import com.reflectoring.gymbuddy.domain.Set; +import com.reflectoring.gymbuddy.domain.Set.SetBuilder; +import com.reflectoring.gymbuddy.domain.Workout; +import com.reflectoring.gymbuddy.dto.set.SetAddRequest; +import com.reflectoring.gymbuddy.dto.set.SetUpdateRequest; +import com.reflectoring.gymbuddy.repository.SetRepository; +import com.reflectoring.gymbuddy.services.SetService; +import java.util.List; +import java.util.Optional; +import org.springframework.stereotype.Service; + +@Service +public class SetServiceImpl implements SetService { + private SetRepository setRepository; + + public SetServiceImpl(SetRepository setRepository){ + this.setRepository = setRepository; + } + @Override + public Set add(Workout workout, SetAddRequest request) { + Set set = new SetBuilder() + .reps(request.getReps()) + .weight(request.getWeight()) + .workout(workout) + .build(); + + set = setRepository.save(set); + return set; + } + + @Override + public Set update(long id,SetUpdateRequest request) { + Optional set = setRepository.findById(id); + if(set.isPresent()){ + set.get().setWeight(request.getWeight()); + set.get().setReps(request.getReps()); + return setRepository.save(set.get()); + }else{ + throw new RuntimeException(); + } + } + + @Override + public List getSetsOfWorkout(long workoutId) { + + return setRepository.findAllByWorkout(workoutId); + + } + + @Override + public void delete(long id) { + setRepository.deleteById(id); + } +} diff --git a/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/implementation/WorkoutServiceImpl.java b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/implementation/WorkoutServiceImpl.java new file mode 100644 index 0000000..0cdf45c --- /dev/null +++ b/testing/assertJ/src/main/java/com/reflectoring/gymbuddy/services/implementation/WorkoutServiceImpl.java @@ -0,0 +1,58 @@ +package com.reflectoring.gymbuddy.services.implementation; + +import com.reflectoring.gymbuddy.domain.Session; +import com.reflectoring.gymbuddy.domain.Workout; +import com.reflectoring.gymbuddy.domain.Workout.WorkoutBuilder; +import com.reflectoring.gymbuddy.dto.set.SetAddRequest; +import com.reflectoring.gymbuddy.dto.workout.WorkoutAddRequest; +import com.reflectoring.gymbuddy.repository.WorkoutRepository; +import com.reflectoring.gymbuddy.services.SetService; +import com.reflectoring.gymbuddy.services.WorkoutService; +import java.util.ArrayList; +import java.util.List; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class WorkoutServiceImpl implements WorkoutService { + + private WorkoutRepository workoutRepository; + + private SetService setService; + + public WorkoutServiceImpl(WorkoutRepository workoutRepository, SetService setService){ + this.workoutRepository = workoutRepository; + this.setService = setService; + } + + @Override + @Transactional + public Workout add(Session session, WorkoutAddRequest request) { + Workout workout = new WorkoutBuilder() + .session(session) + .sets(new ArrayList<>()) + .build(); + workout = workoutRepository.save(workout); + + for(SetAddRequest setRequest : request.getSets()){ + workout.getSets().add(setService.add(workout, setRequest)); + } + + return workout; + } + + @Override + public List getAll(long sessionId) { + return workoutRepository.findAllBySession(sessionId); + } + + @Override + public Workout get(long id) { + return workoutRepository.getById(id); + } + + @Override + public void delete(long id) { + workoutRepository.deleteById(id); + } +} diff --git a/testing/assertJ/src/main/resources/application.properties b/testing/assertJ/src/main/resources/application.properties new file mode 100644 index 0000000..365be0a --- /dev/null +++ b/testing/assertJ/src/main/resources/application.properties @@ -0,0 +1,12 @@ +spring.application.name=Gym Buddy + +spring.datasource.url=jdbc:h2:mem:gymbuddy +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=reflectoring +spring.datasource.password=reflectoring +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect + +spring.jpa.hibernate.ddl-auto=create-drop + +spring.h2.console.enabled=true +spring.h2.console.path=/h2 diff --git a/testing/assertJ/src/test/java/com/reflectoring/gymbuddy/ExtractedPropertiesTests.java b/testing/assertJ/src/test/java/com/reflectoring/gymbuddy/ExtractedPropertiesTests.java new file mode 100644 index 0000000..2dfa4f0 --- /dev/null +++ b/testing/assertJ/src/test/java/com/reflectoring/gymbuddy/ExtractedPropertiesTests.java @@ -0,0 +1,314 @@ +package com.reflectoring.gymbuddy; + +import com.reflectoring.gymbuddy.domain.Person; +import com.reflectoring.gymbuddy.domain.Session; +import com.reflectoring.gymbuddy.dto.person.PersonAddRequest; +import com.reflectoring.gymbuddy.dto.session.SessionAddRequest; +import com.reflectoring.gymbuddy.dto.set.SetAddRequest; +import com.reflectoring.gymbuddy.dto.workout.WorkoutAddRequest; +import com.reflectoring.gymbuddy.extractors.PersonExtractors; +import com.reflectoring.gymbuddy.services.PersonService; +import com.reflectoring.gymbuddy.services.SessionService; +import com.reflectoring.gymbuddy.services.SetService; +import com.reflectoring.gymbuddy.services.WorkoutService; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.*; + +@SpringBootTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +public class ExtractedPropertiesTests { + + @Autowired + PersonService personService; + + @Autowired + SessionService sessionService; + + @Autowired + WorkoutService workoutService; + + @Autowired + SetService setService; + + + @BeforeAll + void init() { + // Adding persons + PersonAddRequest ironmanReq = new PersonAddRequest.PersonAddRequestBuilder() + .name("Tony") + .lastname("Stark") + .email("tony.stark@avengers.com") + .password("avengers") + .build(); + PersonAddRequest hulkReq = new PersonAddRequest.PersonAddRequestBuilder() + .name("Bruce") + .lastname("Banner") + .email("bruce.banner@avengers.com") + .password("avengers") + .build(); + PersonAddRequest marvelReq = new PersonAddRequest.PersonAddRequestBuilder() + .name("Carol") + .lastname("Danvers") + .email("carol.danvers@avengers.com") + .password("avengers") + .build(); + PersonAddRequest widowReq = new PersonAddRequest.PersonAddRequestBuilder() + .name("Natalia") + .lastname("Romanova") + .email("natalia.romanova@avengers.com") + .password("avengers") + .build(); + + Person ironman = personService.add(ironmanReq); + Person hulk = personService.add(hulkReq); + Person marvel = personService.add(marvelReq); + Person widow = personService.add(widowReq); + + // Adding friends to each person + personService.addFriend(ironman.getEmail(), hulk.getEmail()); + personService.addFriend(ironman.getEmail(), widow.getEmail()); + + personService.addFriend(hulk.getEmail(), widow.getEmail()); + personService.addFriend(hulk.getEmail(), marvel.getEmail()); + + // Sets requests + SetAddRequest pushupSetV1 = new SetAddRequest.SetAddRequestBuilder() + .weight(0) + .reps(50) + .build(); + SetAddRequest pushupSetV2 = new SetAddRequest.SetAddRequestBuilder() + .weight(0) + .reps(25) + .build(); + + SetAddRequest pullupsSetV1 = new SetAddRequest.SetAddRequestBuilder() + .weight(0) + .reps(20) + .build(); + SetAddRequest pullupsSetV2 = new SetAddRequest.SetAddRequestBuilder() + .weight(0) + .reps(25) + .build(); + SetAddRequest pullupsSetV3 = new SetAddRequest.SetAddRequestBuilder() + .weight(0) + .reps(35) + .build(); + SetAddRequest pullupsSetV4 = new SetAddRequest.SetAddRequestBuilder() + .weight(0) + .reps(5) + .build(); + + SetAddRequest squatsSetV1 = new SetAddRequest.SetAddRequestBuilder() + .weight(120) + .reps(20) + .build(); + + SetAddRequest deadliftsSetV1 = new SetAddRequest.SetAddRequestBuilder() + .weight(80) + .reps(40) + .build(); + SetAddRequest deadliftsSetV2 = new SetAddRequest.SetAddRequestBuilder() + .weight(150) + .reps(20) + .build(); + SetAddRequest deadliftsSetV3 = new SetAddRequest.SetAddRequestBuilder() + .weight(250) + .reps(5) + .build(); + + SetAddRequest hiitSetV1 = new SetAddRequest.SetAddRequestBuilder() + .weight(0) + .reps(5) + .build(); + SetAddRequest hiitSetV2 = new SetAddRequest.SetAddRequestBuilder() + .weight(0) + .reps(50) + .build(); + SetAddRequest hiitSetV3 = new SetAddRequest.SetAddRequestBuilder() + .weight(0) + .reps(25) + .build(); + SetAddRequest hiitSetV4 = new SetAddRequest.SetAddRequestBuilder() + .weight(50) + .reps(40) + .build(); + SetAddRequest hiitSetV5 = new SetAddRequest.SetAddRequestBuilder() + .weight(100) + .reps(5) + .build(); + + + // Workout requests + WorkoutAddRequest pushups = new WorkoutAddRequest.WorkoutAddRequestBuilder() + .sets(List.of(pushupSetV1, pushupSetV1, pushupSetV2, pushupSetV1, pushupSetV2)) + .build(); + WorkoutAddRequest pullups = new WorkoutAddRequest.WorkoutAddRequestBuilder() + .sets(List.of(pullupsSetV1, pullupsSetV2, pullupsSetV1, pullupsSetV4, pullupsSetV3)) + .build(); + WorkoutAddRequest squats = new WorkoutAddRequest.WorkoutAddRequestBuilder() + .sets(List.of(squatsSetV1, squatsSetV1, squatsSetV1, squatsSetV1, squatsSetV1, squatsSetV1)) + .build(); + WorkoutAddRequest deadlifts = new WorkoutAddRequest.WorkoutAddRequestBuilder() + .sets(List.of(deadliftsSetV1, deadliftsSetV2, deadliftsSetV1, deadliftsSetV2, deadliftsSetV3)) + .build(); + WorkoutAddRequest hiit = new WorkoutAddRequest.WorkoutAddRequestBuilder() + .sets(List.of(hiitSetV1, hiitSetV2, hiitSetV3, hiitSetV4, hiitSetV5)) + .build(); + + // Adding session to each + SessionAddRequest ironmanSessionOne = new SessionAddRequest.SessionAddRequestBuilder() + .start(LocalDateTime.now()) + .end(LocalDateTime.now().plusHours(2)) + .workouts(List.of(pushups, pullups, squats)) + .build(); + SessionAddRequest ironmanSessionTwo = new SessionAddRequest.SessionAddRequestBuilder() + .start(LocalDateTime.now().minusDays(2)) + .end(LocalDateTime.now().minusDays(2).plusHours(3)) + .workouts(List.of(deadlifts, squats)) + .build(); + SessionAddRequest ironmanSessionThree = new SessionAddRequest.SessionAddRequestBuilder() + .start(LocalDateTime.now().minusDays(3)) + .end(LocalDateTime.now().minusDays(3).plusHours(2)) + .workouts(List.of(hiit)) + .build(); + + SessionAddRequest hulkSessionOne = new SessionAddRequest.SessionAddRequestBuilder() + .start(LocalDateTime.now()) + .end(LocalDateTime.now().plusHours(3)) + .workouts(List.of(squats, deadlifts)) + .build(); + SessionAddRequest hulkSessionTwo = new SessionAddRequest.SessionAddRequestBuilder() + .start(LocalDateTime.now().minusDays(4)) + .end(LocalDateTime.now().minusDays(4).plusHours(2)) + .workouts(List.of(pullups, pushups, hiit)) + .build(); + + SessionAddRequest marvelSessionOne = new SessionAddRequest.SessionAddRequestBuilder() + .start(LocalDateTime.now()) + .end(LocalDateTime.now().plusHours(2)) + .workouts(List.of(pushups, pullups, squats)) + .build(); + SessionAddRequest marvelSessionTwo = new SessionAddRequest.SessionAddRequestBuilder() + .start(LocalDateTime.now().minusDays(5)) + .end(LocalDateTime.now().minusDays(5).plusHours(4)) + .workouts(List.of(deadlifts, squats)) + .build(); + SessionAddRequest marvelSessionThree = new SessionAddRequest.SessionAddRequestBuilder() + .start(LocalDateTime.now().minusDays(1)) + .end(LocalDateTime.now().minusDays(1).plusHours(1)) + .workouts(List.of(hiit)) + .build(); + SessionAddRequest marvelSessionFour = new SessionAddRequest.SessionAddRequestBuilder() + .start(LocalDateTime.now().minusDays(10)) + .end(LocalDateTime.now().minusDays(10).plusHours(5)) + .workouts(List.of(pushups, pullups, squats, deadlifts)) + .build(); + + SessionAddRequest widowSessionOne = new SessionAddRequest.SessionAddRequestBuilder() + .start(LocalDateTime.now()) + .end(LocalDateTime.now().plusHours(4)) + .workouts(List.of(hiit, squats)) + .build(); + + // Adding sessions to persons + sessionService.add(ironman, ironmanSessionOne); + sessionService.add(ironman, ironmanSessionTwo); + sessionService.add(ironman, ironmanSessionThree); + + sessionService.add(hulk, hulkSessionOne); + sessionService.add(hulk, hulkSessionTwo); + + sessionService.add(marvel, marvelSessionOne); + sessionService.add(marvel, marvelSessionTwo); + sessionService.add(marvel, marvelSessionThree); + sessionService.add(marvel, marvelSessionFour); + + sessionService.add(widow, widowSessionOne); + } + + @Test + void checkByName_UsingExtracting(){ + assertThat(personService.getAll()) + .extracting("name") + .contains("Tony","Bruce","Carol","Natalia") + .doesNotContain("Peter","Steve"); + } + + @Test + void checkByNameAndLastname_UsingExtracting(){ + assertThat(personService.getAll()) + .extracting("name","lastname") + .contains(tuple("Tony","Stark"), tuple("Carol", "Danvers"), tuple("Bruce", "Banner"),tuple("Natalia","Romanova")) + .doesNotContain(tuple("Peter", "Parker"), tuple("Steve","Rogers")); + } + + @Test + void checkByNestedAtrribute_UsingExtracting(){ + assertThat(sessionService.getAll()) + .filteredOn(session -> session.getStart().isAfter(LocalDateTime.now().minusHours(1))) + .extracting("person.name") + .contains("Tony","Bruce","Carol","Natalia"); + } + + @Test + void checkByNestedAtrribute_PersonIsNUll_UsingExtracting(){ + List sessions = sessionService.getAll().stream().map( + session -> { + if(session.getPerson().getName().equals("Tony")){ + return new Session.SessionBuilder() + .id(session.getId()) + .start(session.getStart()) + .end(session.getEnd()) + .workouts(session.getWorkouts()) + .person(null) + .build(); + } + return session; + } + ).collect(Collectors.toList()); + + assertThat(sessions) + .filteredOn(session -> session.getStart().isAfter(LocalDateTime.now().minusHours(1))) + .extracting("person.name") + .contains("Bruce","Carol","Natalia"); + } + + // ----------- Flatmap extracting --------- + + @Test + void filterOnAllSessionsThatAreFromToday_flatMapExtracting(){ + assertThat(personService.getAll()).flatExtracting("sessions") + .filteredOn(session -> ((Session)session).getStart().isAfter(LocalDateTime.now().minusHours(1))) + .extracting("person.name") + .contains("Tony", "Carol","Bruce","Natalia"); + } + + @Test + void filterOnAllSessionsThatAreFromToday_flatMapExtractingMethod(){ + assertThat(personService.getAll()).flatExtracting(PersonExtractors.sessions()) + .filteredOn(session -> session.getStart().isAfter(LocalDateTime.now().minusHours(1))) + .extracting("person.name") + .contains("Tony", "Carol","Bruce","Natalia"); + } + + // ----------- Method call extracting -------- + @Test + void filterOnAllSesionThatAreFomToday_methodCallExtractingMethod(){ + assertThat(sessionService.getAll()) + .extractingResultOf("getDurationInMinutes", Long.class) + .filteredOn(duration -> duration < 120l) + .hasSize(1); + } + +} \ No newline at end of file diff --git a/testing/assertJ/src/test/java/com/reflectoring/gymbuddy/FilteringTests.java b/testing/assertJ/src/test/java/com/reflectoring/gymbuddy/FilteringTests.java new file mode 100644 index 0000000..c041f6a --- /dev/null +++ b/testing/assertJ/src/test/java/com/reflectoring/gymbuddy/FilteringTests.java @@ -0,0 +1,353 @@ +package com.reflectoring.gymbuddy; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.in; + +import com.reflectoring.gymbuddy.conditions.SessionStartedTodayCondition; +import com.reflectoring.gymbuddy.domain.Person; +import com.reflectoring.gymbuddy.domain.Session; +import com.reflectoring.gymbuddy.dto.person.PersonAddRequest; +import com.reflectoring.gymbuddy.dto.person.PersonAddRequest.PersonAddRequestBuilder; +import com.reflectoring.gymbuddy.dto.session.SessionAddRequest; +import com.reflectoring.gymbuddy.dto.session.SessionAddRequest.SessionAddRequestBuilder; +import com.reflectoring.gymbuddy.dto.set.SetAddRequest; +import com.reflectoring.gymbuddy.dto.set.SetAddRequest.SetAddRequestBuilder; +import com.reflectoring.gymbuddy.dto.workout.WorkoutAddRequest; +import com.reflectoring.gymbuddy.dto.workout.WorkoutAddRequest.WorkoutAddRequestBuilder; +import com.reflectoring.gymbuddy.services.PersonService; +import com.reflectoring.gymbuddy.services.SessionService; +import com.reflectoring.gymbuddy.services.SetService; +import com.reflectoring.gymbuddy.services.WorkoutService; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; + +@SpringBootTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +public class FilteringTests { + + @Autowired + PersonService personService; + + @Autowired + SessionService sessionService; + + @Autowired + WorkoutService workoutService; + + @Autowired + SetService setService; + + + @BeforeAll + void init(){ + // Adding persons + PersonAddRequest ironmanReq = new PersonAddRequestBuilder() + .name("Tony") + .lastname("Stark") + .email("tony.stark@avengers.com") + .password("avengers") + .build(); + PersonAddRequest hulkReq = new PersonAddRequestBuilder() + .name("Bruce") + .lastname("Banner") + .email("bruce.banner@avengers.com") + .password("avengers") + .build(); + PersonAddRequest marvelReq = new PersonAddRequestBuilder() + .name("Carol") + .lastname("Danvers") + .email("carol.danvers@avengers.com") + .password("avengers") + .build(); + PersonAddRequest widowReq = new PersonAddRequestBuilder() + .name("Natalia") + .lastname("Romanova") + .email("natalia.romanova@avengers.com") + .password("avengers") + .build(); + + Person ironman = personService.add(ironmanReq); + Person hulk = personService.add(hulkReq); + Person marvel = personService.add(marvelReq); + Person widow = personService.add(widowReq); + + // Adding friends to each person + personService.addFriend(ironman.getEmail(), hulk.getEmail()); + personService.addFriend(ironman.getEmail(),widow.getEmail()); + + personService.addFriend(hulk.getEmail(), widow.getEmail()); + personService.addFriend(hulk.getEmail(), marvel.getEmail()); + + // Sets requests + SetAddRequest pushupSetV1 = new SetAddRequestBuilder() + .weight(0) + .reps(50) + .build(); + SetAddRequest pushupSetV2 = new SetAddRequestBuilder() + .weight(0) + .reps(25) + .build(); + + SetAddRequest pullupsSetV1 = new SetAddRequestBuilder() + .weight(0) + .reps(20) + .build(); + SetAddRequest pullupsSetV2 = new SetAddRequestBuilder() + .weight(0) + .reps(25) + .build(); + SetAddRequest pullupsSetV3 = new SetAddRequestBuilder() + .weight(0) + .reps(35) + .build(); + SetAddRequest pullupsSetV4 = new SetAddRequestBuilder() + .weight(0) + .reps(5) + .build(); + + SetAddRequest squatsSetV1 = new SetAddRequestBuilder() + .weight(120) + .reps(20) + .build(); + + SetAddRequest deadliftsSetV1 = new SetAddRequestBuilder() + .weight(80) + .reps(40) + .build(); + SetAddRequest deadliftsSetV2 = new SetAddRequestBuilder() + .weight(150) + .reps(20) + .build(); + SetAddRequest deadliftsSetV3 = new SetAddRequestBuilder() + .weight(250) + .reps(5) + .build(); + + SetAddRequest hiitSetV1 = new SetAddRequestBuilder() + .weight(0) + .reps(5) + .build(); + SetAddRequest hiitSetV2 = new SetAddRequestBuilder() + .weight(0) + .reps(50) + .build(); + SetAddRequest hiitSetV3 = new SetAddRequestBuilder() + .weight(0) + .reps(25) + .build(); + SetAddRequest hiitSetV4 = new SetAddRequestBuilder() + .weight(50) + .reps(40) + .build(); + SetAddRequest hiitSetV5 = new SetAddRequestBuilder() + .weight(100) + .reps(5) + .build(); + + + // Workout requests + WorkoutAddRequest pushups = new WorkoutAddRequestBuilder() + .sets(List.of(pushupSetV1, pushupSetV1, pushupSetV2, pushupSetV1, pushupSetV2)) + .build(); + WorkoutAddRequest pullups = new WorkoutAddRequestBuilder() + .sets(List.of(pullupsSetV1, pullupsSetV2, pullupsSetV1, pullupsSetV4, pullupsSetV3)) + .build(); + WorkoutAddRequest squats = new WorkoutAddRequestBuilder() + .sets(List.of(squatsSetV1,squatsSetV1,squatsSetV1,squatsSetV1,squatsSetV1,squatsSetV1)) + .build(); + WorkoutAddRequest deadlifts = new WorkoutAddRequestBuilder() + .sets(List.of(deadliftsSetV1, deadliftsSetV2, deadliftsSetV1, deadliftsSetV2, deadliftsSetV3)) + .build(); + WorkoutAddRequest hiit = new WorkoutAddRequestBuilder() + .sets(List.of(hiitSetV1,hiitSetV2,hiitSetV3,hiitSetV4,hiitSetV5)) + .build(); + + // Adding session to each + SessionAddRequest ironmanSessionOne = new SessionAddRequestBuilder() + .start(LocalDateTime.now()) + .end(LocalDateTime.now().plusHours(2)) + .workouts(List.of(pushups, pullups, squats)) + .build(); + SessionAddRequest ironmanSessionTwo = new SessionAddRequestBuilder() + .start(LocalDateTime.now().minusDays(2)) + .end(LocalDateTime.now().minusDays(2).plusHours(3)) + .workouts(List.of(deadlifts,squats)) + .build(); + SessionAddRequest ironmanSessionThree = new SessionAddRequestBuilder() + .start(LocalDateTime.now().minusDays(3)) + .end(LocalDateTime.now().minusDays(3).plusHours(2)) + .workouts(List.of(hiit)) + .build(); + + SessionAddRequest hulkSessionOne = new SessionAddRequestBuilder() + .start(LocalDateTime.now()) + .end(LocalDateTime.now().plusHours(3)) + .workouts(List.of(squats, deadlifts)) + .build(); + SessionAddRequest hulkSessionTwo = new SessionAddRequestBuilder() + .start(LocalDateTime.now().minusDays(4)) + .end(LocalDateTime.now().minusDays(4).plusHours(2)) + .workouts(List.of(pullups, pushups, hiit)) + .build(); + + SessionAddRequest marvelSessionOne = new SessionAddRequestBuilder() + .start(LocalDateTime.now()) + .end(LocalDateTime.now().plusHours(2)) + .workouts(List.of(pushups, pullups, squats)) + .build(); + SessionAddRequest marvelSessionTwo = new SessionAddRequestBuilder() + .start(LocalDateTime.now().minusDays(5)) + .end(LocalDateTime.now().minusDays(5).plusHours(4)) + .workouts(List.of(deadlifts, squats)) + .build(); + SessionAddRequest marvelSessionThree = new SessionAddRequestBuilder() + .start(LocalDateTime.now().minusDays(1)) + .end(LocalDateTime.now().minusDays(1).plusHours(1)) + .workouts(List.of(hiit)) + .build(); + SessionAddRequest marvelSessionFour = new SessionAddRequestBuilder() + .start(LocalDateTime.now().minusDays(10)) + .end(LocalDateTime.now().minusDays(10).plusHours(5)) + .workouts(List.of(pushups, pullups, squats, deadlifts)) + .build(); + + SessionAddRequest widowSessionOne = new SessionAddRequestBuilder() + .start(LocalDateTime.now()) + .end(LocalDateTime.now().plusHours(4)) + .workouts(List.of(hiit, squats)) + .build(); + + // Adding sessions to persons + sessionService.add(ironman,ironmanSessionOne); + sessionService.add(ironman,ironmanSessionTwo); + sessionService.add(ironman, ironmanSessionThree); + + sessionService.add(hulk, hulkSessionOne); + sessionService.add(hulk, hulkSessionTwo); + + sessionService.add(marvel, marvelSessionOne); + sessionService.add(marvel, marvelSessionTwo); + sessionService.add(marvel, marvelSessionThree); + sessionService.add(marvel, marvelSessionFour); + + sessionService.add(widow, widowSessionOne); + } + + // ---------- Predicate filtering ------------ + @Test + void checkIfTonyIsInList_basicFiltering(){ + assertThat(personService.getAll()).filteredOn(person -> person.getName().equals("Tony")).isNotEmpty(); + } + + @Test + @Disabled + void checkIfTonyIsInList_NullValue_basicFiltering(){ + List sessions = sessionService.getAll().stream().map( + session -> { + if(session.getPerson().getName().equals("Tony")){ + return new Session.SessionBuilder() + .id(session.getId()) + .start(session.getStart()) + .end(session.getEnd()) + .workouts(session.getWorkouts()) + .person(null) + .build(); + } + return session; + } + ).collect(Collectors.toList()); + + assertThat(sessions).filteredOn(session -> session.getPerson().getName().equals("Tony")).isEmpty(); + } + + // How to filter when list is inside list + // Check how many sessions there are that are done today + // Filtering on nested properties + + @Test + void filterOnAllSessionsThatAreFromToday_nestedFiltering() { + assertThat(personService.getAll()) + .map(person -> person.getSessions().stream().filter(session -> session.getStart().isAfter(LocalDateTime.now().minusHours(1))).count()) + .filteredOn(session -> session > 0).size().isEqualTo(4); + } + + //Filtering on complex conditions + @Test + void filterOnNameContainsOAndNumberOfFriends_complexFiltering(){ + assertThat(personService.getAll()) + .filteredOn(person -> person.getName().contains("o") && person.getFriends().size() > 1) + .hasSize(1); + } + + + // -------------- Field filtering -------------- + @Test + void checkIfTonyIsInList_basicFieldFiltering(){ + assertThat(personService.getAll()).filteredOn("name", "Tony").isNotEmpty(); + } + + @Test + void checkIfTonyIsInList_NullValue_basicFieldFiltering(){ + List sessions = sessionService.getAll().stream().map( + session -> { + if(session.getPerson().getName().equals("Tony")){ + return new Session.SessionBuilder() + .id(session.getId()) + .start(session.getStart()) + .end(session.getEnd()) + .workouts(session.getWorkouts()) + .person(null) + .build(); + } + return session; + } + ).collect(Collectors.toList()); + + assertThat(sessions).filteredOn("person.name","Tony").isEmpty(); + } + //Filtering on complex conditions + @Test + void filterOnNameContainsOAndNumberOfFriends_complexFieldFiltering() { + assertThat(personService.getAll()) + .filteredOn("name", in("Tony","Carol")) + .filteredOn(person -> person.getFriends().size() > 1) + .hasSize(1); + } + + // ---------- Custom condition filtering ------------ + + // How to filter when list is inside list + // Check how many sessions there are that are done today + // Filtering on nested properties + + @Test + void filterOnAllSessionsThatAreFromToday_customConditionFiltering() { + Condition sessionStartedToday = new SessionStartedTodayCondition(); + assertThat(personService.getAll()) + .filteredOn(sessionStartedToday) + .hasSize(4); + } + + //Filtering on complex conditions + @Test + void filterOnNameContainsOAndNumberOfFriends_customConditionFiltering(){ + Condition nameAndFriendsCondition = new Condition<>(){ + @Override + public boolean matches(Person person){ + return person.getName().contains("o") && person.getFriends().size() > 1; + } + }; + assertThat(personService.getAll()) + .filteredOn(nameAndFriendsCondition) + .hasSize(1); + } + +} diff --git a/testing/assertJ/src/test/java/com/reflectoring/gymbuddy/conditions/SessionStartedTodayCondition.java b/testing/assertJ/src/test/java/com/reflectoring/gymbuddy/conditions/SessionStartedTodayCondition.java new file mode 100644 index 0000000..47e6000 --- /dev/null +++ b/testing/assertJ/src/test/java/com/reflectoring/gymbuddy/conditions/SessionStartedTodayCondition.java @@ -0,0 +1,15 @@ +package com.reflectoring.gymbuddy.conditions; + +import com.reflectoring.gymbuddy.domain.Person; +import org.assertj.core.api.Condition; + +import java.time.LocalDateTime; + +public class SessionStartedTodayCondition extends Condition { + + @Override + public boolean matches(Person person){ + return person.getSessions().stream() + .anyMatch(session -> session.getStart().isAfter(LocalDateTime.now().minusHours(1))); + } +} diff --git a/testing/assertJ/src/test/java/com/reflectoring/gymbuddy/extractors/PersonExtractors.java b/testing/assertJ/src/test/java/com/reflectoring/gymbuddy/extractors/PersonExtractors.java new file mode 100644 index 0000000..9fd781c --- /dev/null +++ b/testing/assertJ/src/test/java/com/reflectoring/gymbuddy/extractors/PersonExtractors.java @@ -0,0 +1,24 @@ +package com.reflectoring.gymbuddy.extractors; + +import com.reflectoring.gymbuddy.domain.Person; +import com.reflectoring.gymbuddy.domain.Session; + + +import java.util.List; +import java.util.function.Function; + +public class PersonExtractors { + + public PersonExtractors(){} + + public static Function> sessions(){ + return new PersonSessionExtractor(); + } + + private static class PersonSessionExtractor implements Function> { + @Override + public List apply(Person person) { + return person.getSessions(); + } + } +}