Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package org.rutebanken.tiamat.ext.fintraffic.api;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.rutebanken.tiamat.ext.fintraffic.api.model.FintrafficReadApiSearchKey;
import org.rutebanken.tiamat.model.StopPlace;
import org.rutebanken.tiamat.model.VehicleModeEnumeration;
import org.rutebanken.tiamat.repository.StopPlaceRepository;
import org.rutebanken.tiamat.repository.TopographicPlaceRepository;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class FintrafficSearchKeyServiceTest {

private FintrafficSearchKeyService service;

@BeforeEach
void setUp() {
TopographicPlaceRepository topographicPlaceRepositoryMock = mock(TopographicPlaceRepository.class);
StopPlaceRepository stopPlaceRepositoryMock = mock(StopPlaceRepository.class);
when(topographicPlaceRepositoryMock.findTopographicPlace(any())).thenReturn(List.of());

service = new FintrafficSearchKeyService(
new ObjectMapper(),
topographicPlaceRepositoryMock,
stopPlaceRepositoryMock
);
}

private StopPlace createStopPlace(String netexId, VehicleModeEnumeration transportMode) {
return createStopPlace(netexId, transportMode, 1L);
}

private StopPlace createStopPlace(String netexId, VehicleModeEnumeration transportMode, long version) {
StopPlace stopPlace = new StopPlace();
stopPlace.setNetexId(netexId);
stopPlace.setTransportMode(transportMode);
stopPlace.setVersion(version);
return stopPlace;
}

@Test
void generateSearchKeyJSON_regularStopPlace_containsTransportMode() {
StopPlace stopPlace = createStopPlace("FSR:StopPlace:1", VehicleModeEnumeration.BUS);

String json = service.generateSearchKeyJSON(stopPlace);

assertThat(json).contains("bus");
}

@Test
void generateSearchKeyJSON_regularStopPlace_noTransportMode_emptyArray() {
StopPlace stopPlace = createStopPlace("FSR:StopPlace:2", null);

String json = service.generateSearchKeyJSON(stopPlace);

// transportModes should be empty
assertThat(json).contains("\"transportModes\":[]");
}

@Test
void generateSearchKeyJSON_parentStopPlace_collectsChildTransportModes() {
StopPlace parentStopPlace = createStopPlace("FSR:StopPlace:10", null);
parentStopPlace.setParentStopPlace(true);

StopPlace child1 = createStopPlace("FSR:StopPlace:11", VehicleModeEnumeration.BUS, 1L);
StopPlace child2 = createStopPlace("FSR:StopPlace:12", VehicleModeEnumeration.TRAM, 2L);
HashSet<StopPlace> children = new HashSet<>();
children.add(child1);
children.add(child2);
parentStopPlace.setChildren(children);

String json = service.generateSearchKeyJSON(parentStopPlace);

assertThat(json).contains("bus");
assertThat(json).contains("tram");
}

@Test
void generateSearchKeyJSON_parentStopPlace_deduplicatesChildTransportModes() {
StopPlace parentStopPlace = createStopPlace("FSR:StopPlace:20", null);
parentStopPlace.setParentStopPlace(true);

StopPlace child1 = createStopPlace("FSR:StopPlace:21", VehicleModeEnumeration.BUS, 1L);
StopPlace child2 = createStopPlace("FSR:StopPlace:22", VehicleModeEnumeration.BUS, 2L);
HashSet<StopPlace> children = new HashSet<>();
children.add(child1);
children.add(child2);
parentStopPlace.setChildren(children);

String json = service.generateSearchKeyJSON(parentStopPlace);

FintrafficReadApiSearchKey searchKey = parseSearchKey(json);
long busCount = Arrays.stream(searchKey.transportModes()).filter("bus"::equals).count();
assertThat(busCount).isEqualTo(1);
}

@Test
void generateSearchKeyJSON_parentStopPlace_mergesParentAndChildTransportModes() {
StopPlace parentStopPlace = createStopPlace("FSR:StopPlace:30", VehicleModeEnumeration.WATER, 1L);
parentStopPlace.setParentStopPlace(true);

StopPlace child = createStopPlace("FSR:StopPlace:31", VehicleModeEnumeration.BUS, 2L);
HashSet<StopPlace> children = new HashSet<>();
children.add(child);
parentStopPlace.setChildren(children);

String json = service.generateSearchKeyJSON(parentStopPlace);

assertThat(json).contains("water");
assertThat(json).contains("bus");
}

@Test
void generateSearchKeyJSON_parentStopPlace_childWithNullTransportMode_ignored() {
StopPlace parentStopPlace = createStopPlace("FSR:StopPlace:40", null);
parentStopPlace.setParentStopPlace(true);

StopPlace childWithMode = createStopPlace("FSR:StopPlace:41", VehicleModeEnumeration.BUS, 1L);
StopPlace childNoMode = createStopPlace("FSR:StopPlace:42", null, 2L);
HashSet<StopPlace> children = new HashSet<>();
children.add(childWithMode);
children.add(childNoMode);
parentStopPlace.setChildren(children);

String json = service.generateSearchKeyJSON(parentStopPlace);

assertThat(json).contains("bus");
FintrafficReadApiSearchKey searchKey = parseSearchKey(json);
assertThat(searchKey.transportModes()).containsExactly("bus");
}

@Test
void generateSearchKeyJSON_parentStopPlace_noChildren_emptyTransportModes() {
StopPlace parentStopPlace = createStopPlace("FSR:StopPlace:50", null);
parentStopPlace.setParentStopPlace(true);
parentStopPlace.setChildren(Set.of());

String json = service.generateSearchKeyJSON(parentStopPlace);

assertThat(json).contains("\"transportModes\":[]");
}

private FintrafficReadApiSearchKey parseSearchKey(String json) {
try {
return new ObjectMapper().readValue(json, FintrafficReadApiSearchKey.class);
} catch (Exception e) {
throw new RuntimeException("Failed to parse search key JSON: " + json, e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.rutebanken.tiamat.exporter.params.TopographicPlaceSearch;
import org.rutebanken.tiamat.model.GroupOfStopPlaces;
import org.rutebanken.tiamat.model.Parking;
import org.rutebanken.tiamat.model.Quay;
import org.rutebanken.tiamat.model.StopPlace;
Expand All @@ -24,10 +25,8 @@

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.AdditionalMatchers.not;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.matches;
import static org.mockito.Mockito.mock;
Expand Down Expand Up @@ -93,10 +92,14 @@ class FintrafficAuthorizationServiceTest {
}

private static FintrafficAuthorizationService getAuthorizationService() {
return getAuthorizationService(true, true);
return getAuthorizationService(true, true, false);
}

private static FintrafficAuthorizationService getAuthorizationService(boolean codespaceEnabled, boolean municipalityEnabled) {
private static FintrafficAuthorizationService getAuthorizationService(
boolean codespaceEnabled,
boolean municipalityEnabled,
boolean multiModalStopPlaceSupportDisabled
) {
TopographicPlaceRepository topographicPlaceRepositoryMock = mock(TopographicPlaceRepository.class);
TrivoreAuthorizations trivoreAuthorizationsMock = mock(TrivoreAuthorizations.class);

Expand All @@ -117,12 +120,14 @@ private static FintrafficAuthorizationService getAuthorizationService(boolean co
when(trivoreAuthorizationsMock.hasAccess(matches("Parking"), matches("\\{all\\}"), eq(TrivorePermission.MANAGE), anyBoolean())).thenReturn(true);
when(trivoreAuthorizationsMock.hasAccess(matches("Quay"), matches("\\{all\\}"), eq(TrivorePermission.MANAGE), anyBoolean())).thenReturn(true);
when(trivoreAuthorizationsMock.hasAccess(matches("StopPlace"), matches("RAIL"), eq(TrivorePermission.MANAGE), anyBoolean())).thenReturn(false);
when(trivoreAuthorizationsMock.hasAccess(matches("GroupOfStopPlaces"), matches("\\{all\\}"), eq(TrivorePermission.MANAGE), anyBoolean())).thenReturn(true);

return new FintrafficAuthorizationService(
trivoreAuthorizationsMock,
topographicPlaceRepositoryMock,
codespaceEnabled,
municipalityEnabled
municipalityEnabled,
multiModalStopPlaceSupportDisabled
);
}

Expand Down Expand Up @@ -160,6 +165,69 @@ public void testCanEditEntityParking() {
}


@Test
public void testCanEditParentStopPlaceAllChildTransportModesAllowed() {
// Parent stop place with children that all have allowed transport modes (BUS)
StopPlace parentStopPlace = getStopPlace("FSR:StopPlace:100", null, getPoint(new Coordinate(0.3, 0.3)));
parentStopPlace.setParentStopPlace(true);
StopPlace child1 = getStopPlace("FSR:StopPlace:101", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.3, 0.3)));
StopPlace child2 = getStopPlace("FSR:StopPlace:102", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.4, 0.4)));
parentStopPlace.setChildren(Set.of(child1, child2));

FintrafficAuthorizationService authorizationService = getAuthorizationService();
assertThat(authorizationService.canEditEntity(parentStopPlace), equalTo(true));
}

@Test
public void testCanEditParentStopPlaceChildTransportModeNotAllowed() {
// Parent stop place with a child that has a forbidden transport mode (RAIL)
StopPlace parentStopPlace = getStopPlace("FSR:StopPlace:200", null, getPoint(new Coordinate(0.3, 0.3)));
parentStopPlace.setParentStopPlace(true);
StopPlace child = getStopPlace("FSR:StopPlace:201", VehicleModeEnumeration.RAIL, getPoint(new Coordinate(0.3, 0.3)));
parentStopPlace.setChildren(Set.of(child));

FintrafficAuthorizationService authorizationService = getAuthorizationService();
assertThat(authorizationService.canEditEntity(parentStopPlace), equalTo(false));
}

@Test
public void testCanNotEditParentStopPlaceMultiModalSupportDisabled() {
// Parent stop place with children that all have allowed transport modes (BUS)
// Multi-modal stop place support is disabled, so parent stop place should not be editable even if child transport mode is allowed
StopPlace parentStopPlace = getStopPlace("FSR:StopPlace:100", null, getPoint(new Coordinate(0.3, 0.3)));
parentStopPlace.setParentStopPlace(true);
StopPlace child1 = getStopPlace("FSR:StopPlace:101", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.3, 0.3)));
StopPlace child2 = getStopPlace("FSR:StopPlace:102", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.4, 0.4)));
parentStopPlace.setChildren(Set.of(child1, child2));

