From 3715c54d7919e1912953c5111b299de7380659c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Michaluk?= Date: Sun, 4 Mar 2018 09:23:47 +0100 Subject: [PATCH] improved code coverage and bug fix --- .../planning/DeliveriesSuggestion.java | 2 +- .../planning/DeliveriesSuggestionSpec.groovy | 100 ++++++++++++++++++ .../planning/DeliveryAutoPlannerSpec.groovy | 58 ++++++++++ .../forecasting/DemandServiceSpec.groovy | 73 +++++++++++++ docker-compose.yml | 13 --- .../ShortageCalculationAlgorithmSpec.groovy | 2 +- .../ShortageCalculationExamplesSpec.groovy | 2 +- .../ShortagesCalculationAssembler.groovy | 2 +- ...roovy => ShortagesCalculationTrait.groovy} | 2 +- .../ShortagePredictionProcessSpec.groovy | 76 +------------ .../ShortagePredictionProcessTrait.groovy | 77 ++++++++++++++ .../ShortagePredictionServiceSpec.groovy | 26 +++++ 12 files changed, 343 insertions(+), 90 deletions(-) create mode 100644 demand-forecasting-model/src/test/groovy/pl/com/bottega/factory/delivery/planning/DeliveriesSuggestionSpec.groovy create mode 100644 demand-forecasting-model/src/test/groovy/pl/com/bottega/factory/delivery/planning/DeliveryAutoPlannerSpec.groovy create mode 100644 demand-forecasting-model/src/test/groovy/pl/com/bottega/factory/demand/forecasting/DemandServiceSpec.groovy delete mode 100644 docker-compose.yml rename shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/{ShortagesCalculationAssemblerTrait.groovy => ShortagesCalculationTrait.groovy} (97%) create mode 100644 shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessTrait.groovy create mode 100644 shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionServiceSpec.groovy diff --git a/demand-forecasting-model/src/main/java/pl/com/bottega/factory/delivery/planning/DeliveriesSuggestion.java b/demand-forecasting-model/src/main/java/pl/com/bottega/factory/delivery/planning/DeliveriesSuggestion.java index f23b86b..7ffc88d 100644 --- a/demand-forecasting-model/src/main/java/pl/com/bottega/factory/delivery/planning/DeliveriesSuggestion.java +++ b/demand-forecasting-model/src/main/java/pl/com/bottega/factory/delivery/planning/DeliveriesSuggestion.java @@ -17,7 +17,7 @@ interface DeliveriesSuggestion { timesAndFractions.entrySet().stream() .map(e -> new Delivery( refNo, - date.atTime(e.getKey()), ((long) ((double) demand.getLevel() / e.getValue()))) + date.atTime(e.getKey()), ((long) ((double) demand.getLevel() * e.getValue()))) ); } diff --git a/demand-forecasting-model/src/test/groovy/pl/com/bottega/factory/delivery/planning/DeliveriesSuggestionSpec.groovy b/demand-forecasting-model/src/test/groovy/pl/com/bottega/factory/delivery/planning/DeliveriesSuggestionSpec.groovy new file mode 100644 index 0000000..97c24af --- /dev/null +++ b/demand-forecasting-model/src/test/groovy/pl/com/bottega/factory/delivery/planning/DeliveriesSuggestionSpec.groovy @@ -0,0 +1,100 @@ +package pl.com.bottega.factory.delivery.planning + +import pl.com.bottega.factory.demand.forecasting.Demand +import spock.lang.PendingFeature +import spock.lang.Specification + +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.util.stream.Collectors + +import static pl.com.bottega.factory.demand.forecasting.Demand.Schema.AtDayStart + +class DeliveriesSuggestionSpec extends Specification { + + def final refNo = "3009000" + def final date = LocalDate.now() + def final midnight = LocalTime.of(0, 0) + def final sevenAM = LocalTime.of(7, 0) + def final nineAM = LocalTime.of(9, 0) + + def "Times and fractions - all at once"() { + given: + def suggestion = DeliveriesSuggestion.timesAndFractions([(sevenAM): 1.0d]) + + when: + def deliveries = suggestion.deliveriesFor(refNo, date, atDayStart(2000)) + .collect(Collectors.toList()) + + then: + deliveries == [deliveryAt(date.atTime(sevenAM), 2000)] + } + + def "Times and fractions - half twice"() { + given: + def suggestion = DeliveriesSuggestion.timesAndFractions([ + (sevenAM): 0.5d, + (nineAM): 0.5d, + ]) + + when: + def deliveries = suggestion.deliveriesFor(refNo, date, atDayStart(2000)) + .collect(Collectors.toList()) + + then: + deliveries == [ + deliveryAt(date.atTime(sevenAM), 1000), + deliveryAt(date.atTime(nineAM), 1000), + ] + } + + def "Times and fractions - not represented number"() { + given: + def suggestion = DeliveriesSuggestion.timesAndFractions([ + (midnight): 1/3d, + (sevenAM): 1/3d, + (nineAM): 1/3d, + ]) + + when: + def deliveries = suggestion.deliveriesFor(refNo, date, atDayStart(2000)) + .collect(Collectors.toList()) + + then: + deliveries == [ + deliveryAt(date.atTime(midnight), 666), + deliveryAt(date.atTime(sevenAM), 666), + deliveryAt(date.atTime(nineAM), 666), + ] + } + + @PendingFeature + def "Times and fractions - do not lost parts on not represented number"() { + given: + def suggestion = DeliveriesSuggestion.timesAndFractions([ + (midnight): 1/3d, + (sevenAM): 1/3d, + (nineAM): 1/3d, + ]) + + when: + def deliveries = suggestion.deliveriesFor(refNo, date, atDayStart(2000)) + .collect(Collectors.toList()) + + then: + deliveries == [ + deliveryAt(date.atTime(midnight), 666), + deliveryAt(date.atTime(sevenAM), 666), + deliveryAt(date.atTime(nineAM), 668), + ] + } + + def deliveryAt(LocalDateTime time, Integer level) { + new Delivery(refNo, time, level) + } + + def atDayStart(long level) { + Demand.of(level, AtDayStart) + } +} diff --git a/demand-forecasting-model/src/test/groovy/pl/com/bottega/factory/delivery/planning/DeliveryAutoPlannerSpec.groovy b/demand-forecasting-model/src/test/groovy/pl/com/bottega/factory/delivery/planning/DeliveryAutoPlannerSpec.groovy new file mode 100644 index 0000000..936fb19 --- /dev/null +++ b/demand-forecasting-model/src/test/groovy/pl/com/bottega/factory/delivery/planning/DeliveryAutoPlannerSpec.groovy @@ -0,0 +1,58 @@ +package pl.com.bottega.factory.delivery.planning + +import pl.com.bottega.factory.demand.forecasting.Demand +import spock.lang.Specification + +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.util.stream.Collectors + +import static pl.com.bottega.factory.delivery.planning.DeliveriesSuggestion.timesAndFractions +import static pl.com.bottega.factory.demand.forecasting.Demand.Schema.AtDayStart + +class DeliveryAutoPlannerSpec extends Specification { + + def final refNo = "3009000" + def final date = LocalDate.now() + def final midnight = LocalTime.of(0, 0) + def final sevenAM = LocalTime.of(7, 0) + + def "Picks dummy suggestion"() { + given: + def planner = deliveryDefinitions([:]) + + when: + def deliveries = planner.propose(date, atDayStart(2000)) + .collect(Collectors.toList()) + + then: + deliveries == [deliveryAt(date.atTime(midnight), 2000)] + } + + def "Suggests according to defined policy"() { + given: + def planner = deliveryDefinitions([ + (AtDayStart): timesAndFractions([(sevenAM): 1.0d]) + ]) + + when: + def deliveries = planner.propose(date, atDayStart(2000)) + .collect(Collectors.toList()) + + then: + deliveries == [deliveryAt(date.atTime(sevenAM), 2000)] + } + + def deliveryDefinitions(Map suggestions) { + new DeliveryAutoPlanner(refNo, suggestions) + } + + def deliveryAt(LocalDateTime time, Integer level) { + new Delivery(refNo, time, level) + } + + def atDayStart(long level) { + Demand.of(level, AtDayStart) + } +} diff --git a/demand-forecasting-model/src/test/groovy/pl/com/bottega/factory/demand/forecasting/DemandServiceSpec.groovy b/demand-forecasting-model/src/test/groovy/pl/com/bottega/factory/demand/forecasting/DemandServiceSpec.groovy new file mode 100644 index 0000000..6fdb3ff --- /dev/null +++ b/demand-forecasting-model/src/test/groovy/pl/com/bottega/factory/demand/forecasting/DemandServiceSpec.groovy @@ -0,0 +1,73 @@ +package pl.com.bottega.factory.demand.forecasting + +import spock.lang.Specification + +import java.time.LocalDate + +import static pl.com.bottega.factory.demand.forecasting.ReviewDecision.PICK_NEW + +class DemandServiceSpec extends Specification implements ProductDemandTrait { + + def events = Mock(DemandEvents) + def repo = Mock(ProductDemandRepository) + def service = new DemandService(repo) + + void setup() { + builder = new ProductDemandBuilder(events: events) + } + + def "Repository interactions while product demand initialization"() { + given: + def refNo = "3009000" + + when: + service.initNewProduct(refNo) + + then: + 1 * repo.initNewProduct(refNo) + } + + def "Repository interactions while document processing"() { + given: + def today = LocalDate.now(builder.clock) + def demand = demanded(2800, 0) + def document = document(today, 2000, 3500) + repo.get(document.refNo) >> demand + + when: + service.process(document) + + then: + 1 * repo.save(demand) + } + + def "Repository interactions while demand adjustments"() { + given: + def today = LocalDate.now(builder.clock) + def demand = demanded(2800, 0) + def adjustments = adjustments([(today): 1000]) + repo.get(adjustments.refNo) >> demand + + when: + service.adjust(adjustments) + + then: + 1 * repo.save(demand) + } + + def "Repository interactions while review processing"() { + given: + def today = LocalDate.now(builder.clock) + def demand = demand(0, 2800) + .stronglyAdjusted((today): 3500) + .build() + def review = review(today, 0, 3500, 2800) + repo.get(review.refNo) >> demand + + when: + service.review(review, PICK_NEW) + + then: + 1 * repo.save(demand) + } +} diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 3e93076..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: "2" - -volumes: - data: - driver: local - -services: - db: - build: ./database/ - volumes: - - data:/var/lib/postgresql/data - ports: - - "5432:5432" diff --git a/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortageCalculationAlgorithmSpec.groovy b/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortageCalculationAlgorithmSpec.groovy index 6b45355..37d273c 100644 --- a/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortageCalculationAlgorithmSpec.groovy +++ b/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortageCalculationAlgorithmSpec.groovy @@ -3,7 +3,7 @@ package pl.com.bottega.factory.shortages.prediction.calculation import spock.lang.Specification class ShortageCalculationAlgorithmSpec extends Specification - implements ShortagesCalculationAssemblerTrait { + implements ShortagesCalculationTrait { void setup() { TimeGrammar.apply() diff --git a/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortageCalculationExamplesSpec.groovy b/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortageCalculationExamplesSpec.groovy index ff01d87..204fe2e 100644 --- a/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortageCalculationExamplesSpec.groovy +++ b/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortageCalculationExamplesSpec.groovy @@ -3,7 +3,7 @@ package pl.com.bottega.factory.shortages.prediction.calculation import spock.lang.Specification class ShortageCalculationExamplesSpec extends Specification - implements ShortagesCalculationAssemblerTrait { + implements ShortagesCalculationTrait { void setup() { TimeGrammar.apply() diff --git a/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortagesCalculationAssembler.groovy b/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortagesCalculationAssembler.groovy index 083e054..d503cb7 100644 --- a/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortagesCalculationAssembler.groovy +++ b/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortagesCalculationAssembler.groovy @@ -1,4 +1,4 @@ package pl.com.bottega.factory.shortages.prediction.calculation -class ShortagesCalculationAssembler implements ShortagesCalculationAssemblerTrait { +class ShortagesCalculationAssembler implements ShortagesCalculationTrait { } diff --git a/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortagesCalculationAssemblerTrait.groovy b/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortagesCalculationTrait.groovy similarity index 97% rename from shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortagesCalculationAssemblerTrait.groovy rename to shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortagesCalculationTrait.groovy index 48ef91c..6f617e7 100644 --- a/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortagesCalculationAssemblerTrait.groovy +++ b/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/calculation/ShortagesCalculationTrait.groovy @@ -6,7 +6,7 @@ import pl.com.bottega.factory.shortages.prediction.Shortage import java.time.Duration import java.time.LocalDateTime -trait ShortagesCalculationAssemblerTrait { +trait ShortagesCalculationTrait { LocalDateTime now = LocalDateTime.now() String refNo = "3009000" diff --git a/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessSpec.groovy b/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessSpec.groovy index c6e86f8..ea939f3 100644 --- a/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessSpec.groovy +++ b/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessSpec.groovy @@ -1,22 +1,14 @@ package pl.com.bottega.factory.shortages.prediction.monitoring -import pl.com.bottega.factory.product.management.RefNoId -import pl.com.bottega.factory.shortages.prediction.ConfigurationParams -import pl.com.bottega.factory.shortages.prediction.Shortage -import pl.com.bottega.factory.shortages.prediction.calculation.ShortageForecasts -import pl.com.bottega.factory.shortages.prediction.calculation.ShortagesCalculationAssembler import spock.lang.Specification -import java.time.LocalDateTime - import static pl.com.bottega.factory.shortages.prediction.monitoring.NewShortage.After -class ShortagePredictionProcessSpec extends Specification { +class ShortagePredictionProcessSpec extends Specification implements ShortagePredictionProcessTrait { - def refNo = new RefNoId("3009000") - def now = LocalDateTime.now() - def forecastAssembler = new ShortagesCalculationAssembler(refNo: refNo, now: now) - def events = Mock(ShortageEvents) + void setup() { + events = Mock(ShortageEvents) + } def "Emits no events when there is still no shortages"() { given: @@ -117,64 +109,4 @@ class ShortagePredictionProcessSpec extends Specification { 0 * events.emit(_ as NewShortage) 0 * events.emit(_ as ShortageSolved) } - - ShortagePredictionProcess predictionProcess( - Shortage previouslyFound, - ShortageForecasts forecastThatWillFindShortages) { - - new ShortagePredictionProcess( - refNo, - previouslyFound, - ShortageDiffPolicy.ValuesAreNotSame, - forecastThatWillFindShortages, - defaultConfig(), - events - ) - } - - Map someShortages() { - [(now.plusHours(5)): 500L, - (now.plusDays(1)) : 500L] - } - - Map someDifferentShortages() { - [(now.plusHours(5)): 100L, - (now.plusDays(1)) : 900L] - } - - Shortage noShortagesWasPreviouslyFound() { - null - } - - Shortage wasPreviouslyFound(Map shortages) { - forecastAssembler.shortage(shortages).orElse(null) - } - - ShortageForecasts noShortagesWillBeFound() { - forecastAssembler.forecastProvider( - forecastAssembler.stock(1000), - forecastAssembler.noDeliveries(), - forecastAssembler.noProductions() - ) - } - - ShortageForecasts willFindShortages(Map shortages) { - forecastAssembler.forecastProvider( - forecastAssembler.stock(0), - forecastAssembler.deliveries(shortages), - forecastAssembler.noProductions() - ) - } - - ConfigurationParams defaultConfig() { - new InMemoryConfigurationParams(daysAhead: 14) - } - - NewShortage newShortage(After after, Map missing) { - new NewShortage(refNo, after, forecastAssembler.shortage(missing).get()) - } - - ShortageSolved shortageSolved() { - new ShortageSolved(refNo) - } } diff --git a/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessTrait.groovy b/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessTrait.groovy new file mode 100644 index 0000000..561042d --- /dev/null +++ b/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessTrait.groovy @@ -0,0 +1,77 @@ +package pl.com.bottega.factory.shortages.prediction.monitoring + +import pl.com.bottega.factory.product.management.RefNoId +import pl.com.bottega.factory.shortages.prediction.ConfigurationParams +import pl.com.bottega.factory.shortages.prediction.Shortage +import pl.com.bottega.factory.shortages.prediction.calculation.ShortageForecasts +import pl.com.bottega.factory.shortages.prediction.calculation.ShortagesCalculationAssembler + +import java.time.LocalDateTime + +trait ShortagePredictionProcessTrait { + + def refNo = new RefNoId("3009000") + def now = LocalDateTime.now() + def forecastAssembler = new ShortagesCalculationAssembler(refNo: refNo, now: now) + ShortageEvents events + + ShortagePredictionProcess predictionProcess( + Shortage previouslyFound, + ShortageForecasts forecastThatWillFindShortages) { + + new ShortagePredictionProcess( + refNo, + previouslyFound, + ShortageDiffPolicy.ValuesAreNotSame, + forecastThatWillFindShortages, + defaultConfig(), + events + ) + } + + Map someShortages() { + [(now.plusHours(5)): 500L, + (now.plusDays(1)) : 500L] + } + + Map someDifferentShortages() { + [(now.plusHours(5)): 100L, + (now.plusDays(1)) : 900L] + } + + Shortage noShortagesWasPreviouslyFound() { + null + } + + Shortage wasPreviouslyFound(Map shortages) { + forecastAssembler.shortage(shortages).orElse(null) + } + + ShortageForecasts noShortagesWillBeFound() { + forecastAssembler.forecastProvider( + forecastAssembler.stock(1000), + forecastAssembler.noDeliveries(), + forecastAssembler.noProductions() + ) + } + + ShortageForecasts willFindShortages(Map shortages) { + forecastAssembler.forecastProvider( + forecastAssembler.stock(0), + forecastAssembler.deliveries(shortages), + forecastAssembler.noProductions() + ) + } + + ConfigurationParams defaultConfig() { + new InMemoryConfigurationParams(daysAhead: 14) + } + + NewShortage newShortage(NewShortage.After after, Map missing) { + new NewShortage(refNo, after, forecastAssembler.shortage(missing).get()) + } + + ShortageSolved shortageSolved() { + new ShortageSolved(refNo) + } +} \ No newline at end of file diff --git a/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionServiceSpec.groovy b/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionServiceSpec.groovy new file mode 100644 index 0000000..f9b80ef --- /dev/null +++ b/shortages-prediction-model/src/test/groovy/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionServiceSpec.groovy @@ -0,0 +1,26 @@ +package pl.com.bottega.factory.shortages.prediction.monitoring + +import pl.com.bottega.factory.demand.forecasting.DemandedLevelsChanged +import spock.lang.Specification + +class ShortagePredictionServiceSpec extends Specification implements ShortagePredictionProcessTrait { + + def repo = Mock(ShortagePredictionProcessRepository) + def service = new ShortagePredictionService(repo) + + def "Repository interactions while processing demanded changed event"() { + given: + def process = predictionProcess( + noShortagesWasPreviouslyFound(), + noShortagesWillBeFound() + ) + repo.get(refNo) >> process + + when: + service.predictShortages(new DemandedLevelsChanged(refNo, [:])) + + then: + 1 * repo.save(process) + 0 * events.emit(_ as ShortageSolved) + } +}