Skip to content

Commit 25b86cf

Browse files
authored
Merge pull request #378 from tmfg/feature/ext-multimodal-authorization
ext / Update multi modal authorization model for ext/Fintraffic
2 parents 5f9ea71 + 51a1d1b commit 25b86cf

5 files changed

Lines changed: 311 additions & 13 deletions

File tree

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package org.rutebanken.tiamat.ext.fintraffic.api;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import org.junit.jupiter.api.BeforeEach;
5+
import org.junit.jupiter.api.Test;
6+
import org.rutebanken.tiamat.ext.fintraffic.api.model.FintrafficReadApiSearchKey;
7+
import org.rutebanken.tiamat.model.StopPlace;
8+
import org.rutebanken.tiamat.model.VehicleModeEnumeration;
9+
import org.rutebanken.tiamat.repository.StopPlaceRepository;
10+
import org.rutebanken.tiamat.repository.TopographicPlaceRepository;
11+
12+
import java.util.Arrays;
13+
import java.util.HashSet;
14+
import java.util.List;
15+
import java.util.Set;
16+
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
import static org.mockito.ArgumentMatchers.any;
19+
import static org.mockito.Mockito.mock;
20+
import static org.mockito.Mockito.when;
21+
22+
class FintrafficSearchKeyServiceTest {
23+
24+
private FintrafficSearchKeyService service;
25+
26+
@BeforeEach
27+
void setUp() {
28+
TopographicPlaceRepository topographicPlaceRepositoryMock = mock(TopographicPlaceRepository.class);
29+
StopPlaceRepository stopPlaceRepositoryMock = mock(StopPlaceRepository.class);
30+
when(topographicPlaceRepositoryMock.findTopographicPlace(any())).thenReturn(List.of());
31+
32+
service = new FintrafficSearchKeyService(
33+
new ObjectMapper(),
34+
topographicPlaceRepositoryMock,
35+
stopPlaceRepositoryMock
36+
);
37+
}
38+
39+
private StopPlace createStopPlace(String netexId, VehicleModeEnumeration transportMode) {
40+
return createStopPlace(netexId, transportMode, 1L);
41+
}
42+
43+
private StopPlace createStopPlace(String netexId, VehicleModeEnumeration transportMode, long version) {
44+
StopPlace stopPlace = new StopPlace();
45+
stopPlace.setNetexId(netexId);
46+
stopPlace.setTransportMode(transportMode);
47+
stopPlace.setVersion(version);
48+
return stopPlace;
49+
}
50+
51+
@Test
52+
void generateSearchKeyJSON_regularStopPlace_containsTransportMode() {
53+
StopPlace stopPlace = createStopPlace("FSR:StopPlace:1", VehicleModeEnumeration.BUS);
54+
55+
String json = service.generateSearchKeyJSON(stopPlace);
56+
57+
assertThat(json).contains("bus");
58+
}
59+
60+
@Test
61+
void generateSearchKeyJSON_regularStopPlace_noTransportMode_emptyArray() {
62+
StopPlace stopPlace = createStopPlace("FSR:StopPlace:2", null);
63+
64+
String json = service.generateSearchKeyJSON(stopPlace);
65+
66+
// transportModes should be empty
67+
assertThat(json).contains("\"transportModes\":[]");
68+
}
69+
70+
@Test
71+
void generateSearchKeyJSON_parentStopPlace_collectsChildTransportModes() {
72+
StopPlace parentStopPlace = createStopPlace("FSR:StopPlace:10", null);
73+
parentStopPlace.setParentStopPlace(true);
74+
75+
StopPlace child1 = createStopPlace("FSR:StopPlace:11", VehicleModeEnumeration.BUS, 1L);
76+
StopPlace child2 = createStopPlace("FSR:StopPlace:12", VehicleModeEnumeration.TRAM, 2L);
77+
HashSet<StopPlace> children = new HashSet<>();
78+
children.add(child1);
79+
children.add(child2);
80+
parentStopPlace.setChildren(children);
81+
82+
String json = service.generateSearchKeyJSON(parentStopPlace);
83+
84+
assertThat(json).contains("bus");
85+
assertThat(json).contains("tram");
86+
}
87+
88+
@Test
89+
void generateSearchKeyJSON_parentStopPlace_deduplicatesChildTransportModes() {
90+
StopPlace parentStopPlace = createStopPlace("FSR:StopPlace:20", null);
91+
parentStopPlace.setParentStopPlace(true);
92+
93+
StopPlace child1 = createStopPlace("FSR:StopPlace:21", VehicleModeEnumeration.BUS, 1L);
94+
StopPlace child2 = createStopPlace("FSR:StopPlace:22", VehicleModeEnumeration.BUS, 2L);
95+
HashSet<StopPlace> children = new HashSet<>();
96+
children.add(child1);
97+
children.add(child2);
98+
parentStopPlace.setChildren(children);
99+
100+
String json = service.generateSearchKeyJSON(parentStopPlace);
101+
102+
FintrafficReadApiSearchKey searchKey = parseSearchKey(json);
103+
long busCount = Arrays.stream(searchKey.transportModes()).filter("bus"::equals).count();
104+
assertThat(busCount).isEqualTo(1);
105+
}
106+
107+
@Test
108+
void generateSearchKeyJSON_parentStopPlace_mergesParentAndChildTransportModes() {
109+
StopPlace parentStopPlace = createStopPlace("FSR:StopPlace:30", VehicleModeEnumeration.WATER, 1L);
110+
parentStopPlace.setParentStopPlace(true);
111+
112+
StopPlace child = createStopPlace("FSR:StopPlace:31", VehicleModeEnumeration.BUS, 2L);
113+
HashSet<StopPlace> children = new HashSet<>();
114+
children.add(child);
115+
parentStopPlace.setChildren(children);
116+
117+
String json = service.generateSearchKeyJSON(parentStopPlace);
118+
119+
assertThat(json).contains("water");
120+
assertThat(json).contains("bus");
121+
}
122+
123+
@Test
124+
void generateSearchKeyJSON_parentStopPlace_childWithNullTransportMode_ignored() {
125+
StopPlace parentStopPlace = createStopPlace("FSR:StopPlace:40", null);
126+
parentStopPlace.setParentStopPlace(true);
127+
128+
StopPlace childWithMode = createStopPlace("FSR:StopPlace:41", VehicleModeEnumeration.BUS, 1L);
129+
StopPlace childNoMode = createStopPlace("FSR:StopPlace:42", null, 2L);
130+
HashSet<StopPlace> children = new HashSet<>();
131+
children.add(childWithMode);
132+
children.add(childNoMode);
133+
parentStopPlace.setChildren(children);
134+
135+
String json = service.generateSearchKeyJSON(parentStopPlace);
136+
137+
assertThat(json).contains("bus");
138+
FintrafficReadApiSearchKey searchKey = parseSearchKey(json);
139+
assertThat(searchKey.transportModes()).containsExactly("bus");
140+
}
141+
142+
@Test
143+
void generateSearchKeyJSON_parentStopPlace_noChildren_emptyTransportModes() {
144+
StopPlace parentStopPlace = createStopPlace("FSR:StopPlace:50", null);
145+
parentStopPlace.setParentStopPlace(true);
146+
parentStopPlace.setChildren(Set.of());
147+
148+
String json = service.generateSearchKeyJSON(parentStopPlace);
149+
150+
assertThat(json).contains("\"transportModes\":[]");
151+
}
152+
153+
private FintrafficReadApiSearchKey parseSearchKey(String json) {
154+
try {
155+
return new ObjectMapper().readValue(json, FintrafficReadApiSearchKey.class);
156+
} catch (Exception e) {
157+
throw new RuntimeException("Failed to parse search key JSON: " + json, e);
158+
}
159+
}
160+
}

