Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Respond with status code 502 if configured feed not yet available #432

Merged
merged 6 commits into from
May 3, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
}
}