// Copyright © 2015 HSL <https://www.hsl.fi>
// This program is dual-licensed under the EUPL v1.2 and AGPLv3 licenses.
package fi.hsl.parkandride.back;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import fi.hsl.parkandride.core.back.ContactRepository;
import fi.hsl.parkandride.core.back.FacilityRepository;
import fi.hsl.parkandride.core.back.OperatorRepository;
import fi.hsl.parkandride.core.domain.*;
import fi.hsl.parkandride.core.service.ValidationException;
import org.geolatte.geom.Point;
import org.geolatte.geom.Polygon;
import org.junit.Before;
import org.junit.Test;
import javax.inject.Inject;
import java.util.*;
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.DayType.*;
import static fi.hsl.parkandride.core.domain.FacilityStatus.EXCEPTIONAL_SITUATION;
import static fi.hsl.parkandride.core.domain.FacilityStatus.IN_OPERATION;
import static fi.hsl.parkandride.core.domain.PricingMethod.CUSTOM;
import static fi.hsl.parkandride.core.domain.PricingMethod.PARK_AND_RIDE_247_FREE;
import static fi.hsl.parkandride.core.domain.Service.*;
import static fi.hsl.parkandride.core.domain.Sort.Dir.ASC;
import static fi.hsl.parkandride.core.domain.Sort.Dir.DESC;
import static fi.hsl.parkandride.core.domain.Usage.PARK_AND_RIDE;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class FacilityDaoTest extends AbstractDaoTest {
public static final MultilingualString NAME = new MultilingualString("Facility");
public static final MultilingualString STATUS_DESCRIPTION = new MultilingualString("Status description");
public static final MultilingualString OPENING_HOURS_INFO = new MultilingualString("Opening Hours");
public static final MultilingualUrl OPENING_HOURS_URL = new MultilingualUrl("http://www.hsl.fi");
private static final Point PORT_LOCATION1 = (Point) Spatial.fromWkt("POINT(25.010822 60.25054)");
private static final Point PORT_LOCATION2 = (Point) Spatial.fromWkt("POINT(25.012479 60.250337)");
private static final Polygon LOCATION = (Polygon) Spatial.fromWkt("POLYGON((" +
"25.010822 60.25054, " +
"25.010822 60.250023, " +
"25.012479 60.250337, " +
"25.011449 60.250885, " +
"25.010822 60.25054))");
public static final Polygon OVERLAPPING_AREA = (Polygon) Spatial.fromWkt("POLYGON((" +
"25.011942 60.251343, " +
"25.011717 60.250454, " +
"25.013487 60.250848, " +
"25.011942 60.251343))");
public static final Polygon NON_OVERLAPPING_AREA = (Polygon) Spatial.fromWkt("POLYGON((" +
"25.011942 60.251343, " +
"25.012211 60.250816, " +
"25.013487 60.250848, " +
"25.011942 60.251343))");
public static final SortedSet<String> ALIASES = ImmutableSortedSet.of("alias", "blias");
public static final List<Port> PORTS = ImmutableList.of(new Port(PORT_LOCATION1, true, false, true, false, "street", "00100", "city", "info"));
public static final NullSafeSortedSet<Service> SERVICES = new NullSafeSortedSet<>(asList(ELEVATOR, TOILETS, ACCESSIBLE_TOILETS));
public static final Pricing PRICING1 = new Pricing(CAR, PARK_AND_RIDE, 50, SATURDAY, "8", "18", "2 EUR/H");
public static final Pricing PRICING2 = new Pricing(CAR, PARK_AND_RIDE, 50, SUNDAY, "8", "18", "1 EUR/H");
public static final Map<CapacityType, Integer> BUILT_CAPACITY = ImmutableMap.of(
CAR, 50,
ELECTRIC_CAR, 2
);
public static final List<UnavailableCapacity> UNAVAILABLE_CAPACITIES = Arrays.asList(
new UnavailableCapacity(CAR, PARK_AND_RIDE, 1)
);
@Inject
ContactRepository contactDao;
@Inject
FacilityRepository facilityDao;
@Inject
OperatorRepository operatorDao;
private FacilityContacts dummyContacts;
private Long operatorId;
@Before
public void initialize() {
dummyContacts = new FacilityContacts(createDummyContact(), createDummyContact());
operatorId = createDummyOperator();
}
@Test
public void all_usages_and_capacities_are_materialized_and_projected() {
Facility facility = createFacility();
facility.pricingMethod = CUSTOM;
final Map<CapacityType, Integer> builtCapacity = new HashMap<>();
facility.pricing = new ArrayList<>();
CapacityType[] capacityTypes = CapacityType.values();
Usage[] usages = Usage.values();
for (int i = 0; i < capacityTypes.length || i < usages.length; i++) {
CapacityType type = capacityTypes[i % capacityTypes.length];
Usage usage = usages[i % usages.length];
builtCapacity.put(type, i + 1);
facility.pricing.add(new Pricing(type, usage, i + 1, BUSINESS_DAY, "0", "24", null));
}
facility.builtCapacity = builtCapacity;
final long id = facilityDao.insertFacility(facility);
facility = facilityDao.getFacility(id);
assertThat(facility.builtCapacity).isEqualTo(builtCapacity);
assertThat(facility.usages).isEqualTo(ImmutableSet.copyOf(Usage.values()));
FacilitySummary facilitySummary = facilityDao.summarizeFacilities(new FacilitySearch());
assertThat(facilitySummary.capacities).isEqualTo(builtCapacity);
}
@Test
public void normalize_facility_on_create() {
Facility facility = mock(Facility.class);
try {
facilityDao.insertFacility(facility);
} catch (RuntimeException e) {
}
verify(facility).normalize();
}
@Test
public void normalize_facility_on_update() {
Facility facility = mock(Facility.class);
try {
facilityDao.updateFacility(123l, facility, facility);
} catch (RuntimeException e) {
}
verify(facility).normalize();
}
@Test
public void create_read_update() {
Facility facility = createFacility();
// Insert
final long id = facilityDao.insertFacility(facility);
assertThat(id).isGreaterThan(0);
assertThat(facility.id).isNotEqualTo(id);
// Find by id
facility = facilityDao.getFacility(id);
assertDefault(facility);
assertThat(facility.contacts).isEqualTo(dummyContacts);
// Search
assertDefault(facilityDao.findFacilities(new PageableFacilitySearch()).get(0));
// Update
final MultilingualString newName = new MultilingualString("changed name");
final SortedSet<String> newAliases = ImmutableSortedSet.of("clias");
final List<Port> newPorts = ImmutableList.of(new Port(PORT_LOCATION2, true, true, true, true), new Port(PORT_LOCATION1, false, false, false, false));
final NullSafeSortedSet<Service> newServices = new NullSafeSortedSet<>(asList(LIGHTING));
facility.name = newName;
facility.status = IN_OPERATION;
facility.pricingMethod = PARK_AND_RIDE_247_FREE;
facility.statusDescription = null;
facility.aliases = newAliases;
facility.ports = newPorts;
facility.services = newServices;
facilityDao.updateFacility(id, facility);
facility = facilityDao.getFacility(id);
assertThat(facility.name).isEqualTo(newName);
assertThat(facility.status).isEqualTo(IN_OPERATION);
assertThat(facility.statusDescription).isNull();
assertThat(facility.aliases).isEqualTo(newAliases);
assertThat(ImmutableSet.copyOf(facility.ports)).isEqualTo(ImmutableSet.copyOf(newPorts));
assertThat(facility.services).isEqualTo(newServices);
assertThat(facility.pricing).isEqualTo(asList(
free24h(CAR, PARK_AND_RIDE, 50, BUSINESS_DAY),
free24h(CAR, PARK_AND_RIDE, 50, SATURDAY),
free24h(CAR, PARK_AND_RIDE, 50, SUNDAY),
free24h(ELECTRIC_CAR, PARK_AND_RIDE, 2, BUSINESS_DAY),
free24h(ELECTRIC_CAR, PARK_AND_RIDE, 2, SATURDAY),
free24h(ELECTRIC_CAR, PARK_AND_RIDE, 2, SUNDAY)
));
assertThat(facility.usages).isEqualTo(ImmutableSet.of(PARK_AND_RIDE));
assertThat(facility.unavailableCapacities).isEqualTo(asList(
new UnavailableCapacity(CAR, PARK_AND_RIDE, 1),
new UnavailableCapacity(ELECTRIC_CAR, PARK_AND_RIDE, 0)
));
// Remove aliases, capacities and ports
facility.aliases = new HashSet<>();
facility.ports = new ArrayList<>();
facility.services = new NullSafeSortedSet<>();
facility.contacts.service = null;
facilityDao.updateFacility(id, facility);
// Find by geometry
List<FacilityInfo> facilities = findByGeometry(OVERLAPPING_AREA);
assertThat(facilities).hasSize(1);
// Not found by geometry
assertThat(findByGeometry(NON_OVERLAPPING_AREA)).isEmpty();
}
private Pricing free24h(CapacityType type, Usage usage, int maxCapacity, DayType dayType) {
return new Pricing(type, usage, maxCapacity, dayType, "00", "24", null);
}
private Long createDummyContact() {
Contact contact = new Contact();
contact.name = new MultilingualString("TEST " + UUID.randomUUID());
contact.email = "test@example.com";
return contactDao.insertContact(contact);
}
private Long createDummyOperator() {
Operator operator = new Operator("SMOOTH");
return operatorDao.insertOperator(operator);
}
private void assertDefault(FacilityInfo facility) {
assertThat(facility).isNotNull();
assertThat(facility.location).isEqualTo(LOCATION);
assertThat(facility.operatorId).isEqualTo(operatorId);
assertThat(facility.status).isEqualTo(EXCEPTIONAL_SITUATION);
assertThat(facility.statusDescription).isEqualTo(STATUS_DESCRIPTION);
assertThat(facility.name).isEqualTo(NAME);
assertThat(facility.builtCapacity).isEqualTo(BUILT_CAPACITY);
assertThat(facility.usages).isEqualTo(ImmutableSet.of(PARK_AND_RIDE));
}
private void assertDefault(Facility facility) {
assertDefault((FacilityInfo) facility);
assertThat(facility.aliases).isEqualTo(ALIASES);
assertThat(facility.ports).isEqualTo(PORTS);
assertThat(facility.services).isEqualTo(SERVICES);
assertThat(facility.pricing).isEqualTo(asList(PRICING1, PRICING2));
assertThat(facility.unavailableCapacities).isEqualTo(UNAVAILABLE_CAPACITIES);
assertThat(facility.openingHours.byDayType).isEqualTo(ImmutableMap.of(
SATURDAY, new TimeDuration("8", "18"),
SUNDAY, new TimeDuration("8", "18")
));
assertThat(facility.openingHours.info).isEqualTo(OPENING_HOURS_INFO);
assertThat(facility.openingHours.url).isEqualTo(OPENING_HOURS_URL);
}
private List<FacilityInfo> findByGeometry(Polygon geometry) {
PageableFacilitySearch search = new PageableFacilitySearch();
search.setGeometry(geometry);
return facilityDao.findFacilities(search).results;
}
private Facility createFacility() {
return createFacility(operatorId, dummyContacts);
}
public static Facility createFacility(Long operatorId, FacilityContacts contacts) {
Facility facility = new Facility();
facility.id = 0L;
facility.name = NAME;
facility.location = LOCATION;
facility.operatorId = operatorId;
facility.status = EXCEPTIONAL_SITUATION;
facility.pricingMethod = PricingMethod.CUSTOM;
facility.statusDescription = STATUS_DESCRIPTION;
facility.aliases = ALIASES;
facility.ports = PORTS;
facility.services = SERVICES;
facility.contacts = contacts;
facility.builtCapacity = BUILT_CAPACITY;
// pricing in wrong order should be sorted on load
facility.pricing.add(PRICING2);
facility.pricing.add(PRICING1);
facility.unavailableCapacities = UNAVAILABLE_CAPACITIES;
facility.openingHours.info = OPENING_HOURS_INFO;
facility.openingHours.url = OPENING_HOURS_URL;
return facility;
}
@Test
public void sorting() {
Facility f1 = new Facility();
f1.name = new MultilingualString("a", "å", "C");
f1.status = IN_OPERATION;
f1.pricingMethod = PricingMethod.CUSTOM;
f1.location = LOCATION;
f1.contacts = dummyContacts;
f1.operatorId = operatorId;
f1.id = facilityDao.insertFacility(f1);
Facility f2 = new Facility();
f2.name = new MultilingualString("D", "Ä", "F");
f2.status = IN_OPERATION;
f2.pricingMethod = PricingMethod.CUSTOM;
f2.location = LOCATION;
f2.operatorId = operatorId;
f2.contacts = dummyContacts;
f2.id = facilityDao.insertFacility(f2);
// Default sort
PageableFacilitySearch search = new PageableFacilitySearch();
assertResultOrder(facilityDao.findFacilities(search), f1.id, f2.id);
// name.fi desc
search.sort = new Sort("name.fi", DESC);
assertResultOrder(facilityDao.findFacilities(search), f2.id, f1.id);
// name.sv desc
search.sort = new Sort("name.sv", DESC);
// NOTE: This doesn't work on mac/postgresql because it's fi-collation is broken
assertResultOrder(facilityDao.findFacilities(search), f2.id, f1.id);
// name.en asc
search.sort = new Sort("name.en", ASC);
assertResultOrder(facilityDao.findFacilities(search), f1.id, f2.id);
}
@Test(expected = ValidationException.class)
public void illegal_sort_by() {
PageableFacilitySearch search = new PageableFacilitySearch();
search.sort = new Sort("foobar");
facilityDao.findFacilities(search);
}
@Test
public void unique_name() {
Facility facility = createFacility();
facilityDao.insertFacility(facility);
verifyUniqueName(facility, "fi");
verifyUniqueName(facility, "sv");
verifyUniqueName(facility, "en");
}
private void verifyUniqueName(Facility facility, String lang) {
facility.name = new MultilingualString("something else");
try {
facility.name.asMap().put(lang, NAME.asMap().get(lang));
facilityDao.insertFacility(facility);
fail("should not allow duplicate names");
} catch (ValidationException e) {
assertThat(e.violations).hasSize(1);
assertThat(e.violations.get(0).path).isEqualTo("name." + lang);
}
}
private void assertResultOrder(SearchResults<FacilityInfo> results, long id1, long id2) {
assertThat(results.size()).isEqualTo(2);
assertThat(results.get(0).id).isEqualTo(id1);
assertThat(results.get(1).id).isEqualTo(id2);
}
@Test(expected = NotFoundException.class)
public void get_throws_an_exception_if_not_found() {
facilityDao.getFacility(0);
}
@Test(expected = NotFoundException.class)
public void update_throws_an_exception_if_not_found() {
facilityDao.updateFacility(0, createFacility());
}
}