// Copyright © 2015 HSL <https://www.hsl.fi> // This program is dual-licensed under the EUPL v1.2 and AGPLv3 licenses. package fi.hsl.parkandride.core.service; import static fi.hsl.parkandride.core.domain.CapacityType.BICYCLE; import static fi.hsl.parkandride.core.domain.CapacityType.CAR; import static fi.hsl.parkandride.core.domain.CapacityType.ELECTRIC_CAR; import static fi.hsl.parkandride.core.domain.CapacityType.MOTORCYCLE; import static fi.hsl.parkandride.core.domain.DayType.BUSINESS_DAY; import static fi.hsl.parkandride.core.domain.DayType.SATURDAY; import static fi.hsl.parkandride.core.domain.Usage.COMMERCIAL; import static fi.hsl.parkandride.core.domain.Usage.PARK_AND_RIDE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import org.assertj.core.api.Assertions; import org.junit.Test; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import fi.hsl.parkandride.core.domain.*; public class CapacityPricingValidatorTest { @Test public void allow_null_pricing_method() { CapacityPricingValidator.validateAndNormalize(new Facility(), new ArrayList<>()); } @Test public void hours_can_overlap_for_different_usages() { Pricing a = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 7, 17); Pricing b = pricing(CAR, COMMERCIAL, BUSINESS_DAY, 8, 18); List<Violation> violations = new ArrayList<>(); CapacityPricingValidator.validateAndNormalizeCustomPricing(maxCapacities(a, b), ImmutableList.of(a, b), ImmutableList.of(), violations); Assertions.assertThat(violations).isEmpty(); } @Test public void hours_can_overlap_for_different_capacities() { Pricing a = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 7, 17); Pricing b = pricing(BICYCLE, PARK_AND_RIDE, BUSINESS_DAY, 8, 18); List<Violation> violations = new ArrayList<>(); CapacityPricingValidator.validateAndNormalizeCustomPricing(maxCapacities(a, b), ImmutableList.of(a, b), ImmutableList.of(), violations); Assertions.assertThat(violations).isEmpty(); } @Test public void hours_can_overlap_for_different_days() { Pricing a = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 7, 17); Pricing b = pricing(CAR, PARK_AND_RIDE, SATURDAY, 8, 18); List<Violation> violations = new ArrayList<>(); CapacityPricingValidator.validateAndNormalizeCustomPricing(maxCapacities(a, b), ImmutableList.of(a, b), ImmutableList.of(), violations); Assertions.assertThat(violations).isEmpty(); } @Test public void hours_cannot_overlap_for_same_usage_capacity_day() { Pricing a = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 7, 17); Pricing b = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 8, 18); List<Violation> violations = new ArrayList<>(); CapacityPricingValidator.validateAndNormalizeCustomPricing(maxCapacities(a, b), ImmutableList.of(a, b), ImmutableList.of(), violations); Assertions.assertThat(violations).extracting("type", "path").containsOnly(tuple("PricingOverlap", "pricing[1].time")); } @Test public void unavailable_capacity_may_equal_max() { Pricing a = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 0, 24); a.maxCapacity = 5; Pricing b = pricing(CAR, PARK_AND_RIDE, SATURDAY, 0, 24); b.maxCapacity = 10; UnavailableCapacity uc = new UnavailableCapacity(CAR, PARK_AND_RIDE, 10); List<Violation> violations = new ArrayList<>(); CapacityPricingValidator.validateAndNormalizeCustomPricing(maxCapacities(a, b), ImmutableList.of(a, b), asList(uc), violations); Assertions.assertThat(violations).isEmpty(); } @Test public void pricing_order_doesnt_count_in_unavailable_capacity_max_check() { Pricing a = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 0, 24); a.maxCapacity = 5; Pricing b = pricing(CAR, PARK_AND_RIDE, SATURDAY, 0, 24); b.maxCapacity = 10; UnavailableCapacity uc = new UnavailableCapacity(CAR, PARK_AND_RIDE, 10); List<Violation> violations = new ArrayList<>(); CapacityPricingValidator.validateAndNormalizeCustomPricing(maxCapacities(a, b), ImmutableList.of(b, a), asList(uc), violations); Assertions.assertThat(violations).isEmpty(); } @Test public void unavailable_capacity_is_removed_if_pricing_is_not_found__wrong_type() { Pricing a = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 0, 24); a.maxCapacity = 5; Pricing b = pricing(CAR, PARK_AND_RIDE, SATURDAY, 0, 24); b.maxCapacity = 10; List<UnavailableCapacity> unavailableCapacities = asList(new UnavailableCapacity(BICYCLE, PARK_AND_RIDE, 10)); List<Violation> violations = new ArrayList<>(); CapacityPricingValidator.validateAndNormalizeCustomPricing(maxCapacities(a, b), ImmutableList.of(a, b), unavailableCapacities, violations); assertThat(unavailableCapacities).isEmpty(); } @Test public void unavailable_capacity_is_removed_if_pricing_is_not_found__wrong_usage() { Pricing a = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 0, 24); a.maxCapacity = 5; Pricing b = pricing(CAR, PARK_AND_RIDE, SATURDAY, 0, 24); b.maxCapacity = 10; List<UnavailableCapacity> unavailableCapacities = asList(new UnavailableCapacity(CAR, COMMERCIAL, 10)); List<Violation> violations = new ArrayList<>(); CapacityPricingValidator.validateAndNormalizeCustomPricing(maxCapacities(a, b), ImmutableList.of(a, b), unavailableCapacities, violations); assertThat(unavailableCapacities).isEmpty(); } @Test public void unavailable_capacity_overflow() { Pricing a = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 0, 24); a.maxCapacity = 5; Pricing b = pricing(CAR, PARK_AND_RIDE, SATURDAY, 0, 24); b.maxCapacity = 10; UnavailableCapacity uc = new UnavailableCapacity(CAR, PARK_AND_RIDE, 11); List<Violation> violations = new ArrayList<>(); CapacityPricingValidator.validateAndNormalizeCustomPricing(maxCapacities(a, b), ImmutableList.of(a, b), asList(uc), violations); Assertions.assertThat(violations).extracting("type", "path").containsOnly(tuple("UnavailableCapacityOverflow", "unavailableCapacities[0].capacity")); } @Test public void duplicate_unavailable_capacity() { Pricing a = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 0, 24); a.maxCapacity = 5; Pricing b = pricing(CAR, PARK_AND_RIDE, SATURDAY, 0, 24); b.maxCapacity = 10; UnavailableCapacity uc = new UnavailableCapacity(CAR, PARK_AND_RIDE, 5); List<Violation> violations = new ArrayList<>(); CapacityPricingValidator.validateAndNormalizeCustomPricing(maxCapacities(a, b), ImmutableList.of(a, b), asList(uc, uc), violations); Assertions.assertThat(violations).extracting("type", "path").containsOnly(tuple("DuplicateUnavailableCapacity", "unavailableCapacities[1]")); } @Test public void consecutive_intervals_for_same_usage_capacity_day() { Pricing a = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 8, 9); Pricing b = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 9, 10); List<Violation> violations = new ArrayList<>(); CapacityPricingValidator.validateAndNormalizeCustomPricing(maxCapacities(a, b), ImmutableList.of(a, b), asList(), violations); Assertions.assertThat(violations).isEmpty(); } @Test public void built_capacity_must_exist_for_pricing_capacity() { Pricing a = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 7, 17); List<Violation> violations = new ArrayList<>(); CapacityPricingValidator.validateAndNormalizeCustomPricing(Maps.newHashMap(), ImmutableList.of(a), asList(), violations); Assertions.assertThat(violations).extracting("type", "path").containsOnly(tuple("BuiltCapacityNotFound", "pricing[0].capacityType")); } @Test public void single_pricing_capacity_cannot_be_larger_than_build_capacity() { Pricing a = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 7, 17); Map<CapacityType, Integer> builtCapacity = Maps.newHashMap(); builtCapacity.put(CAR, a.maxCapacity-1); List<Violation> violations = new ArrayList<>(); CapacityPricingValidator.validateAndNormalizeCustomPricing(builtCapacity, ImmutableList.of(a), asList(), violations); Assertions.assertThat(violations).extracting("type", "path").containsOnly(tuple("PricingCapacityOverflow", "pricing[0].maxCapacity")); } @Test public void all_violations_are_reported() { Pricing a = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 7, 17); Pricing b = pricing(CAR, PARK_AND_RIDE, BUSINESS_DAY, 8, 18); Pricing c = pricing(CAR, COMMERCIAL, BUSINESS_DAY, 7, 17); Pricing d = pricing(CAR, COMMERCIAL, BUSINESS_DAY, 8, 18); Pricing e = pricing(MOTORCYCLE, COMMERCIAL, BUSINESS_DAY, 8, 18); UnavailableCapacity uc1 = new UnavailableCapacity(ELECTRIC_CAR, PARK_AND_RIDE, 0); UnavailableCapacity uc2 = new UnavailableCapacity(CAR, PARK_AND_RIDE, 43); UnavailableCapacity uc3 = new UnavailableCapacity(CAR, PARK_AND_RIDE, 0); Map<CapacityType, Integer> builtCapacity = Maps.newHashMap(); builtCapacity.put(a.capacityType, a.maxCapacity-1); builtCapacity.put(c.capacityType, c.maxCapacity-1); List<Violation> violations = new ArrayList<>(); List<UnavailableCapacity> unavailableCapacities = asList(uc1, uc2, uc3); CapacityPricingValidator.validateAndNormalizeCustomPricing(builtCapacity, ImmutableList.of(a, b, c, d, e), unavailableCapacities, violations); assertThat(violations).extracting("type", "path").containsOnly( tuple("PricingOverlap", "pricing[1].time"), tuple("PricingOverlap", "pricing[3].time"), tuple("PricingCapacityOverflow", "pricing[0].maxCapacity"), tuple("PricingCapacityOverflow", "pricing[1].maxCapacity"), tuple("PricingCapacityOverflow", "pricing[2].maxCapacity"), tuple("PricingCapacityOverflow", "pricing[3].maxCapacity"), tuple("BuiltCapacityNotFound", "pricing[4].capacityType"), tuple("UnavailableCapacityOverflow", "unavailableCapacities[1].capacity"), tuple("DuplicateUnavailableCapacity", "unavailableCapacities[2]") ); assertThat(unavailableCapacities).isEqualTo(asList(uc2, uc3)); } private static <T> List<T> asList(T... values) { List<T> list = new ArrayList<>(values.length); list.addAll(Arrays.asList(values)); return list; } private static Map<CapacityType, Integer> maxCapacities(Pricing... pricing) { Map<CapacityType, Integer> result = Maps.newHashMap(); for (Pricing p : pricing) { Integer max = result.get(p.capacityType); if (max == null || p.maxCapacity > max) { result.put(p.capacityType, p.maxCapacity); } } return result; } private static Pricing pricing(CapacityType capacity, Usage usage, DayType day, int from, int until) { return new Pricing(capacity, usage, 42, day, String.valueOf(from), String.valueOf(until), "42"); } }