diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml
new file mode 100644
index 00000000000..01bff2cd9f1
--- /dev/null
+++ b/.github/workflows/integration-test.yml
@@ -0,0 +1,28 @@
+name: Integration test
+
+on:
+ pull_request:
+ types:
+ - labeled
+ - synchronize
+env:
+ MAVEN_ARGS: "--no-transfer-progress -Dstyle.color=always"
+
+jobs:
+ run:
+ runs-on: ubuntu-latest
+ if: github.repository_owner == 'opentripplanner' && contains( github.event.pull_request.labels.*.name, 'Integration Test' )
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up JDK 21
+ uses: actions/setup-java@v4
+ with:
+ java-version: 21
+ distribution: temurin
+ cache: maven
+
+
+ - name: Run integration tests
+ run: mvn test-compile failsafe:integration-test -PprettierSkip
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 5ce18e0196f..18427e601da 100644
--- a/pom.xml
+++ b/pom.xml
@@ -104,6 +104,7 @@
gtfs-realtime-protobuf
application
otp-shaded
+ test/integration
diff --git a/renovate.json5 b/renovate.json5
index e0bf1c43889..71123c4a4c9 100644
--- a/renovate.json5
+++ b/renovate.json5
@@ -46,6 +46,7 @@
"org.mockito:mockito-core",
"com.tngtech.archunit:archunit",
"org.apache.maven.plugins:maven-surefire-plugin",
+ "org.apache.maven.plugins:maven-failsafe-plugin ",
"me.fabriciorby:maven-surefire-junit5-tree-reporter",
"com.google.truth:truth",
"org.jacoco:jacoco-maven-plugin", // coverage plugin
@@ -163,12 +164,20 @@
"description": "give some projects time to publish a changelog before opening the PR",
"matchPackageNames": [
"com.google.dagger:{/,}**",
- "org.apache.httpcomponents.client5:httpclient5"
],
"matchUpdateTypes": ["major", "minor"],
"minimumReleaseAge": "1 week",
"schedule": "on the 13th through 14th day of the month"
},
+ {
+ "description": "Apache HTTP client",
+ "matchPackageNames": [
+ "org.apache.httpcomponents.client5:httpclient5"
+ ],
+ "minimumReleaseAge": "1 week",
+ "changelogUrl": "https://github.com/apache/httpcomponents-client/blob/master/RELEASE_NOTES.txt",
+ "addLabels": ["Integration Test"]
+ },
{
"groupName": "Jackson non-patch",
"matchPackageNames": [
diff --git a/test/integration/pom.xml b/test/integration/pom.xml
new file mode 100644
index 00000000000..2ec845fd6be
--- /dev/null
+++ b/test/integration/pom.xml
@@ -0,0 +1,82 @@
+
+
+ 4.0.0
+
+ org.opentripplanner
+ otp-root
+ 2.8.0-SNAPSHOT
+ ../../pom.xml
+
+
+ integration-test
+
+
+
+ ${project.groupId}
+ application
+ ${project.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit.version}
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 3.5.3
+
+
+ **/*Test.java
+
+
+ true
+
+ plain
+
+ true
+ true
+ true
+ true
+ false
+ true
+ true
+ false
+ UNICODE
+
+
+
+
+ me.fabriciorby
+ maven-surefire-junit5-tree-reporter
+ 1.4.0
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/integration/src/test/java/org/opentripplanner/OtpHttpClientTest.java b/test/integration/src/test/java/org/opentripplanner/OtpHttpClientTest.java
new file mode 100644
index 00000000000..39bdaf758d6
--- /dev/null
+++ b/test/integration/src/test/java/org/opentripplanner/OtpHttpClientTest.java
@@ -0,0 +1,44 @@
+package org.opentripplanner;
+
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import jakarta.ws.rs.core.UriBuilder;
+import java.io.IOException;
+import java.time.Duration;
+import java.util.Map;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.opentripplanner.framework.io.OtpHttpClient;
+import org.opentripplanner.framework.io.OtpHttpClientFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This integration test makes sure that Apache HTTP client still works with important hosts and
+ * HTTP servers.
+ */
+class OtpHttpClientTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(OtpHttpClientTest.class);
+ private static final OtpHttpClient OTP_HTTP_CLIENT = new OtpHttpClientFactory().create(LOG);
+
+ @ParameterizedTest
+ @ValueSource(
+ strings = {
+ // a few entur URLs
+ "https://api.entur.io/mobility/v2/gbfs/",
+ "https://storage.googleapis.com/marduk-production/outbound/gtfs/rb_sjn-aggregated-gtfs.zip",
+ // Apache HTTP Client broke handling of S3 SSL certificates previously
+ "https://s3.amazonaws.com/kcm-alerts-realtime-prod/tripupdates.pb",
+ }
+ )
+ void httpGetRequest(String url) throws IOException {
+ var uri = UriBuilder.fromUri(url).build();
+
+ var stream = OTP_HTTP_CLIENT.getAsInputStream(uri, Duration.ofSeconds(30), Map.of());
+ var bytes = IOUtils.toByteArray(stream);
+
+ assertNotEquals(0, bytes.length, "Empty response body for %s".formatted(url));
+ }
+}