src/ext-test/java/org/rutebanken/tiamat/ext/fintraffic/auth/FintrafficAuthorizationServiceTest.java

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.locationtech.jts.geom.Point;
99
import org.locationtech.jts.geom.Polygon;
1010
import org.rutebanken.tiamat.exporter.params.TopographicPlaceSearch;
11+
import org.rutebanken.tiamat.model.GroupOfStopPlaces;
1112
import org.rutebanken.tiamat.model.Parking;
1213
import org.rutebanken.tiamat.model.Quay;
1314
import org.rutebanken.tiamat.model.StopPlace;
@@ -24,10 +25,8 @@
2425

2526
import static org.hamcrest.MatcherAssert.assertThat;
2627
import static org.hamcrest.Matchers.equalTo;
27-
import static org.mockito.AdditionalMatchers.not;
2828
import static org.mockito.ArgumentMatchers.any;
2929
import static org.mockito.ArgumentMatchers.anyBoolean;
30-
import static org.mockito.ArgumentMatchers.anyString;
3130
import static org.mockito.ArgumentMatchers.eq;
3231
import static org.mockito.ArgumentMatchers.matches;
3332
import static org.mockito.Mockito.mock;
@@ -93,10 +92,14 @@ class FintrafficAuthorizationServiceTest {
9392
}
9493