FintrafficAuthorizationService authorizationService = getAuthorizationService(true, true, true);
assertThat(authorizationService.canEditEntity(parentStopPlace), equalTo(false));
}

@Test
public void testCanEditParentStopPlaceWithMixedChildTransportModes() {
// Parent stop place with children of mixed transport modes: one allowed (BUS), one denied (RAIL)
StopPlace parentStopPlace = getStopPlace("FSR:StopPlace:300", null, getPoint(new Coordinate(0.3, 0.3)));
parentStopPlace.setParentStopPlace(true);
StopPlace childBus = getStopPlace("FSR:StopPlace:301", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.3, 0.3)));
StopPlace childRail = getStopPlace("FSR:StopPlace:302", VehicleModeEnumeration.RAIL, getPoint(new Coordinate(0.4, 0.4)));
parentStopPlace.setChildren(Set.of(childBus, childRail));

FintrafficAuthorizationService authorizationService = getAuthorizationService();
assertThat(authorizationService.canEditEntity(parentStopPlace), equalTo(false));
}

@Test
public void testCanEditParentStopPlaceWithNoChildren() {
// Parent stop place with no children
StopPlace parentStopPlace = getStopPlace("FSR:StopPlace:400", null, getPoint(new Coordinate(0.3, 0.3)));
parentStopPlace.setParentStopPlace(true);
parentStopPlace.setChildren(Set.of());

FintrafficAuthorizationService authorizationService = getAuthorizationService();
assertThat(authorizationService.canEditEntity(parentStopPlace), equalTo(false));
}

