Skip to content

Commit

Permalink
Merge pull request #432 from mobidata-bw/issues/431_service_unavailable
Browse files Browse the repository at this point in the history
Respond with status code 502 if configured feed not yet available
  • Loading branch information
testower committed May 3, 2024
2 parents 373e17a + 1b3a164 commit 1591a84
Show file tree
Hide file tree
Showing 6 changed files with 384 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,38 @@ protected Object getFeed(String systemId, String feed) {
var data = feedCache.find(feedName, feedProvider);

if (data == null) {
throwsIfFeedCouldOrShouldExist(feedName, feedProvider);
throw new NoSuchElementException();
}
return data;
}

/*
Throws an UpstreamFeedNotYetAvailableException, if either the discoveryFile (gbf file) is not yet cached,
the requested feed is published in the discovery file, or the discovery file is malformed.
*/
protected void throwsIfFeedCouldOrShouldExist(
GBFSFeedName feedName,
FeedProvider feedProvider
) {
try {
GBFS discoveryFile = (GBFS) feedCache.find(GBFSFeedName.GBFS, feedProvider);
if (
discoveryFile == null ||
((GBFS) discoveryFile).getFeedsData()
.values()
.stream()
.map(GBFSFeeds::getFeeds)
.flatMap(list -> list.stream())
.map(GBFSFeed::getName)
.anyMatch(name -> name.equals(feedName))
) {
throw new UpstreamFeedNotYetAvailableException();
}
} catch (NullPointerException e) {
// in case the gbfs is malformed, e.g. no languages are defined, or no feeds,
// this is an upstream error and the requested feed might exist
throw new UpstreamFeedNotYetAvailableException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@
import java.time.Instant;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;
import org.entur.gbfs.v2_3.gbfs.GBFS;
import org.entur.gbfs.v2_3.gbfs.GBFSFeed;
import org.entur.gbfs.v2_3.gbfs.GBFSFeedName;
import org.entur.gbfs.v2_3.gbfs.GBFSFeeds;
import org.entur.lamassu.cache.GBFSV2FeedCache;
import org.entur.lamassu.model.discovery.SystemDiscovery;
import org.entur.lamassu.model.provider.FeedProvider;
import org.entur.lamassu.service.FeedProviderService;
import org.entur.lamassu.service.SystemDiscoveryService;
import org.entur.lamassu.util.CacheUtil;
Expand Down Expand Up @@ -74,7 +78,6 @@ public ResponseEntity<Object> getGbfsFeedForProvider(
try {
var feedName = GBFSFeedName.fromValue(feed);
Object data = getFeed(systemId, feed);

return ResponseEntity
.ok()
.cacheControl(
Expand Down Expand Up @@ -114,8 +117,38 @@ protected Object getFeed(String systemId, String feed) {
var data = feedCache.find(feedName, feedProvider);

if (data == null) {
throwsIfFeedCouldOrShouldExist(feedName, feedProvider);
throw new NoSuchElementException();
}
return data;
}

/*
Throws an UpstreamFeedNotYetAvailableException, if either the discoveryFile (gbf file) is not yet cached,
the requested feed is published in the discovery file, or the discovery file is malformed.
*/
protected void throwsIfFeedCouldOrShouldExist(
GBFSFeedName feedName,
FeedProvider feedProvider
) {
try {
GBFS discoveryFile = (GBFS) feedCache.find(GBFSFeedName.GBFS, feedProvider);
if (
discoveryFile == null ||
((GBFS) discoveryFile).getFeedsData()
.values()
.stream()
.map(GBFSFeeds::getFeeds)
.flatMap(list -> list.stream())
.map(GBFSFeed::getName)
.anyMatch(name -> name.equals(feedName))
) {
throw new UpstreamFeedNotYetAvailableException();
}
} catch (NullPointerException e) {
// in case the gbfs is malformed, e.g. no languages are defined, or no feeds,
// this is an upstream error and the requested feed might exist
throw new UpstreamFeedNotYetAvailableException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;
import org.entur.gbfs.v3_0.gbfs.GBFSFeed;
import org.entur.gbfs.v3_0.gbfs.GBFSGbfs;
import org.entur.gbfs.v3_0.manifest.GBFSManifest;
import org.entur.lamassu.cache.GBFSV3FeedCache;
import org.entur.lamassu.model.provider.FeedProvider;
import org.entur.lamassu.service.FeedProviderService;
import org.entur.lamassu.service.SystemDiscoveryService;
import org.entur.lamassu.util.CacheUtil;
Expand Down Expand Up @@ -121,8 +123,40 @@ protected Object getFeed(String systemId, String feed) {
var data = v3FeedCache.find(feedName, feedProvider);

if (data == null) {
throwsIfFeedCouldOrShouldExist(feedName, feedProvider);
throw new NoSuchElementException();
}
return data;
}

/*
Throws an UpstreamFeedNotYetAvailableException, if either the discoveryFile (gbf file) is not yet cached,
the requested feed is published in the discovery file, or the discovery file is malformed.
*/
protected void throwsIfFeedCouldOrShouldExist(
GBFSFeed.Name feedName,
FeedProvider feedProvider
) {
try {
GBFSGbfs discoveryFile = (GBFSGbfs) v3FeedCache.find(
GBFSFeed.Name.GBFS,
feedProvider
);
if (
discoveryFile == null ||
discoveryFile
.getData()
.getFeeds()
.stream()
.map(GBFSFeed::getName)
.anyMatch(name -> name.equals(feedName))
) {
throw new UpstreamFeedNotYetAvailableException();
}
} catch (NullPointerException e) {
// in case the gbfs is malformed, e.g. no languages are defined, or no feeds,
// this is an upstream error and the requested feed might exist
throw new UpstreamFeedNotYetAvailableException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
*
*
* * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by
* * the European Commission - subsequent versions of the EUPL (the "Licence");
* * You may not use this work except in compliance with the Licence.
* * You may obtain a copy of the Licence at:
* *
* * https://joinup.ec.europa.eu/software/page/eupl
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the Licence is distributed on an "AS IS" basis,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the Licence for the specific language governing permissions and
* * limitations under the Licence.
*
*/

package org.entur.lamassu.controller;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.BAD_GATEWAY, reason = "Feed not (yet) available")
public class UpstreamFeedNotYetAvailableException extends RuntimeException {

public UpstreamFeedNotYetAvailableException() {
super();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package org.entur.lamassu.controller;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.List;
import java.util.Map;
import org.entur.gbfs.v2_3.gbfs.GBFS;
import org.entur.gbfs.v2_3.gbfs.GBFSFeed;
import org.entur.gbfs.v2_3.gbfs.GBFSFeedName;
import org.entur.gbfs.v2_3.gbfs.GBFSFeeds;
import org.entur.lamassu.cache.GBFSV2FeedCache;
import org.entur.lamassu.model.provider.FeedProvider;
import org.entur.lamassu.service.FeedProviderService;
import org.entur.lamassu.service.SystemDiscoveryService;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.web.server.ResponseStatusException;

public class GBFSV2FeedControllerTest {

public static final String KNOWN_SYSTEM_ID = "knownSystem";
private GBFSV2FeedController feedController;
private FeedProviderService mockedFeedProviderService;

@Rule
public ExpectedException expectedException = ExpectedException.none();

private GBFSV2FeedCache mockedFeedCache;

@Before
public void before() {
SystemDiscoveryService systemDiscoveryService = mock(SystemDiscoveryService.class);
mockedFeedCache = mock(GBFSV2FeedCache.class);
mockedFeedProviderService = mock(FeedProviderService.class);

feedController =
new GBFSV2FeedController(
systemDiscoveryService,
mockedFeedCache,
mockedFeedProviderService
);
}

@Test
public void throws400OnNonGBFSFeedRequest() {
expectedException.expect(ResponseStatusException.class);
expectedException.expectMessage("400 BAD_REQUEST");

feedController.getGbfsFeedForProvider("anySystem", "no-gbfs-feed");
}

@Test
public void throws404OnNonConfiguredSystemRequest() {
expectedException.expect(ResponseStatusException.class);
expectedException.expectMessage("404 NOT_FOUND");
feedController.getGbfsFeedForProvider("unknownSystem", "gbfs");
}

@Test
public void throws502OnConfiguredSystemButUnavailableFeedRequest() {
FeedProvider feedProvider = new FeedProvider();
feedProvider.setSystemId(KNOWN_SYSTEM_ID);
when(mockedFeedProviderService.getFeedProviderBySystemId(KNOWN_SYSTEM_ID))
.thenReturn(feedProvider);

expectedException.expect(UpstreamFeedNotYetAvailableException.class);
feedController.getGbfsFeedForProvider(KNOWN_SYSTEM_ID, "gbfs");
}

@Test
public void throws404OnConfiguredSystemButUndeclaredFeedRequest() {
var feedProvider = new FeedProvider();
feedProvider.setSystemId(KNOWN_SYSTEM_ID);
var gbfs = createDiscoveryFileWithFeed(GBFSFeedName.GBFS);

when(mockedFeedProviderService.getFeedProviderBySystemId(KNOWN_SYSTEM_ID))
.thenReturn(feedProvider);
when(mockedFeedCache.find(GBFSFeedName.GBFS, feedProvider)).thenReturn(gbfs);
when(mockedFeedCache.find(GBFSFeedName.GeofencingZones, feedProvider))
.thenReturn(null);
expectedException.expect(ResponseStatusException.class);
expectedException.expectMessage("404 NOT_FOUND");
feedController.getGbfsFeedForProvider(KNOWN_SYSTEM_ID, "geofencing_zones");
}

@Test
public void throws502OnConfiguredSystemAndDeclaredFeedRequest() {
var feedProvider = new FeedProvider();
feedProvider.setSystemId(KNOWN_SYSTEM_ID);
var gbfs = createDiscoveryFileWithFeed(GBFSFeedName.GeofencingZones);

when(mockedFeedProviderService.getFeedProviderBySystemId(KNOWN_SYSTEM_ID))
.thenReturn(feedProvider);
when(mockedFeedCache.find(GBFSFeedName.GBFS, feedProvider)).thenReturn(gbfs);
when(mockedFeedCache.find(GBFSFeedName.GeofencingZones, feedProvider))
.thenReturn(null);
expectedException.expect(UpstreamFeedNotYetAvailableException.class);
feedController.getGbfsFeedForProvider(KNOWN_SYSTEM_ID, "geofencing_zones");
}

@Test
public void throws502OnConfiguredSystemAndMalformedDiscoveryFeedRequest() {
var feedProvider = new FeedProvider();
feedProvider.setSystemId(KNOWN_SYSTEM_ID);
// GBFS is malformed, as it has no feeds defined
var gbfs = new GBFS();

when(mockedFeedProviderService.getFeedProviderBySystemId(KNOWN_SYSTEM_ID))
.thenReturn(feedProvider);
when(mockedFeedCache.find(GBFSFeedName.GBFS, feedProvider)).thenReturn(gbfs);
when(mockedFeedCache.find(GBFSFeedName.GeofencingZones, feedProvider))
.thenReturn(null);
expectedException.expect(UpstreamFeedNotYetAvailableException.class);
feedController.getGbfsFeedForProvider(KNOWN_SYSTEM_ID, "geofencing_zones");
}

public GBFS createDiscoveryFileWithFeed(GBFSFeedName feedName) {
var gbfs = new GBFS();
var feeds = new GBFSFeeds();
var feed = new GBFSFeed();
feed.setName(feedName);
feeds.setFeeds(List.of(feed));
gbfs.setFeedsData(Map.of("en", feeds));
return gbfs;
}
}

0 comments on commit 1591a84

Please sign in to comment.