diff --git a/pom.xml b/pom.xml
index 64041c04e..3a7fab6a6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -272,7 +272,7 @@
com.github.ibi-group
gtfs-lib
- e9de250
+ 5e004388b2473c391412fdd4954068c35186e231
diff --git a/src/main/java/com/conveyal/datatools/manager/extensions/mtc/MtcFeedResource.java b/src/main/java/com/conveyal/datatools/manager/extensions/mtc/MtcFeedResource.java
index 5b3eed845..5d27a495f 100644
--- a/src/main/java/com/conveyal/datatools/manager/extensions/mtc/MtcFeedResource.java
+++ b/src/main/java/com/conveyal/datatools/manager/extensions/mtc/MtcFeedResource.java
@@ -51,6 +51,8 @@ public class MtcFeedResource implements ExternalFeedResource {
public static final String TEST_AGENCY = "test-agency";
public static final String AGENCY_ID_FIELDNAME = "AgencyId";
public static final String RESOURCE_TYPE = "MTC";
+ public static final String STOP_CODE_PRIMARY_PREFIX_FIELD_NAME = "PrimaryPrefix";
+ public static final String STOP_CODE_SECONDARY_PREFIXES_FIELD_NAME = "SecondaryPrefixes";
private String rtdApi, s3Bucket, s3Prefix;
@@ -337,4 +339,14 @@ static String convertRtdString(String s) {
if ("null".equals(s)) return "";
return s;
}
+
+ public static String getFieldValue(Map> properties, String fieldName) {
+ Map values = properties.get(RESOURCE_TYPE);
+ return values.get(fieldName);
+ }
+
+ public static List getSecondaryStopCodePrefixes(Map> properties) {
+ String secondaryStopPrefixValue = MtcFeedResource.getFieldValue(properties, MtcFeedResource.STOP_CODE_SECONDARY_PREFIXES_FIELD_NAME);
+ return (secondaryStopPrefixValue == null) ? null : List.of(secondaryStopPrefixValue.split(","));
+ }
}
diff --git a/src/main/java/com/conveyal/datatools/manager/extensions/mtc/RtdCarrier.java b/src/main/java/com/conveyal/datatools/manager/extensions/mtc/RtdCarrier.java
index 4e66fe541..91fad0452 100644
--- a/src/main/java/com/conveyal/datatools/manager/extensions/mtc/RtdCarrier.java
+++ b/src/main/java/com/conveyal/datatools/manager/extensions/mtc/RtdCarrier.java
@@ -9,6 +9,8 @@
import java.lang.reflect.Field;
+import static com.conveyal.datatools.manager.extensions.mtc.MtcFeedResource.STOP_CODE_PRIMARY_PREFIX_FIELD_NAME;
+import static com.conveyal.datatools.manager.extensions.mtc.MtcFeedResource.STOP_CODE_SECONDARY_PREFIXES_FIELD_NAME;
import static com.conveyal.datatools.manager.models.ExternalFeedSourceProperty.constructId;
/**
@@ -70,6 +72,12 @@ public class RtdCarrier {
@JsonProperty
String EditedDate;
+ @JsonProperty
+ String PrimaryPrefix;
+
+ @JsonProperty
+ String SecondaryPrefixes;
+
/** Empty constructor needed for serialization (also used to create empty carrier). */
public RtdCarrier() {
}
@@ -94,6 +102,8 @@ public RtdCarrier(FeedSource source) {
AgencyEmail = getValueForField(source, "AgencyEmail");
AgencyUrl = getValueForField(source, "AgencyUrl");
AgencyFareUrl = getValueForField(source, "AgencyFareUrl");
+ PrimaryPrefix = getValueForField(source, STOP_CODE_PRIMARY_PREFIX_FIELD_NAME);
+ SecondaryPrefixes = getValueForField(source, STOP_CODE_SECONDARY_PREFIXES_FIELD_NAME);
}
private String getPropId(FeedSource source, String fieldName) {
diff --git a/src/main/java/com/conveyal/datatools/manager/models/Deployment.java b/src/main/java/com/conveyal/datatools/manager/models/Deployment.java
index aeccae961..201b96f1b 100644
--- a/src/main/java/com/conveyal/datatools/manager/models/Deployment.java
+++ b/src/main/java/com/conveyal/datatools/manager/models/Deployment.java
@@ -18,6 +18,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.ByteStreams;
import com.mongodb.client.FindIterable;
+import org.apache.logging.log4j.util.Strings;
import org.bson.codecs.pojo.annotations.BsonIgnore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -355,7 +356,9 @@ public void dump (File output, boolean includeManifest, boolean includeOsm, bool
LOG.error("Could not retrieve file for {}", v.name);
throw new RuntimeException(e1);
}
- ZipEntry e = new ZipEntry(gtfsFile.getName());
+ // Determine the entry name for the zip file.
+ String entryName = getFeedSourceBundleFilename(v, gtfsFile);
+ ZipEntry e = new ZipEntry(entryName);
out.putNextEntry(e);
ByteStreams.copy(in, out);
try {
@@ -417,6 +420,29 @@ public void dump (File output, boolean includeManifest, boolean includeOsm, bool
out.close();
}
+ /**
+ * Determine the entry name for a GTFS file within the deployment bundle.
+ * This prioritizes the FeedSource filename if available and valid, otherwise falls back
+ * to the original GTFS filename derived from the FeedVersion.
+ *
+ * @param feedVersion The FeedVersion being processed.
+ * @param gtfsFile The GTFS file associated with the FeedVersion.
+ * @return The calculated entry name for the zip file.
+ */
+ public String getFeedSourceBundleFilename(FeedVersion feedVersion, File gtfsFile) {
+ String gtfsFileName = gtfsFile.getName();
+ FeedSource fs = feedVersion.parentFeedSource();
+
+ if (fs != null && !Strings.isBlank(fs.filename)) {
+ // Use FeedSource filename if available, ensuring it ends with .zip
+ LOG.info("Using FeedSource filename for zip entry: {}", gtfsFileName);
+ return fs.filename.endsWith(".zip") ? fs.filename : fs.filename + ".zip";
+ }
+ // Fallback to the original GTFS filename derived from FeedVersion
+ LOG.info("Using FeedVersion filename for zip entry: {}", gtfsFileName);
+ return gtfsFileName;
+ }
+
/** Download config from provided URL. */
public String downloadConfig(String configUrl) throws IOException {
if (configUrl != null) {
diff --git a/src/main/java/com/conveyal/datatools/manager/models/FeedSource.java b/src/main/java/com/conveyal/datatools/manager/models/FeedSource.java
index db7eea7a9..c88d6c9d0 100644
--- a/src/main/java/com/conveyal/datatools/manager/models/FeedSource.java
+++ b/src/main/java/com/conveyal/datatools/manager/models/FeedSource.java
@@ -97,6 +97,9 @@ public String organizationId () {
/** The name of this feed source, e.g. MTA New York City Subway */
public String name;
+ /** An optional display filename for the feed in the bundle, e.g. "agency_transit.zip" */
+ public String filename;
+
/** Is this feed public, i.e. should it be listed on the
* public feeds page for download?
*/
diff --git a/src/main/java/com/conveyal/datatools/manager/models/FeedSourceSummary.java b/src/main/java/com/conveyal/datatools/manager/models/FeedSourceSummary.java
index 15f93e57b..1b88b91a5 100644
--- a/src/main/java/com/conveyal/datatools/manager/models/FeedSourceSummary.java
+++ b/src/main/java/com/conveyal/datatools/manager/models/FeedSourceSummary.java
@@ -42,6 +42,9 @@ public class FeedSourceSummary {
public boolean deployable;
public boolean isPublic;
+ /** An optional display filename for the feed in the bundle, e.g. "agency_transit.zip" */
+ public String filename;
+
@JsonSerialize(using = JacksonSerializers.LocalDateIsoSerializer.class)
@JsonDeserialize(using = JacksonSerializers.LocalDateIsoDeserializer.class)
public LocalDate lastUpdated;
@@ -89,6 +92,8 @@ public FeedSourceSummary(String projectId, String organizationId, Document feedS
// Convert to local date type for consistency.
this.lastUpdated = getLocalDateFromDate(feedSourceDocument.getDate("lastUpdated"));
this.url = feedSourceDocument.getString("url");
+ // Get optional filename.
+ this.filename = feedSourceDocument.getString("filename");
}
/**
@@ -131,6 +136,7 @@ public static List getFeedSourceSummaries(String projectId, S
"lastUpdated": 1,
"labelIds": 1,
"url": 1,
+ "filename": 1,
"noteIds": 1
}
},
@@ -154,6 +160,7 @@ public static List getFeedSourceSummaries(String projectId, S
"lastUpdated",
"labelIds",
"url",
+ "filename",
"noteIds")
)
),
diff --git a/src/main/java/com/conveyal/datatools/manager/models/FeedVersion.java b/src/main/java/com/conveyal/datatools/manager/models/FeedVersion.java
index bb87c6187..0091605cc 100644
--- a/src/main/java/com/conveyal/datatools/manager/models/FeedVersion.java
+++ b/src/main/java/com/conveyal/datatools/manager/models/FeedVersion.java
@@ -3,6 +3,7 @@
import com.conveyal.datatools.common.status.MonitorableJob;
import com.conveyal.datatools.common.utils.Scheduler;
import com.conveyal.datatools.manager.DataManager;
+import com.conveyal.datatools.manager.extensions.mtc.MtcFeedResource;
import com.conveyal.datatools.manager.jobs.ValidateFeedJob;
import com.conveyal.datatools.manager.jobs.ValidateMobilityDataFeedJob;
import com.conveyal.datatools.manager.jobs.validation.RouteTypeValidatorBuilder;
@@ -48,6 +49,8 @@
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.Date;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -365,15 +368,24 @@ public void validate(MonitorableJob.Status status) {
// FIXME: pass status to validate? Or somehow listen to events?
status.update("Validating feed...", 33);
+ FeedSource fs = Persistence.feedSources.getById(this.feedSourceId);
+
// Validate the feed version.
// Certain extensions, if enabled, have extra validators.
if (isExtensionEnabled("mtc")) {
+ Map> properties = fs.externalProperties();
+ String primaryStopCodePrefix = MtcFeedResource.getFieldValue(properties, MtcFeedResource.STOP_CODE_PRIMARY_PREFIX_FIELD_NAME);
+ List secondaryStopCodePrefixes = MtcFeedResource.getSecondaryStopCodePrefixes(properties);
validationResult = GTFS.validate(feedLoadResult.uniqueIdentifier, DataManager.GTFS_DATA_SOURCE,
RouteTypeValidatorBuilder::buildRouteValidator,
- MTCValidator::new
+ (feed, errorStorage) -> new MTCValidator(
+ feed,
+ errorStorage,
+ primaryStopCodePrefix,
+ secondaryStopCodePrefixes
+ )
);
} else {
- FeedSource fs = Persistence.feedSources.getById(this.feedSourceId);
/*
Get feed_id from feed version
diff --git a/src/main/java/com/conveyal/datatools/manager/utils/HashUtils.java b/src/main/java/com/conveyal/datatools/manager/utils/HashUtils.java
index 7df050e52..a23660b7c 100644
--- a/src/main/java/com/conveyal/datatools/manager/utils/HashUtils.java
+++ b/src/main/java/com/conveyal/datatools/manager/utils/HashUtils.java
@@ -1,6 +1,5 @@
package com.conveyal.datatools.manager.utils;
-import com.conveyal.datatools.manager.extensions.mtc.MtcFeedResource;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -14,7 +13,7 @@
public class HashUtils {
- public static final Logger LOG = LoggerFactory.getLogger(MtcFeedResource.class);
+ public static final Logger LOG = LoggerFactory.getLogger(HashUtils.class);
/**
* Get MD5 hash for the specified file.
diff --git a/src/test/java/com/conveyal/datatools/manager/extensions/mtc/MtcFeedResourceTest.java b/src/test/java/com/conveyal/datatools/manager/extensions/mtc/MtcFeedResourceTest.java
index 3a6cb6a31..07b69eaf5 100644
--- a/src/test/java/com/conveyal/datatools/manager/extensions/mtc/MtcFeedResourceTest.java
+++ b/src/test/java/com/conveyal/datatools/manager/extensions/mtc/MtcFeedResourceTest.java
@@ -8,6 +8,7 @@
import com.conveyal.datatools.manager.models.FeedVersion;
import com.conveyal.datatools.manager.models.Project;
import com.conveyal.datatools.manager.persistence.Persistence;
+import com.conveyal.datatools.manager.utils.SqlAssert;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.github.tomakehurst.wiremock.WireMockServer;
@@ -18,11 +19,15 @@
import org.junit.jupiter.params.provider.ValueSource;
import java.io.IOException;
+import java.sql.SQLException;
import java.util.Date;
import static com.conveyal.datatools.TestUtils.createFeedVersion;
import static com.conveyal.datatools.TestUtils.parseJson;
import static com.conveyal.datatools.TestUtils.zipFolderFiles;
+import static com.conveyal.datatools.manager.extensions.mtc.MtcFeedResource.STOP_CODE_PRIMARY_PREFIX_FIELD_NAME;
+import static com.conveyal.datatools.manager.extensions.mtc.MtcFeedResource.STOP_CODE_SECONDARY_PREFIXES_FIELD_NAME;
+import static com.conveyal.gtfs.error.NewGTFSErrorType.MISSING_STOP_CODE_PREFIX;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
@@ -99,31 +104,13 @@ void canUpdateFeedExternalPropertiesToMongo() throws IOException {
// Set up some entries in the ExternalFeedSourceProperties collection.
// This one (AgencyId) should not change.
- ExternalFeedSourceProperty agencyIdProp = new ExternalFeedSourceProperty(
- feedSource,
- "MTC",
- "AgencyId",
- AGENCY_CODE
- );
- Persistence.externalFeedSourceProperties.create(agencyIdProp);
+ String agencyIdPropId = createExternalFeedSourceProperties("AgencyId", AGENCY_CODE);
// This one (AgencyPublicId) should be deleted after this test (not in RTD response).
- ExternalFeedSourceProperty agencyPublicIdProp = new ExternalFeedSourceProperty(
- feedSource,
- "MTC",
- "AgencyPublicId",
- AGENCY_CODE
- );
- Persistence.externalFeedSourceProperties.create(agencyPublicIdProp);
+ String agencyPublicIdPropId = createExternalFeedSourceProperties("AgencyPublicId", AGENCY_CODE);
// This one (AgencyEmail) should be updated with this test.
- ExternalFeedSourceProperty agencyEmailProp = new ExternalFeedSourceProperty(
- feedSource,
- "MTC",
- "AgencyEmail",
- "old@email.example.com"
- );
- Persistence.externalFeedSourceProperties.create(agencyEmailProp);
+ String agencyEmailPropId = createExternalFeedSourceProperties("AgencyEmail", "old@email.example.com");
// make RTD request and parse the json response
JsonNode rtdResponse = parseJson(
@@ -137,55 +124,86 @@ void canUpdateFeedExternalPropertiesToMongo() throws IOException {
// Also extract desired values from response
String responseEmail = rtdResponse.get("AgencyEmail").asText();
String responseAgencyName = rtdResponse.get("AgencyName").asText();
+ String responsePrimaryPrefix = rtdResponse.get(STOP_CODE_PRIMARY_PREFIX_FIELD_NAME).asText();
+ String responseSecondaryPrefixes = rtdResponse.get(STOP_CODE_SECONDARY_PREFIXES_FIELD_NAME).asText();
// Update MTC Feed properties in Mongo based response.
new MtcFeedResource().updateMongoExternalFeedProperties(feedSource, rtdResponse);
// Existing field AgencyId should retain the same value.
- ExternalFeedSourceProperty updatedAgencyIdProp = Persistence.externalFeedSourceProperties.getById(agencyIdProp.id);
- assertThat(updatedAgencyIdProp.value, equalTo(agencyIdProp.value));
+ ExternalFeedSourceProperty updatedAgencyIdProp = Persistence.externalFeedSourceProperties.getById(agencyIdPropId);
+ assertThat(updatedAgencyIdProp.value, equalTo(AGENCY_CODE));
// Existing field AgencyEmail should be updated from RTD response.
- ExternalFeedSourceProperty updatedEmailProp = Persistence.externalFeedSourceProperties.getById(agencyEmailProp.id);
+ ExternalFeedSourceProperty updatedEmailProp = Persistence.externalFeedSourceProperties.getById(agencyEmailPropId);
assertThat(updatedEmailProp.value, equalTo(responseEmail));
- // New field AgencyName (not set up above) from RTD response should be added to Mongo.
- ExternalFeedSourceProperty newAgencyNameProp = Persistence.externalFeedSourceProperties.getOneFiltered(
- and(
- eq("feedSourceId", feedSource.id),
- eq("resourceType", "MTC"),
- eq("name", "AgencyName") )
- );
- assertThat(newAgencyNameProp, notNullValue());
- assertThat(newAgencyNameProp.value, equalTo(responseAgencyName));
+ checkForRTDPropInMongo("AgencyName", responseAgencyName);
+ String primaryPrefixPropId = checkForRTDPropInMongo(STOP_CODE_PRIMARY_PREFIX_FIELD_NAME, responsePrimaryPrefix);
+ String secondaryPrefixesPropId = checkForRTDPropInMongo(STOP_CODE_SECONDARY_PREFIXES_FIELD_NAME, responseSecondaryPrefixes);
// Removed field AgencyPublicId from RTD should be deleted from Mongo.
- ExternalFeedSourceProperty removedPublicIdProp = Persistence.externalFeedSourceProperties.getById(agencyPublicIdProp.id);
+ ExternalFeedSourceProperty removedPublicIdProp = Persistence.externalFeedSourceProperties.getById(agencyPublicIdPropId);
assertThat(removedPublicIdProp, nullValue());
- Persistence.externalFeedSourceProperties.removeById(agencyIdProp.id);
- Persistence.externalFeedSourceProperties.removeById(agencyPublicIdProp.id);
- Persistence.externalFeedSourceProperties.removeById(agencyEmailProp.id);
+ Persistence.externalFeedSourceProperties.removeById(agencyIdPropId);
+ Persistence.externalFeedSourceProperties.removeById(agencyPublicIdPropId);
+ Persistence.externalFeedSourceProperties.removeById(agencyEmailPropId);
+ Persistence.externalFeedSourceProperties.removeById(primaryPrefixPropId);
+ Persistence.externalFeedSourceProperties.removeById(secondaryPrefixesPropId);
+ }
+
+ /**
+ * Check that new fields from RTD response are added to Mongo.
+ */
+ private String checkForRTDPropInMongo(String propName, String expectedValue) {
+ ExternalFeedSourceProperty prop = Persistence.externalFeedSourceProperties.getOneFiltered(
+ and(
+ eq("feedSourceId", feedSource.id),
+ eq("resourceType", "MTC"),
+ eq("name", propName)
+ )
+ );
+ assertThat(prop, notNullValue());
+ assertThat(prop.value, equalTo(expectedValue));
+ return prop.id;
}
@Test
void shouldTolerateNullObjectInExternalPropertyAgencyId() throws IOException {
// Add an entry in the ExternalFeedSourceProperties collection
// with AgencyId value set to null.
- ExternalFeedSourceProperty agencyIdProp = new ExternalFeedSourceProperty(
- feedSource,
- "MTC",
- "AgencyId",
- null
- );
- Persistence.externalFeedSourceProperties.create(agencyIdProp);
-
+ String agencyIdProp = createExternalFeedSourceProperties("AgencyId", null);
// Trigger the feed update process (it should not upload anything to S3).
- FeedVersion feedVersion = createFeedVersion(feedSource, zipFolderFiles("mini-bart-new"));
+ FeedVersion feedVersion = createFeedVersion(feedSource, zipFolderFiles("mtc-feed-resource-test"));
MtcFeedResource mtcFeedResource = new MtcFeedResource();
assertDoesNotThrow(() -> mtcFeedResource.feedVersionCreated(feedVersion, null));
+ Persistence.externalFeedSourceProperties.removeById(agencyIdProp);
+ }
- Persistence.externalFeedSourceProperties.removeById(agencyIdProp.id);
+ @Test
+ void shouldValidateStopCodePrefixes() throws IOException, SQLException {
+ String primaryPrefixPropId = createExternalFeedSourceProperties(STOP_CODE_PRIMARY_PREFIX_FIELD_NAME, "primary-1");
+ String secondaryPrefixesPropId = createExternalFeedSourceProperties(STOP_CODE_SECONDARY_PREFIXES_FIELD_NAME, "secondary-1,secondary-2");
+
+ FeedVersion feedVersion = createFeedVersion(feedSource, zipFolderFiles("mtc-feed-resource-test"));
+ MtcFeedResource mtcFeedResource = new MtcFeedResource();
+ assertDoesNotThrow(() -> mtcFeedResource.feedVersionCreated(feedVersion, null));
+ SqlAssert sqlAssert = new SqlAssert(feedVersion);
+ sqlAssert.errors.assertCount(1, String.format("error_type='%s'", MISSING_STOP_CODE_PREFIX.name()));
+ Persistence.externalFeedSourceProperties.removeById(primaryPrefixPropId);
+ Persistence.externalFeedSourceProperties.removeById(secondaryPrefixesPropId);
+ }
+
+ private String createExternalFeedSourceProperties(String name, String value) {
+ ExternalFeedSourceProperty prop = new ExternalFeedSourceProperty(
+ feedSource,
+ "MTC",
+ name,
+ value
+ );
+ Persistence.externalFeedSourceProperties.create(prop);
+ return prop.id;
}
@ParameterizedTest
diff --git a/src/test/java/com/conveyal/datatools/manager/jobs/DeployJobTest.java b/src/test/java/com/conveyal/datatools/manager/jobs/DeployJobTest.java
index e0504d589..9cdf6b0f0 100644
--- a/src/test/java/com/conveyal/datatools/manager/jobs/DeployJobTest.java
+++ b/src/test/java/com/conveyal/datatools/manager/jobs/DeployJobTest.java
@@ -9,6 +9,8 @@
import com.conveyal.datatools.manager.auth.Auth0UserProfile;
import com.conveyal.datatools.manager.models.Deployment;
import com.conveyal.datatools.manager.models.EC2Info;
+import com.conveyal.datatools.manager.models.FeedSource;
+import com.conveyal.datatools.manager.models.FeedVersion;
import com.conveyal.datatools.manager.models.OtpServer;
import com.conveyal.datatools.manager.models.Project;
import com.conveyal.datatools.manager.persistence.Persistence;
@@ -18,6 +20,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
@@ -26,6 +29,7 @@
import static com.conveyal.datatools.TestUtils.getBooleanEnvVar;
import static com.zenika.snapshotmatcher.SnapshotMatcher.matchesSnapshot;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@@ -90,6 +94,41 @@ void canDownloadConfigs() throws IOException {
assertNotNull(deployment.downloadConfig(deployment.customRouterConfigUrl));
}
+ /**
+ * Tests that the bundle filename is correctly determined.
+ */
+ @Test
+ public void canGetFeedSourceBundleFilename() throws IOException {
+ FeedSource feedSource = new FeedSource("Test FeedSource");
+ feedSource.projectId = project.id;
+ FeedVersion feedVersion = new FeedVersion(feedSource);
+ File gtfsFile = File.createTempFile("test-gtfs-", ".zip");
+ var gtfsFileName = gtfsFile.getName();
+ gtfsFile.deleteOnExit();
+
+ feedSource.filename = "gtfs_from_source.zip";
+ Persistence.feedSources.create(feedSource);
+ assertEquals("gtfs_from_source.zip", deployment.getFeedSourceBundleFilename(feedVersion, gtfsFile));
+
+ feedSource.filename = "gtfs_from_source";
+ Persistence.feedSources.replace(feedSource.id, feedSource);
+ assertEquals("gtfs_from_source.zip", deployment.getFeedSourceBundleFilename(feedVersion, gtfsFile));
+
+ feedSource.filename = " ";
+ Persistence.feedSources.replace(feedSource.id, feedSource);
+ assertEquals(gtfsFileName, deployment.getFeedSourceBundleFilename(feedVersion, gtfsFile));
+
+ feedSource.filename = null;
+ Persistence.feedSources.replace(feedSource.id, feedSource);
+ assertEquals(gtfsFileName, deployment.getFeedSourceBundleFilename(feedVersion, gtfsFile));
+
+ FeedVersion versionWithNullSource = new FeedVersion();
+ assertEquals(gtfsFileName, deployment.getFeedSourceBundleFilename(versionWithNullSource, gtfsFile));
+
+ Persistence.feedVersions.removeById(feedVersion.id);
+ Persistence.feedSources.removeById(feedSource.id);
+ }
+
/**
* Tests that the otp-runner manifest and user data for a graph build + run server instance can be generated
* properly
diff --git a/src/test/resources/com/conveyal/datatools/gtfs/mtc-feed-resource-test/agency.txt b/src/test/resources/com/conveyal/datatools/gtfs/mtc-feed-resource-test/agency.txt
new file mode 100644
index 000000000..665ceeec3
--- /dev/null
+++ b/src/test/resources/com/conveyal/datatools/gtfs/mtc-feed-resource-test/agency.txt
@@ -0,0 +1,2 @@
+agency_id,agency_name,agency_url,agency_timezone,agency_lang
+BART,Bay Area Rapid Transit,http://www.bart.gov,America/Los_Angeles,en
diff --git a/src/test/resources/com/conveyal/datatools/gtfs/mtc-feed-resource-test/stops.txt b/src/test/resources/com/conveyal/datatools/gtfs/mtc-feed-resource-test/stops.txt
new file mode 100644
index 000000000..d8997a83b
--- /dev/null
+++ b/src/test/resources/com/conveyal/datatools/gtfs/mtc-feed-resource-test/stops.txt
@@ -0,0 +1,183 @@
+"stop_id","stop_code","stop_name","stop_desc","stop_lat","stop_lon","zone_id","plc_url","location_type","parent_station"
+"LAKE","stop-code-1","Lake Merritt","","37.797322","-122.265247","LAKE","","0","place_LAKE"
+"FTVL","primary-1-stop-code-2","Fruitvale","","37.774841","-122.224081","FTVL","","0","place_FTVL"
+"COLS","secondary-1-stop-code-3","Coliseum","","37.753576","-122.196716","COLS","","0","place_COLS"
+"SANL","secondary-2-stop-code-4","San Leandro","","37.721784","-122.160740","SANL","","0","place_SANL"
+"BAYF","","Bay Fair","","37.696908","-122.126446","BAYF","","0","place_BAYF"
+"HAYW","","Hayward","","37.669699","-122.086958","HAYW","","0","place_HAYW"
+"SHAY","","South Hayward","","37.634340","-122.057182","SHAY","","0","place_SHAY"
+"UCTY","","Union City","","37.590735","-122.017248","UCTY","","0","place_UCTY"
+"FRMT","","Fremont","","37.557480","-121.976619","FRMT","","0","place_FRMT"
+"ROCK","","Rockridge","","37.844755","-122.251235","ROCK","","0","place_ROCK"
+"ORIN","","Orinda","","37.878481","-122.183667","ORIN","","0","place_ORIN"
+"LAFY","","Lafayette","","37.893183","-122.124620","LAFY","","0","place_LAFY"
+"WCRK","","Walnut Creek","","37.905791","-122.067327","WCRK","","0","place_WCRK"
+"PHIL","","Pleasant Hill / Contra Costa Centre","","37.928434","-122.055971","PHIL","","0","place_PHIL"
+"CONC","","Concord","","37.973757","-122.029072","CONC","","0","place_CONC"
+"NCON","","North Concord / Martinez","","38.003383","-122.024512","NCON","","0","place_NCON"
+"PITT","","Pittsburg / Bay Point","","38.018910","-121.944236","PITT","","0","place_PITT"
+"PCTR","","Pittsburg Center","","38.016847","-121.889062","PCTR","","0","place_PCTR"
+"ANTC","","Antioch","","37.995373","-121.780346","ANTC","","0","place_ANTC"
+"OAKL","","Oakland International Airport Station","","37.713268","-122.212200","OAKL","","0",""
+"12TH","","12th Street / Oakland City Center","","37.803482","-122.271630","12TH","","0","place_12TH"
+"19TH","","19th Street Oakland","","37.808078","-122.268758","19TH","","0","place_19TH"
+"MCAR","","MacArthur","","37.828803","-122.267105","MCAR","","0","place_MCAR"
+"CAST","","Castro Valley","","37.690737","-122.075601","CAST","","0","place_CAST"
+"WDUB","","West Dublin / Pleasanton","","37.699721","-121.928277","WDUB","","0","place_WDUB"
+"DUBL","","Dublin / Pleasanton","","37.701646","-121.899229","DUBL","","0","place_DUBL"
+"WOAK","","West Oakland","","37.804888","-122.295151","WOAK","","0","place_WOAK"
+"EMBR","","Embarcadero","","37.792762","-122.397037","EMBR","","0","place_EMBR"
+"MONT","","Montgomery Street","","37.789173","-122.401587","MONT","","0","place_MONT"
+"POWL","","Powell Street","","37.784606","-122.407331","POWL","","0","place_POWL"
+"CIVC","","Civic Center / UN Plaza","","37.779408","-122.413826","CIVC","","0","place_CIVC"
+"16TH","","16th Street / Mission","","37.765173","-122.419704","16TH","","0","place_16TH"
+"24TH","","24th Street / Mission","","37.752419","-122.418468","24TH","","0","place_24TH"
+"GLEN","","Glen Park","","37.733235","-122.433515","GLEN","","0","place_GLEN"
+"BALB","","Balboa Park","","37.721747","-122.447457","BALB","","0","place_BALB"
+"DALY","","Daly City","","37.706259","-122.468908","DALY","","0","place_DALY"
+"ASHB","","Ashby","","37.853072","-122.269771","ASHB","","0","place_ASHB"
+"DBRK","","Downtown Berkeley","","37.870110","-122.268109","DBRK","","0","place_DBRK"
+"NBRK","","North Berkeley","","37.874005","-122.283523","NBRK","","0","place_NBRK"
+"PLZA","","El Cerrito Plaza","","37.902610","-122.298920","PLZA","","0","place_PLZA"
+"DELN","","El Cerrito Del Norte","","37.925184","-122.316892","DELN","","0","place_DELN"
+"RICH","","Richmond","","37.936758","-122.353047","RICH","","0","place_RICH"
+"WARM","","Warm Springs / South Fremont","","37.502285","-121.939395","WARM","","0","place_WARM"
+"MLPT","","Milpitas","","37.410277","-121.891081","MLPT","","0","place_MLPT"
+"BERY","","Berryessa / North San Jose","","37.368473","-121.874681","BERY","","0","place_BERY"
+"COLM","","Colma","","37.684635","-122.466157","COLM","","0","place_COLM"
+"SSAN","","South San Francisco","","37.664462","-122.444211","SSAN","","0","place_SSAN"
+"SBRN","","San Bruno","","37.637730","-122.416326","SBRN","","0","place_SBRN"
+"MLBR","","Millbrae (Caltrain Transfer Platform)","","37.600237","-122.386757","MLBR","","0","place_MLBR"
+"SFIA","","San Francisco International Airport","","37.616091","-122.391954","SFIA","","0","place_SFIA"
+"place_LAKE","","Lake Merritt","","37.797322","-122.265247","","https://www.bart.gov/stations/lake","1",""
+"place_FTVL","","Fruitvale","","37.774841","-122.224081","","https://www.bart.gov/stations/ftvl","1",""
+"place_COLS","","Coliseum","","37.753576","-122.196716","","https://www.bart.gov/stations/cols","1",""
+"place_SANL","","San Leandro","","37.721784","-122.160740","","https://www.bart.gov/stations/sanl","1",""
+"place_BAYF","","Bay Fair","","37.696908","-122.126446","","https://www.bart.gov/stations/bayf","1",""
+"place_HAYW","","Hayward","","37.669699","-122.086958","","https://www.bart.gov/stations/hayw","1",""
+"place_SHAY","","South Hayward","","37.634340","-122.057182","","https://www.bart.gov/stations/shay","1",""
+"place_UCTY","","Union City","","37.590735","-122.017248","","https://www.bart.gov/stations/ucty","1",""
+"place_FRMT","","Fremont","","37.557480","-121.976619","","https://www.bart.gov/stations/frmt","1",""
+"place_ROCK","","Rockridge","","37.844755","-122.251235","","https://www.bart.gov/stations/rock","1",""
+"place_ORIN","","Orinda","","37.878481","-122.183667","","https://www.bart.gov/stations/orin","1",""
+"place_LAFY","","Lafayette","","37.893183","-122.124620","","https://www.bart.gov/stations/lafy","1",""
+"place_WCRK","","Walnut Creek","","37.905791","-122.067327","","https://www.bart.gov/stations/wcrk","1",""
+"place_PHIL","","Pleasant Hill / Contra Costa Centre","","37.928434","-122.055971","","https://www.bart.gov/stations/phil","1",""
+"place_CONC","","Concord","","37.973757","-122.029072","","https://www.bart.gov/stations/conc","1",""
+"place_NCON","","North Concord / Martinez","","38.003383","-122.024512","","https://www.bart.gov/stations/ncon","1",""
+"place_PITT","","Pittsburg / Bay Point","","38.018910","-121.944236","","https://www.bart.gov/stations/pitt","1",""
+"place_PCTR","","Pittsburg Center","","38.016847","-121.889062","","https://www.bart.gov/stations/pctr","1",""
+"place_ANTC","","Antioch","","37.995373","-121.780346","","https://www.bart.gov/stations/antc","1",""
+"place_12TH","","12th Street / Oakland City Center","","37.803482","-122.271630","","https://www.bart.gov/stations/12th","1",""
+"place_19TH","","19th Street Oakland","","37.808078","-122.268758","","https://www.bart.gov/stations/19th","1",""
+"place_MCAR","","MacArthur","","37.828803","-122.267105","","https://www.bart.gov/stations/mcar","1",""
+"place_CAST","","Castro Valley","","37.690737","-122.075601","","https://www.bart.gov/stations/cast","1",""
+"place_WDUB","","West Dublin / Pleasanton","","37.699721","-121.928277","","https://www.bart.gov/stations/wdub","1",""
+"place_DUBL","","Dublin / Pleasanton","","37.701646","-121.899229","","https://www.bart.gov/stations/dubl","1",""
+"place_WOAK","","West Oakland","","37.804888","-122.295151","","https://www.bart.gov/stations/woak","1",""
+"place_EMBR","","Embarcadero","","37.792762","-122.397037","","https://www.bart.gov/stations/embr","1",""
+"place_MONT","","Montgomery Street","","37.789173","-122.401587","","https://www.bart.gov/stations/mont","1",""
+"place_POWL","","Powell Street","","37.784606","-122.407331","","https://www.bart.gov/stations/powl","1",""
+"place_CIVC","","Civic Center / UN Plaza","","37.779408","-122.413826","","https://www.bart.gov/stations/civc","1",""
+"place_16TH","","16th Street / Mission","","37.765173","-122.419704","","https://www.bart.gov/stations/16th","1",""
+"place_24TH","","24th Street / Mission","","37.752419","-122.418468","","https://www.bart.gov/stations/24th","1",""
+"place_GLEN","","Glen Park","","37.733235","-122.433515","","https://www.bart.gov/stations/glen","1",""
+"place_BALB","","Balboa Park","","37.721747","-122.447457","","https://www.bart.gov/stations/balb","1",""
+"place_DALY","","Daly City","","37.706259","-122.468908","","https://www.bart.gov/stations/daly","1",""
+"place_ASHB","","Ashby","","37.853072","-122.269771","","https://www.bart.gov/stations/ashb","1",""
+"place_DBRK","","Downtown Berkeley","","37.870110","-122.268109","","https://www.bart.gov/stations/dbrk","1",""
+"place_NBRK","","North Berkeley","","37.874005","-122.283523","","https://www.bart.gov/stations/nbrk","1",""
+"place_PLZA","","El Cerrito Plaza","","37.902610","-122.298920","","https://www.bart.gov/stations/plza","1",""
+"place_DELN","","El Cerrito Del Norte","","37.925184","-122.316892","","https://www.bart.gov/stations/deln","1",""
+"place_RICH","","Richmond","","37.936758","-122.353047","","https://www.bart.gov/stations/rich","1",""
+"place_WARM","","Warm Springs / South Fremont","","37.502285","-121.939395","","https://www.bart.gov/stations/warm","1",""
+"place_MLPT","","Milpitas","","37.410277","-121.891081","","https://www.bart.gov/stations/mlpt","1",""
+"place_BERY","","Berryessa / North San Jose","","37.368473","-121.874681","","https://www.bart.gov/stations/bery","1",""
+"place_COLM","","Colma","","37.684635","-122.466157","","https://www.bart.gov/stations/colm","1",""
+"place_SSAN","","South San Francisco","","37.664462","-122.444211","","https://www.bart.gov/stations/ssan","1",""
+"place_SBRN","","San Bruno","","37.637730","-122.416326","","https://www.bart.gov/stations/sbrn","1",""
+"place_MLBR","","Millbrae","","37.600237","-122.386757","","https://www.bart.gov/stations/mlbr","1",""
+"place_SFIA","","San Francisco International Airport","","37.616091","-122.391954","","https://www.bart.gov/stations/sfia","1",""
+"12TH_1","","Enter/Exit: 13th Street @ Broadway (NW)","Broadway @ 13th Street (SW)","37.803899855","-122.271324","","","2","place_12TH"
+"12TH_2","","Enter/Exit: 13th Street @ Broadway (SE)","Broadway @ 13th Street (NE)","37.803315739","-122.271689","","","2","place_12TH"
+"12TH_3","","Enter/Exit: 12th Street @ Broadway (N)","Broadway @ 13th Street (NW)","37.802391559","-122.272269","","","2","place_12TH"
+"12TH_4","","Enter/Exit: Oakland City Center","Broadway @ 12th Street (NW)","37.803828455","-122.271861","","","2","place_12TH"
+"12TH_5","","Enter/Exit: Frank Ogawa Plaza","Broadway @ 12th Street (NE)","37.804768858","-122.271305","","","2","place_12TH"
+"12TH_6","","Enter/Exit: 12th Street @ Broadway","Broadway @ 14th Street (NW)","37.802483563","-122.272503","","","2","place_12TH"
+"12TH_7","","Enter/Exit: 14th Street @ Broadway","14th Street (NE)","37.80425355","-122.270789","","","2","place_12TH"
+"12TH_8","","Enter/Exit: 13th Street @ Broadway","13th Street (NE)","37.803407437","-122.271917","","","2","place_12TH"
+"16TH_1","","Enter/Exit : 16th Street @ Mission Street (NE)","16th Street @ Mission Street (NE)","37.765279205","-122.41933807","","","2","place_16TH"
+"16TH_2","","Enter/Exit : 16th Street @ Mission Street (SW)","16th Street @ Mission Street (SW)","37.764742121","-122.42004455","","","2","place_16TH"
+"19TH_1","","Enter/Exit : Broadway @ 19th Street (South East)","Broadway @ 19th Street (South East)","37.808416009","-122.26851746","","","2","place_19TH"
+"19TH_2","","Enter/Exit : Broadway @ 17th Street (West)","Broadway @ 17th Street (West)","37.807281706","-122.26978798","","","2","place_19TH"
+"19TH_3","","Enter/Exit : Broadway @ 17th Street (East)","Broadway @ 17th Street (East)","37.806916005","-122.26945171","","","2","place_19TH"
+"19TH_4","","Enter/Exit : Broadway @ 20th Street (West)","Broadway @ 20th Street (West)","37.808859155","-122.26849781","","","2","place_19TH"
+"19TH_5","","Enter/Exit : Broadway @ 19th Street (South West)","Broadway @ 19th Street (South West)","37.807522576","-122.26907679","","","2","place_19TH"
+"19TH_6","","Enter/Exit : 20th Street Entrance (East)","20th Street Entrance (East)","37.808975066","-122.26785938","","","2","place_19TH"
+"24TH_1","","Enter/Exit : 24th Street @ Mission Street (NE)","24th Street @ Mission Street (NE)","37.752479","-122.418098","","","2","place_24TH"
+"24TH_2","","Enter/Exit : 24th Street @ Mission Street (SW)","24th Street @ Mission Street (SW)","37.751936","-122.418814","","","2","place_24TH"
+"ASHB_1","","Enter/Exit : Pedestrian station entrance (WEST)","Pedestrian station entrance","37.853056","-122.269977","","","2","place_ASHB"
+"BALB_1","","Enter/Exit : Geneva Ave (West)","Geneva Ave (South)","37.721974","-122.447499","","","2","place_BALB"
+"BALB_2","","Enter/Exit : Geneva Ave (East)","Geneva Ave (North)","37.721954","-122.447314","","","2","place_BALB"
+"BAYF_1","","Enter/Exit : Pedestrian underpass (SW)","Pedestrian underpass (SW)","37.696868","-122.126976","","","2","place_BAYF"
+"BAYF_2","","Enter/Exit : Pedestrian underpass (NW)","Pedestrian underpass (NW)","37.697079","-122.12646","","","2","place_BAYF"
+"CAST_1","","Enter/Exit : Station entrance","Station entrance","37.691351","-122.075931","","","2","place_CAST"
+"CIVC_1","","Enter/Exit : UN Plaza Stairs","UN Plaza Stairs","37.779755","-122.414238","","","2","place_CIVC"
+"CIVC_2","","Enter/Exit : Market Street (E Middle)","Market Street (E Middle)","37.779519","-122.413575","","","2","place_CIVC"
+"CIVC_3","","Enter/Exit : Market Street @ 7th Street (NW)","Market Street @ 7th Street (NW)","37.780351","-122.412888","","","2","place_CIVC"
+"CIVC_4","","Enter/Exit : Market Street @ 7th Street (NE)","Market Street @ 7th Street (NE)","37.780132","-122.412787","","","2","place_CIVC"
+"CIVC_5","","Enter/Exit : Market Street @ Hyde Street (SW)","Market Street @ Hyde Street (SW)","37.778953","-122.414656","","","2","place_CIVC"
+"CIVC_6","","Enter/Exit : Market Street @ 8th Street (SE)","Market Street @ 8th Street (SE)","37.778811","-122.414464","","","2","place_CIVC"
+"CIVC_7","","Enter/Exit : Market Street @ Grove Street (SE)","Market Street @ Grove Street (SE)","37.778689","-122.415076","","","2","place_CIVC"
+"CIVC_8","","Enter/Exit : Market Street South of 8th Street","Market Street South of 8th Street","37.778396","-122.414934","","","2","place_CIVC"
+"COLM_2","","Enter/Exit : West Entrance","West Entrance","37.684757","-122.466056","","","2","place_COLM"
+"COLS_1","","Enter/Exit : Station entrance (South)","Station entrance (South)","37.753823","-122.197226","","","2","place_COLS"
+"COLS_2","","Enter/Exit : Station entrance (North)","Station entrance (North)","37.753998","-122.196777","","","2","place_COLS"
+"COLS_4","","Enter/Exit : Oakland Airport Connector entrance","Oakland Airport Connector entrance","37.75278901","-122.1957663","","","2","place_COLS"
+"CONC_1","","Enter/Exit : East entrance","East entrance","37.973601","-122.029093","","","2","place_CONC"
+"CONC_2","","Enter/Exit : West entrance","West entrance","37.97358","-122.029393","","","2","place_CONC"
+"DBRK_1","","Enter/Exit : Shattuck Ave @ Allston Way (West)","Shattuck Ave @ Allston Way (West)","37.869492","-122.268173","","","2","place_DBRK"
+"DBRK_2","","Enter/Exit : Shattuck Ave @ Allston Way (East)","Shattuck Ave @ Allston Way (East)","37.869703","-122.267754","","","2","place_DBRK"
+"DBRK_3","","Enter/Exit : Shattuck Ave @ Addison Street (West)","Shattuck Ave @ Addison Street (West)","37.871002","-122.268361","","","2","place_DBRK"
+"DBRK_4","","Enter/Exit : Shattuck Ave @ Addison Street (East)","Shattuck Ave @ Addison Street (East)","37.871009","-122.268129","","","2","place_DBRK"
+"EMBR_1","","Enter/Exit : Market @ Spear Street (SE)","Market @ Spear Street (SE)","37.793523","-122.395835","","","2","place_EMBR"
+"EMBR_2","","Enter/Exit : Market @ Drumm Street (NE)","Market @ Drumm Street (NE)","37.793669","-122.396016","","","2","place_EMBR"
+"EMBR_3","","Enter/Exit : Market @ Davis Street (North)","Market @ Davis Street (North)","37.792886","-122.39701","","","2","place_EMBR"
+"EMBR_4","","Enter/Exit : Market Street @ Main Street (South)","Market Street @ Main Street (South)","37.792892","-122.396619","","","2","place_EMBR"
+"EMBR_5","","Enter/Exit : Market Street @ Front Street (NW)","Market Street @ Front Street (NW)","37.792198","-122.397884","","","2","place_EMBR"
+"EMBR_6","","Enter/Exit : Market Street @ Fremont Street (SW)","Market Street @ Fremont Street (SW)","37.792072","-122.397644","","","2","place_EMBR"
+"FTVL_2","","Enter/Exit : Station entrance (North)","Station entrance (North)","37.775038","-122.224196","","","2","place_FTVL"
+"FTVL_3","","Enter/Exit : Station entrance (South)","Station entrance (South)","37.774847","-122.22437","","","2","place_FTVL"
+"FRMT_1","","Enter/Exit : Station entrance (North)","Station entrance (North)","37.557508","-121.976412","","","2","place_FRMT"
+"FRMT_2","","Enter/Exit : Station entrance (South)","Station entrance (South)","37.557397","-121.976701","","","2","place_FRMT"
+"GLEN_1","","Enter/Exit : Station entrance","Station entrance","37.733176","-122.43387","","","2","place_GLEN"
+"LAKE_1","","Enter/Exit : Oak Street @ 8th Street (SW)","Oak Street @ 8th Street (SW)","37.797109","-122.265574","","","2","place_LAKE"
+"LAKE_2","","Enter/Exit : Oak Street @ 8th Street (SE)","Oak Street @ 8th Street (SE)","37.797067","-122.265151","","","2","place_LAKE"
+"LAKE_3","","Enter/Exit : Oak Street @ 9th Street (NE)","Oak Street @ 9th Street (NE)","37.797384","-122.264819","","","2","place_LAKE"
+"LAKE_4","","Enter/Exit : 8th Street @ Fallon St (East)","Oak Street@8th St €","37.796765","-122.264276","","","2","place_LAKE"
+"MLBR_5","","Enter/Exit : Station entrance (SW)","Station entrance (SW)","37.599939","-122.387135","","","2","place_MLBR"
+"MLBR_2","","Enter/Exit : Station entrance (NE)","Station entrnce (NE)","37.600452","-122.386433","","","2","place_MLBR"
+"MONT_1","","Enter/Exit : Market @ Post Street (NW)","Market @ Post Street (NW)","37.788924306","-122.40218957","","","2","place_MONT"
+"MONT_2","","Enter/Exit : Market (SW)","Market (SW)","37.788512","-122.402155","","","2","place_MONT"
+"MONT_3","","Enter/Exit : Market @ Montgomery Street (NE)","Market @ Montgomery Street (NE)","37.789179","-122.40175","","","2","place_MONT"
+"MONT_4","","Enter/Exit : Market @ 2nd Street (South)","Market @ 2nd Street (South)","37.789407","-122.401038","","","2","place_MONT"
+"MONT_5","","Enter/Exit : Sutter Street","Sutter Street","37.78995","-122.400711","","","2","place_MONT"
+"MONT_6","","Enter/Exit : Sansome Street","Sansome Street","37.790503","-122.40069","","","2","place_MONT"
+"MONT_7","","Enter/Exit : Market Street (SE)","Market Street (SE)","37.789816","-122.400508","","","2","place_MONT"
+"POWL_1","","Enter/Exit : Market Street @ 4th Street (NE)","Market Street @ 4th Street (NE)","37.786037872","-122.40566443","","","2","place_POWL"
+"POWL_2","","Enter/Exit : Market Street @ 4th Street (SE)","Market Street @ 4th Street (SE)","37.78590866","-122.40552017","","","2","place_POWL"
+"POWL_3","","Enter/Exit : Stockton Street","Stockton Street","37.785864","-122.406341","","","2","place_POWL"
+"POWL_4","","Enter/Exit : Market Street @ Ellis Street","Market Street @ Ellis Street","37.785432","-122.406427","","","2","place_POWL"
+"POWL_5","","Enter/Exit : Market Street @ 4th Street (South)","Market Street @ 4th Street (South)","37.785256","-122.406301","","","2","place_POWL"
+"POWL_6","","Enter/Exit : Market Street @ 5th Street (NE)","Market Street @ 5th Street (NE)","37.78438523","-122.40742314","","","2","place_POWL"
+"POWL_7","","Enter/Exit : Escalator to Market Street and Cable Cars","Escalator to Market Street and Cable Cars","37.784562","-122.40777","","","2","place_POWL"
+"POWL_8","","Enter/Exit : Market Street @ 5th Street","Market Street @ 5th Street","37.783715","-122.408259","","","2","place_POWL"
+"SBRN_3","","Enter/Exit : Station entrance (East)","Station entrance (East)","37.637794217","-122.41616622","","","2","place_SBRN"
+"SBRN_4","","Enter/Exit : Station entrance (West)","Station entrance (West)","37.63774422","-122.41641365","","","2","place_SBRN"
+"SFIA_1","","Enter/Exit : Station entrance","Station entrance","37.615911","-122.392665","","","2","place_SFIA"
+"SHAY_3","","Enter/Exit : Station entrance (East)","Station entrance (East)","37.634562","-122.056826","","","2","place_SHAY"
+"PITT_1","","Enter/Exit : Station entrance (South)","Station entrance (South)","38.018237","-121.945339","","","2","place_PITT"
+"DELN_1","","Enter/Exit : Station entrance (North)","Station entrance (North)","37.925323","-122.317023","","","2","place_DELN"
+"DELN_2","","Enter/Exit : Station entrance (South)","Station entrance (South)","37.924976","-122.316747","","","2","place_DELN"
+"MLPT_1","","Enter/Exit: Station entrance","Station entrance","37.410155","-121.890683","","","2","place_MLPT"
+"BERY_1","","Enter/Exit: Station entrance","Station entrance","37.368488","-121.874458","","","2","place_BERY"
diff --git a/src/test/resources/com/conveyal/datatools/mtc-rtd-mock-responses/__files/rtdGetResponse.json b/src/test/resources/com/conveyal/datatools/mtc-rtd-mock-responses/__files/rtdGetResponse.json
index 018834197..d067b4873 100644
--- a/src/test/resources/com/conveyal/datatools/mtc-rtd-mock-responses/__files/rtdGetResponse.json
+++ b/src/test/resources/com/conveyal/datatools/mtc-rtd-mock-responses/__files/rtdGetResponse.json
@@ -1 +1 @@
-{"AgencyId":"DE","AgencyName":"Dumbarton Express Consortium","AgencyPhone":null,"RttAgencyName":"Dumbarton Express","RttEnabled":"Y","AgencyShortName":"Dumbarton","AddressLat":null,"AddressLon":null,"DefaultRouteType":null,"CarrierStatus":null,"AgencyAddress":"AC Transit (administrator of the Dumbarton Express)","AgencyEmail":"new@email.example.com","AgencyUrl":"https://dumbartonexpress.com","AgencyFareUrl":"","EditedBy":"binh.dam@ibigroup.com","EditedDate":"2021-10-29T16:06:07.914796"}
\ No newline at end of file
+{"AgencyId":"DE","AgencyName":"Dumbarton Express Consortium","AgencyPhone":null,"RttAgencyName":"Dumbarton Express","RttEnabled":"Y","AgencyShortName":"Dumbarton","AddressLat":null,"AddressLon":null,"DefaultRouteType":null,"CarrierStatus":null,"AgencyAddress":"AC Transit (administrator of the Dumbarton Express)","AgencyEmail":"new@email.example.com","AgencyUrl":"https://dumbartonexpress.com","AgencyFareUrl":"","EditedBy":"binh.dam@ibigroup.com","EditedDate":"2021-10-29T16:06:07.914796","PrimaryPrefix":"primary-1","SecondaryPrefixes":"secondary-1,secondary-2"}
\ No newline at end of file