@Test
public void testCanEditStopPlaceWithNestedEntities() {
StopPlace stopPlaceWithQuayAndNestedStopPlace = getStopPlace("FSR:StopPlace:1", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.3, 0.3)));
Expand Down Expand Up @@ -212,7 +280,7 @@ public void testCanEditEntityByEitherCodespaceOrMunicipality() {

@Test
public void testCodespaceOnlyMode() {
FintrafficAuthorizationService authorizationService = getAuthorizationService(true, false);
FintrafficAuthorizationService authorizationService = getAuthorizationService(true, false, false);
// Point inside region (codespace) — allowed
assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(0.5, 0.5))), equalTo(true));
// Point inside municipality area but municipality auth disabled — denied
Expand All @@ -221,7 +289,7 @@ public void testCodespaceOnlyMode() {

@Test
public void testMunicipalityOnlyMode() {
FintrafficAuthorizationService authorizationService = getAuthorizationService(false, true);
FintrafficAuthorizationService authorizationService = getAuthorizationService(false, true, false);
// Point inside region but codespace auth disabled — denied
assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(0.5, 0.5))), equalTo(false));
// Point inside municipality area — allowed
Expand All @@ -230,9 +298,18 @@ public void testMunicipalityOnlyMode() {

@Test
public void testBothAuthorizationMethodsDisabled() {
FintrafficAuthorizationService authorizationService = getAuthorizationService(false, false);
FintrafficAuthorizationService authorizationService = getAuthorizationService(false, false, false);
// Both disabled — all geographic edits denied
assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(0.5, 0.5))), equalTo(false));
assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(2.5, 2.5))), equalTo(false));
}

