improved code coverage and bug fix

This commit is contained in:
Michał Michaluk
2018-03-04 09:23:47 +01:00
parent 3e3c793548
commit 3715c54d79
12 changed files with 343 additions and 90 deletions

View File

@@ -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())))
);
}

View File

@@ -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)
}
}

View File

@@ -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<Demand.Schema, DeliveriesSuggestion> suggestions) {
new DeliveryAutoPlanner(refNo, suggestions)
}
def deliveryAt(LocalDateTime time, Integer level) {
new Delivery(refNo, time, level)
}
def atDayStart(long level) {
Demand.of(level, AtDayStart)
}
}

View File

@@ -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)
}
}

View File

@@ -1,13 +0,0 @@
version: "2"
volumes:
data:
driver: local
services:
db:
build: ./database/
volumes:
- data:/var/lib/postgresql/data
ports:
- "5432:5432"

View File

@@ -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()

View File

@@ -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()

View File

@@ -1,4 +1,4 @@
package pl.com.bottega.factory.shortages.prediction.calculation
class ShortagesCalculationAssembler implements ShortagesCalculationAssemblerTrait {
class ShortagesCalculationAssembler implements ShortagesCalculationTrait {
}

View File

@@ -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"

View File

@@ -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<LocalDateTime, Long> someShortages() {
[(now.plusHours(5)): 500L,
(now.plusDays(1)) : 500L]
}
Map<LocalDateTime, Long> someDifferentShortages() {
[(now.plusHours(5)): 100L,
(now.plusDays(1)) : 900L]
}
Shortage noShortagesWasPreviouslyFound() {
null
}
Shortage wasPreviouslyFound(Map<LocalDateTime, Long> shortages) {
forecastAssembler.shortage(shortages).orElse(null)
}
ShortageForecasts noShortagesWillBeFound() {
forecastAssembler.forecastProvider(
forecastAssembler.stock(1000),
forecastAssembler.noDeliveries(),
forecastAssembler.noProductions()
)
}
ShortageForecasts willFindShortages(Map<LocalDateTime, Long> shortages) {
forecastAssembler.forecastProvider(
forecastAssembler.stock(0),
forecastAssembler.deliveries(shortages),
forecastAssembler.noProductions()
)
}
ConfigurationParams defaultConfig() {
new InMemoryConfigurationParams(daysAhead: 14)
}
NewShortage newShortage(After after, Map<LocalDateTime, Long> missing) {
new NewShortage(refNo, after, forecastAssembler.shortage(missing).get())
}
ShortageSolved shortageSolved() {
new ShortageSolved(refNo)
}
}

View File

@@ -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<LocalDateTime, Long> someShortages() {
[(now.plusHours(5)): 500L,
(now.plusDays(1)) : 500L]
}
Map<LocalDateTime, Long> someDifferentShortages() {
[(now.plusHours(5)): 100L,
(now.plusDays(1)) : 900L]
}
Shortage noShortagesWasPreviouslyFound() {
null
}
Shortage wasPreviouslyFound(Map<LocalDateTime, Long> shortages) {
forecastAssembler.shortage(shortages).orElse(null)
}
ShortageForecasts noShortagesWillBeFound() {
forecastAssembler.forecastProvider(
forecastAssembler.stock(1000),
forecastAssembler.noDeliveries(),
forecastAssembler.noProductions()
)
}
ShortageForecasts willFindShortages(Map<LocalDateTime, Long> shortages) {
forecastAssembler.forecastProvider(
forecastAssembler.stock(0),
forecastAssembler.deliveries(shortages),
forecastAssembler.noProductions()
)
}
ConfigurationParams defaultConfig() {
new InMemoryConfigurationParams(daysAhead: 14)
}
NewShortage newShortage(NewShortage.After after, Map<LocalDateTime, Long> missing) {
new NewShortage(refNo, after, forecastAssembler.shortage(missing).get())
}
ShortageSolved shortageSolved() {
new ShortageSolved(refNo)
}
}

View File

@@ -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)
}
}