9594
private static FintrafficAuthorizationService getAuthorizationService() {
96-
return getAuthorizationService(true, true);
95+
return getAuthorizationService(true, true, false);
9796
}
9897

99-
private static FintrafficAuthorizationService getAuthorizationService(boolean codespaceEnabled, boolean municipalityEnabled) {
98+
private static FintrafficAuthorizationService getAuthorizationService(
99+
boolean codespaceEnabled,
100+
boolean municipalityEnabled,
101+
boolean multiModalStopPlaceSupportDisabled
102+
) {
100103
TopographicPlaceRepository topographicPlaceRepositoryMock = mock(TopographicPlaceRepository.class);
101104
TrivoreAuthorizations trivoreAuthorizationsMock = mock(TrivoreAuthorizations.class);
102105

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

121125
return new FintrafficAuthorizationService(
122126
trivoreAuthorizationsMock,
123127
topographicPlaceRepositoryMock,
124128
codespaceEnabled,
125-
municipalityEnabled
129+
municipalityEnabled,
130+
multiModalStopPlaceSupportDisabled
126131
);
127132
}
128133

@@ -160,6 +165,69 @@ public void testCanEditEntityParking() {
160165
}
161166

162167

168+
@Test
169+
public void testCanEditParentStopPlaceAllChildTransportModesAllowed() {
170+
// Parent stop place with children that all have allowed transport modes (BUS)
171+
StopPlace parentStopPlace = getStopPlace("FSR:StopPlace:100", null, getPoint(new Coordinate(0.3, 0.3)));
172+
parentStopPlace.setParentStopPlace(true);
173+
StopPlace child1 = getStopPlace("FSR:StopPlace:101", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.3, 0.3)));
174+
StopPlace child2 = getStopPlace("FSR:StopPlace:102", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.4, 0.4)));
175+
parentStopPlace.setChildren(Set.of(child1, child2));
176+
177+
FintrafficAuthorizationService authorizationService = getAuthorizationService();
178+
assertThat(authorizationService.canEditEntity(parentStopPlace), equalTo(true));
179+
}
180+
181+
@Test
182+
public void testCanEditParentStopPlaceChildTransportModeNotAllowed() {
183+
// Parent stop place with a child that has a forbidden transport mode (RAIL)
184+
StopPlace parentStopPlace = getStopPlace("FSR:StopPlace:200", null, getPoint(new Coordinate(0.3, 0.3)));
185+
parentStopPlace.setParentStopPlace(true);
186+
StopPlace child = getStopPlace("FSR:StopPlace:201", VehicleModeEnumeration.RAIL, getPoint(new Coordinate(0.3, 0.3)));
187+
parentStopPlace.setChildren(Set.of(child));
188+
189+
FintrafficAuthorizationService authorizationService = getAuthorizationService();
190+
assertThat(authorizationService.canEditEntity(parentStopPlace), equalTo(false));
191+
}
192+
193+
@Test
194+
public void testCanNotEditParentStopPlaceMultiModalSupportDisabled() {
195+
// Parent stop place with children that all have allowed transport modes (BUS)
196+
// Multi-modal stop place support is disabled, so parent stop place should not be editable even if child transport mode is allowed
197+
StopPlace parentStopPlace = getStopPlace("FSR:StopPlace:100", null, getPoint(new Coordinate(0.3, 0.3)));
198+
parentStopPlace.setParentStopPlace(true);
199+
StopPlace child1 = getStopPlace("FSR:StopPlace:101", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.3, 0.3)));
200+
StopPlace child2 = getStopPlace("FSR:StopPlace:102", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.4, 0.4)));
201+
parentStopPlace.setChildren(Set.of(child1, child2));
202+
203+
FintrafficAuthorizationService authorizationService = getAuthorizationService(true, true, true);
204+
assertThat(authorizationService.canEditEntity(parentStopPlace), equalTo(false));
205+
}
206+
207+
@Test
208+
public void testCanEditParentStopPlaceWithMixedChildTransportModes() {
209+
// Parent stop place with children of mixed transport modes: one allowed (BUS), one denied (RAIL)
210+
StopPlace parentStopPlace = getStopPlace("FSR:StopPlace:300", null, getPoint(new Coordinate(0.3, 0.3)));
211+
parentStopPlace.setParentStopPlace(true);
212+
StopPlace childBus = getStopPlace("FSR:StopPlace:301", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.3, 0.3)));
213+
StopPlace childRail = getStopPlace("FSR:StopPlace:302", VehicleModeEnumeration.RAIL, getPoint(new Coordinate(0.4, 0.4)));
214+
parentStopPlace.setChildren(Set.of(childBus, childRail));
215+
216+
FintrafficAuthorizationService authorizationService = getAuthorizationService();
217+
assertThat(authorizationService.canEditEntity(parentStopPlace), equalTo(false));
218+
}
219+
220+
@Test
221+
public void testCanEditParentStopPlaceWithNoChildren() {
222+
// Parent stop place with no children
223+
StopPlace parentStopPlace = getStopPlace("FSR:StopPlace:400", null, getPoint(new Coordinate(0.3, 0.3)));
224+
parentStopPlace.setParentStopPlace(true);
225+
parentStopPlace.setChildren(Set.of());
226+
227+
FintrafficAuthorizationService authorizationService = getAuthorizationService();
228+
assertThat(authorizationService.canEditEntity(parentStopPlace), equalTo(false));
229+
}
230+
163231
@Test
164232
public void testCanEditStopPlaceWithNestedEntities() {
165233
StopPlace stopPlaceWithQuayAndNestedStopPlace = getStopPlace("FSR:StopPlace:1", VehicleModeEnumeration.BUS, getPoint(new Coordinate(0.3, 0.3)));
@@ -212,7 +280,7 @@ public void testCanEditEntityByEitherCodespaceOrMunicipality() {
212280

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

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

231299
@Test
232300
public void testBothAuthorizationMethodsDisabled() {
233-
FintrafficAuthorizationService authorizationService = getAuthorizationService(false, false);
301+
FintrafficAuthorizationService authorizationService = getAuthorizationService(false, false, false);
234302
// Both disabled — all geographic edits denied
235303
assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(0.5, 0.5))), equalTo(false));
236304
assertThat(authorizationService.canEditEntity(getPoint(new Coordinate(2.5, 2.5))), equalTo(false));
237305
}
306+
307+
@Test
308+
public void testGroupOfStopPlacesEditable() {
309+
GroupOfStopPlaces groupOfStopPlaces = new GroupOfStopPlaces();
310+
groupOfStopPlaces.setNetexId("FSR:GroupOfStopPlaces:1");
311+
312+
FintrafficAuthorizationService authorizationService = getAuthorizationService();
313+
assertThat(authorizationService.canEditEntity(groupOfStopPlaces), equalTo(true));
314+
}
238315
}