@Test
public void testGroupOfStopPlacesEditable() {
GroupOfStopPlaces groupOfStopPlaces = new GroupOfStopPlaces();
groupOfStopPlaces.setNetexId("FSR:GroupOfStopPlaces:1");

FintrafficAuthorizationService authorizationService = getAuthorizationService();
assertThat(authorizationService.canEditEntity(groupOfStopPlaces), equalTo(true));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.rutebanken.tiamat.model.StopPlace;
import org.rutebanken.tiamat.model.TopographicPlace;
import org.rutebanken.tiamat.model.TopographicPlaceTypeEnumeration;
import org.rutebanken.tiamat.model.VehicleModeEnumeration;
import org.rutebanken.tiamat.repository.StopPlaceRepository;
import org.rutebanken.tiamat.repository.TopographicPlaceRepository;
import org.slf4j.Logger;
Expand Down Expand Up @@ -152,6 +153,19 @@ private FintrafficReadApiSearchKey extractSearchKey(org.rutebanken.tiamat.model.
String[] transportModes = stopPlace.getTransportMode() != null
? new String[]{stopPlace.getTransportMode().value()}
: new String[]{};

// If StopPlace is parentStopPlace then get transportModes from child stopPlaces as well
if (stopPlace.isParentStopPlace() && stopPlace.getChildren() != null) {
String[] childTransportModes = stopPlace.getChildren().stream().map(org.rutebanken.tiamat.model.StopPlace::getTransportMode)
.filter(Objects::nonNull)
.map(VehicleModeEnumeration::value)
.toArray(String[]::new);

transportModes = Stream.concat(Stream.of(transportModes), Stream.of(childTransportModes))
.distinct()
.toArray(String[]::new);
}

Optional<Point> stopPlaceCentroid = Optional.ofNullable(stopPlace.getCentroid());
Optional<String[]> areaCodes = stopPlaceCentroid.map(this::getAdministrativeZonesForPoint);

Expand Down
Loading
Loading