src/ext/java/org/rutebanken/tiamat/ext/fintraffic/api/FintrafficSearchKeyService.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.rutebanken.tiamat.model.StopPlace;
2121
import org.rutebanken.tiamat.model.TopographicPlace;
2222
import org.rutebanken.tiamat.model.TopographicPlaceTypeEnumeration;
23+
import org.rutebanken.tiamat.model.VehicleModeEnumeration;
2324
import org.rutebanken.tiamat.repository.StopPlaceRepository;
2425
import org.rutebanken.tiamat.repository.TopographicPlaceRepository;
2526
import org.slf4j.Logger;
@@ -152,6 +153,19 @@ private FintrafficReadApiSearchKey extractSearchKey(org.rutebanken.tiamat.model.
152153
String[] transportModes = stopPlace.getTransportMode() != null
153154
? new String[]{stopPlace.getTransportMode().value()}
154155
: new String[]{};
156+
157+
// If StopPlace is parentStopPlace then get transportModes from child stopPlaces as well
158+
if (stopPlace.isParentStopPlace() && stopPlace.getChildren() != null) {
159+
String[] childTransportModes = stopPlace.getChildren().stream().map(org.rutebanken.tiamat.model.StopPlace::getTransportMode)
160+
.filter(Objects::nonNull)
161+
.map(VehicleModeEnumeration::value)
162+
.toArray(String[]::new);
163+
164+
transportModes = Stream.concat(Stream.of(transportModes), Stream.of(childTransportModes))
165+
.distinct()
166+
.toArray(String[]::new);
167+
}
168+
155169
Optional<Point> stopPlaceCentroid = Optional.ofNullable(stopPlace.getCentroid());
156170
Optional<String[]> areaCodes = stopPlaceCentroid.map(this::getAdministrativeZonesForPoint);
157171

0 commit comments

Comments
 (0)