diff --git a/pom.xml b/pom.xml
index 86741e5b4..f121c1fc5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,6 +34,7 @@
4.0.12
4.35.0
24.0.0
+ 3.13.2
@@ -205,9 +206,9 @@
- com.squareup.okhttp3
- mockwebserver
- ${okhttpVersion}
+ org.wiremock
+ wiremock
+ ${wiremock.version}
test
diff --git a/src/main/groovy/com/cloudogu/gitops/dependencyinjection/HttpClientFactory.groovy b/src/main/groovy/com/cloudogu/gitops/dependencyinjection/HttpClientFactory.groovy
index 585a1fa85..97db911da 100644
--- a/src/main/groovy/com/cloudogu/gitops/dependencyinjection/HttpClientFactory.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/dependencyinjection/HttpClientFactory.groovy
@@ -14,6 +14,7 @@ import okhttp3.logging.HttpLoggingInterceptor
import org.jetbrains.annotations.NotNull
import org.slf4j.LoggerFactory
+import javax.net.ssl.HostnameVerifier
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager
@@ -35,6 +36,8 @@ class HttpClientFactory {
builder.sslSocketFactory(context.socketFactory, context.trustManager)
}
+ builder.hostnameVerifier({ hostname, session -> true } as HostnameVerifier)
+
return builder.build()
}
diff --git a/src/main/groovy/com/cloudogu/gitops/jenkins/JenkinsApiClient.groovy b/src/main/groovy/com/cloudogu/gitops/jenkins/JenkinsApiClient.groovy
index 6d1b1606f..bc5ae8d94 100644
--- a/src/main/groovy/com/cloudogu/gitops/jenkins/JenkinsApiClient.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/jenkins/JenkinsApiClient.groovy
@@ -22,7 +22,14 @@ class JenkinsApiClient {
Config config,
@Named("jenkins") OkHttpClient client
) {
- this.client = client
+
+ if (config.application.insecure) {
+ this.client = client.newBuilder()
+ .hostnameVerifier({ hostname, session -> true })
+ .build()
+ } else {
+ this.client = client
+ }
this.config = config
}
diff --git a/src/main/groovy/com/cloudogu/gitops/okhttp/RetryInterceptor.groovy b/src/main/groovy/com/cloudogu/gitops/okhttp/RetryInterceptor.groovy
index 8a42c0b69..32124d1c1 100644
--- a/src/main/groovy/com/cloudogu/gitops/okhttp/RetryInterceptor.groovy
+++ b/src/main/groovy/com/cloudogu/gitops/okhttp/RetryInterceptor.groovy
@@ -15,7 +15,7 @@ class RetryInterceptor implements Interceptor {
private int retries
private int waitPeriodInMs
- // Number of retries in uncommonly high, because we might have to outlive a unexpected Jenkins restart
+ // Number of retries in uncommonly high, because we might have to outlive a unexpected Jenkins restart
RetryInterceptor(int retries = 180, int waitPeriodInMs = 2000) {
this.waitPeriodInMs = waitPeriodInMs
this.retries = retries
@@ -23,38 +23,57 @@ class RetryInterceptor implements Interceptor {
@Override
Response intercept(@NotNull Chain chain) throws IOException {
- def i = 0;
+ def i = 0
Response response = null
+ IOException lastException = null
+
do {
try {
response = chain.proceed(chain.request())
+
if (response.code() !in getStatusCodesToRetry()) {
- break
+ // Success or non-retriable error - return the response
+ return response
}
log.trace("Retry HTTP Request to {} due to status code {}", chain.request().url().toString(), response.code())
+ response.close()
} catch (SocketTimeoutException e) {
- // fallthrough to retry
+ lastException = e
log.trace("Retry HTTP Request to {} due to SocketTimeoutException: {}", chain.request().url().toString(), e.message)
}
- response?.close()
- Thread.sleep(waitPeriodInMs)
+
+ // Wait before next retry (but not after the last attempt)
+ if (i < retries) {
+ Thread.sleep(waitPeriodInMs)
+ }
++i
- } while(i < retries)
- return response
+ } while(i <= retries)
+
+ // If we got here, all retries failed
+ if (response != null) {
+ // Return the last failed response
+ return response
+ } else if (lastException != null) {
+ // All attempts resulted in timeout - throw the last exception
+ throw lastException
+ } else {
+ // This should never happen, but as a safety net
+ throw new IOException("Request failed after ${retries} retries")
+ }
}
private List getStatusCodesToRetry() {
return [
- // list of codes if from curl --retry
- 408, // Request Timeout
- 429, // Too Many Requests
- 500, // Internal Server Error
- 502, // Bad Gateway
- 503, // Service Unavailable
- 504, // Gateway Timeout
+ // list of codes from curl --retry
+ 408, // Request Timeout
+ 429, // Too Many Requests
+ 500, // Internal Server Error
+ 502, // Bad Gateway
+ 503, // Service Unavailable
+ 504, // Gateway Timeout
]
}
-}
+}
\ No newline at end of file
diff --git a/src/test/groovy/com/cloudogu/gitops/common/MockWebServerHttpsFactory.groovy b/src/test/groovy/com/cloudogu/gitops/common/MockWebServerHttpsFactory.groovy
deleted file mode 100644
index a1e782f53..000000000
--- a/src/test/groovy/com/cloudogu/gitops/common/MockWebServerHttpsFactory.groovy
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.cloudogu.gitops.common
-
-import okhttp3.tls.HandshakeCertificates
-import okhttp3.tls.HeldCertificate
-
-class MockWebServerHttpsFactory {
- static HandshakeCertificates createSocketFactory() {
- def heldCertificate = new HeldCertificate.Builder()
- .addSubjectAlternativeName(InetAddress.getByName('localhost').getCanonicalHostName())
- .build()
-
- return new HandshakeCertificates.Builder()
- .heldCertificate(heldCertificate)
- .build()
- }
-}
diff --git a/src/test/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/UsersApiTest.groovy b/src/test/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/UsersApiTest.groovy
index 07b698d34..cf35de66b 100644
--- a/src/test/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/UsersApiTest.groovy
+++ b/src/test/groovy/com/cloudogu/gitops/git/providers/scmmanager/api/UsersApiTest.groovy
@@ -1,59 +1,66 @@
package com.cloudogu.gitops.git.providers.scmmanager.api
-import com.cloudogu.gitops.common.MockWebServerHttpsFactory
import com.cloudogu.gitops.config.Credentials
-import okhttp3.mockwebserver.MockResponse
-import okhttp3.mockwebserver.MockWebServer
-import org.junit.jupiter.api.AfterEach
+import com.github.tomakehurst.wiremock.junit5.WireMockExtension
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
import javax.net.ssl.SSLHandshakeException
+import static com.github.tomakehurst.wiremock.client.WireMock.*
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig
import static groovy.test.GroovyAssert.shouldFail
import static org.assertj.core.api.Assertions.assertThat
class UsersApiTest {
- private MockWebServer webServer = new MockWebServer()
- private Credentials credentials = new Credentials("user", "pass")
- @AfterEach
- void tearDown() {
- webServer.shutdown()
- }
+ @RegisterExtension
+ static WireMockExtension wireMock = WireMockExtension.newInstance()
+ .options(wireMockConfig()
+ .dynamicPort()
+ .dynamicHttpsPort())
+ .build()
+
+ private Credentials credentials = new Credentials("user", "pass")
@Test
void 'allows self-signed certificates when using insecure option'() {
- webServer.useHttps(MockWebServerHttpsFactory.createSocketFactory().sslSocketFactory(), false)
-
- def api = usersApi(true)
- webServer.enqueue(new MockResponse().setResponseCode(204))
+ wireMock.stubFor(delete(urlPathEqualTo("/scm/api/v2/users/test-user"))
+ .willReturn(aResponse().withStatus(204)))
+ def api = usersApi(true, true) // insecure=true, useHttps=true
def resp = api.delete('test-user').execute()
assertThat(resp.isSuccessful()).isTrue()
- assertThat(webServer.requestCount).isEqualTo(1)
+ wireMock.verify(1, deleteRequestedFor(urlPathEqualTo("/scm/api/v2/users/test-user")))
}
@Test
void 'does not allow self-signed certificates by default'() {
- webServer.useHttps(MockWebServerHttpsFactory.createSocketFactory().sslSocketFactory(), false)
+ wireMock.stubFor(delete(urlPathEqualTo("/scm/api/v2/users/test-user"))
+ .willReturn(aResponse().withStatus(204)))
- def api = usersApi(false)
+ def api = usersApi(false, true) // insecure=false, useHttps=true
shouldFail(SSLHandshakeException) {
api.delete('test-user').execute()
}
- assertThat(webServer.requestCount).isEqualTo(0)
- }
+ wireMock.verify(0, deleteRequestedFor(urlPathEqualTo("/scm/api/v2/users/test-user")))
+ }
- private UsersApi usersApi(boolean insecure) {
- def client = new ScmManagerApiClient(apiBaseUrl(), credentials, insecure)
+ private UsersApi usersApi(boolean insecure, boolean useHttps = false) {
+ def client = new ScmManagerApiClient(apiBaseUrl(useHttps), credentials, insecure)
return client.usersApi()
}
- private String apiBaseUrl() {
- return "${webServer.url('scm')}/api/"
+ private String apiBaseUrl(boolean useHttps) {
+ if (useHttps) {
+ // Use the proper HTTPS port from WireMock
+ def httpsPort = wireMock.httpsPort
+ return "https://localhost:${httpsPort}/scm/api/"
+ } else {
+ return "${wireMock.baseUrl()}/scm/api/"
+ }
}
-
}
\ No newline at end of file
diff --git a/src/test/groovy/com/cloudogu/gitops/jenkins/JenkinsApiClientTest.groovy b/src/test/groovy/com/cloudogu/gitops/jenkins/JenkinsApiClientTest.groovy
index b83f4c0e6..c8fb6de73 100644
--- a/src/test/groovy/com/cloudogu/gitops/jenkins/JenkinsApiClientTest.groovy
+++ b/src/test/groovy/com/cloudogu/gitops/jenkins/JenkinsApiClientTest.groovy
@@ -1,221 +1,275 @@
package com.cloudogu.gitops.jenkins
-import com.cloudogu.gitops.common.MockWebServerHttpsFactory
import com.cloudogu.gitops.config.Config
-
+import com.github.tomakehurst.wiremock.junit5.WireMockExtension
import io.micronaut.context.ApplicationContext
import okhttp3.FormBody
import okhttp3.JavaNetCookieJar
import okhttp3.OkHttpClient
-import okhttp3.mockwebserver.MockResponse
-import okhttp3.mockwebserver.MockWebServer
-import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
-import java.nio.charset.Charset
+import javax.net.ssl.SSLContext
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.TrustManager
+import javax.net.ssl.X509TrustManager
+import java.security.SecureRandom
+import java.security.cert.X509Certificate
+import static com.github.tomakehurst.wiremock.client.WireMock.*
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig
import static groovy.test.GroovyAssert.shouldFail
import static org.assertj.core.api.Assertions.assertThat
class JenkinsApiClientTest {
- private MockWebServer webServer = new MockWebServer()
- @AfterEach
- void tearDown() {
- webServer.shutdown()
- }
+ @RegisterExtension
+ static WireMockExtension wireMock = WireMockExtension.newInstance()
+ .options(wireMockConfig()
+ .dynamicPort()
+ .dynamicHttpsPort())
+ .build()
@Test
void 'runs script with crumb'() {
- webServer.setDispatcher { request ->
- switch (request.path) {
- case "/jenkins/crumbIssuer/api/json":
- return new MockResponse().setBody('{"crumb": "the-crumb", "crumbRequestField": "Jenkins-Crumb"}')
- case "/jenkins/scriptText":
- return new MockResponse().setBody("ok")
- default:
- return new MockResponse().setStatus("404")
- }
- }
- webServer.start()
+ wireMock.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withBody('{"crumb": "the-crumb", "crumbRequestField": "Jenkins-Crumb"}')))
+
+ wireMock.stubFor(post(urlPathEqualTo("/jenkins/scriptText"))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withBody("ok")))
- def httpClient = new OkHttpClient.Builder().cookieJar(new JavaNetCookieJar(new CookieManager())).build()
+ def httpClient = getUnsafeOkHttpClient().newBuilder().cookieJar(new JavaNetCookieJar(new CookieManager())).build()
def apiClient = new JenkinsApiClient(
- new Config(jenkins: new Config.JenkinsSchema(url: webServer.url("jenkins").toString())),
+ new Config(jenkins: new Config.JenkinsSchema(url: "${wireMock.baseUrl()}/jenkins")),
httpClient)
def result = apiClient.runScript("println('ok')")
assertThat(result).isEqualTo("ok")
- def crumbRequest = webServer.takeRequest()
- assertThat(crumbRequest.path).isEqualTo("/jenkins/crumbIssuer/api/json")
- assertThat(crumbRequest.getHeader('Authorization')).startsWith("Basic ")
+ wireMock.verify(1, getRequestedFor(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .withHeader("Authorization", matching("Basic .*")))
- def runScriptRequest = webServer.takeRequest()
- assertThat(runScriptRequest.path).isEqualTo("/jenkins/scriptText")
- assertThat(runScriptRequest.getHeader('Authorization')).startsWith("Basic ")
- assertThat(runScriptRequest.getHeader('Jenkins-Crumb')).startsWith("the-crumb")
+ wireMock.verify(1, postRequestedFor(urlPathEqualTo("/jenkins/scriptText"))
+ .withHeader("Authorization", matching("Basic .*"))
+ .withHeader("Jenkins-Crumb", equalTo("the-crumb")))
}
@Test
void 'adds crumb to sendRequest'() {
- webServer.enqueue(new MockResponse().setBody('{"crumb": "the-crumb", "crumbRequestField": "Jenkins-Crumb"}'))
- webServer.enqueue(new MockResponse())
+ wireMock.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withBody('{"crumb": "the-crumb", "crumbRequestField": "Jenkins-Crumb"}')))
+
+ wireMock.stubFor(post(urlPathEqualTo("/jenkins/foobar"))
+ .willReturn(aResponse().withStatus(200)))
def client = new JenkinsApiClient(
- new Config(jenkins: new Config.JenkinsSchema(url: webServer.url("jenkins").toString())),
- new OkHttpClient())
+ new Config(jenkins: new Config.JenkinsSchema(url: "${wireMock.baseUrl()}/jenkins")),
+ getUnsafeOkHttpClient())
client.postRequestWithCrumb("foobar")
- assertThat(webServer.requestCount).isEqualTo(2)
- webServer.takeRequest() // crumb
- def request = webServer.takeRequest()
- assertThat(request.method).isEqualTo("POST")
- assertThat(request.headers.get("Jenkins-Crumb")).isEqualTo("the-crumb")
+ wireMock.verify(1, getRequestedFor(urlPathEqualTo("/jenkins/crumbIssuer/api/json")))
+ wireMock.verify(1, postRequestedFor(urlPathEqualTo("/jenkins/foobar"))
+ .withHeader("Jenkins-Crumb", equalTo("the-crumb")))
}
@Test
void 'adds crumb and post data to sendRequest'() {
- webServer.enqueue(new MockResponse().setBody('{"crumb": "the-crumb", "crumbRequestField": "Jenkins-Crumb"}'))
- webServer.enqueue(new MockResponse())
+ wireMock.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withBody('{"crumb": "the-crumb", "crumbRequestField": "Jenkins-Crumb"}')))
+
+ wireMock.stubFor(post(urlPathEqualTo("/jenkins/foobar"))
+ .willReturn(aResponse().withStatus(200)))
def client = new JenkinsApiClient(
- new Config(jenkins: new Config.JenkinsSchema(url: webServer.url("jenkins").toString())),
- new OkHttpClient())
+ new Config(jenkins: new Config.JenkinsSchema(url: "${wireMock.baseUrl()}/jenkins")),
+ getUnsafeOkHttpClient())
client.postRequestWithCrumb("foobar", new FormBody.Builder().add('key', 'value with spaces').build())
- assertThat(webServer.requestCount).isEqualTo(2)
- webServer.takeRequest() // crumb
- def request = webServer.takeRequest()
- assertThat(request.method).isEqualTo("POST")
- assertThat(request.headers.get("Jenkins-Crumb")).isEqualTo("the-crumb")
- assertThat(request.body.readString(Charset.defaultCharset())).isEqualTo("key=value%20with%20spaces")
+ wireMock.verify(1, getRequestedFor(urlPathEqualTo("/jenkins/crumbIssuer/api/json")))
+ wireMock.verify(1, postRequestedFor(urlPathEqualTo("/jenkins/foobar"))
+ .withHeader("Jenkins-Crumb", equalTo("the-crumb"))
+ .withRequestBody(equalTo("key=value%20with%20spaces")))
}
@Test
void 'allows self-signed certificates when using insecure'() {
- webServer.useHttps(MockWebServerHttpsFactory.createSocketFactory().sslSocketFactory(), false)
- webServer.setDispatcher { request ->
- switch (request.path) {
- case "/jenkins/crumbIssuer/api/json":
- return new MockResponse().setBody('{"crumb": "the-crumb", "crumbRequestField": "Jenkins-Crumb"}')
- case "/jenkins/scriptText":
- return new MockResponse().setBody("ok")
- default:
- return new MockResponse().setStatus("404")
- }
- }
- webServer.start()
+ wireMock.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withBody('{"crumb": "the-crumb", "crumbRequestField": "Jenkins-Crumb"}')))
+
+ wireMock.stubFor(post(urlPathEqualTo("/jenkins/scriptText"))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withBody("ok")))
def apiClient = ApplicationContext.run()
.registerSingleton(new Config(
application: new Config.ApplicationSchema(
insecure: true),
jenkins: new Config.JenkinsSchema(
- url: webServer.url("jenkins"))
+ url: "${wireMock.baseUrl().replace('http://', 'https://')}/jenkins")
))
.getBean(JenkinsApiClient)
def result = apiClient.runScript("println('ok')")
assertThat(result).isEqualTo("ok")
- def crumbRequest = webServer.takeRequest()
- assertThat(crumbRequest.path).isEqualTo("/jenkins/crumbIssuer/api/json")
- assertThat(crumbRequest.getHeader('Authorization')).startsWith("Basic ")
+ wireMock.verify(1, getRequestedFor(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .withHeader("Authorization", matching("Basic .*")))
- def runScriptRequest = webServer.takeRequest()
- assertThat(runScriptRequest.path).isEqualTo("/jenkins/scriptText")
- assertThat(runScriptRequest.getHeader('Authorization')).startsWith("Basic ")
- assertThat(runScriptRequest.getHeader('Jenkins-Crumb')).startsWith("the-crumb")
+ wireMock.verify(1, postRequestedFor(urlPathEqualTo("/jenkins/scriptText"))
+ .withHeader("Authorization", matching("Basic .*"))
+ .withHeader("Jenkins-Crumb", equalTo("the-crumb")))
}
@Test
void 'retries on invalid crumb'() {
- Queue crumbQueue = new ArrayDeque()
- crumbQueue.add("the-invalid-crumb")
- crumbQueue.add("the-second-crumb")
- webServer.setDispatcher { request ->
- switch (request.path) {
- case "/jenkins/crumbIssuer/api/json":
- return new MockResponse().setBody('{"crumb": "'+crumbQueue.poll()+'", "crumbRequestField": "Jenkins-Crumb"}')
- case "/jenkins/scriptText":
- def isInvalidCrumb = request.getHeader('Jenkins-Crumb') == 'the-invalid-crumb'
- def body = !isInvalidCrumb ? 'ok' : '{"servlet":"Stapler", "message":"No valid crumb was included in the request", "url":"/scriptText", "status":"403"}'
- return new MockResponse().setBody(body).setResponseCode(isInvalidCrumb ? 403 : 200)
- default:
- return new MockResponse().setStatus("404")
- }
- }
- webServer.start()
+ wireMock.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .inScenario("Invalid Crumb Retry")
+ .whenScenarioStateIs("Started")
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withBody('{"crumb": "the-invalid-crumb", "crumbRequestField": "Jenkins-Crumb"}'))
+ .willSetStateTo("First Crumb"))
+
+ wireMock.stubFor(post(urlPathEqualTo("/jenkins/scriptText"))
+ .inScenario("Invalid Crumb Retry")
+ .whenScenarioStateIs("First Crumb")
+ .withHeader("Jenkins-Crumb", equalTo("the-invalid-crumb"))
+ .willReturn(aResponse()
+ .withStatus(403)
+ .withBody('{"servlet":"Stapler", "message":"No valid crumb was included in the request", "url":"/scriptText", "status":"403"}'))
+ .willSetStateTo("Invalid Crumb Response"))
+
+ wireMock.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .inScenario("Invalid Crumb Retry")
+ .whenScenarioStateIs("Invalid Crumb Response")
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withBody('{"crumb": "the-second-crumb", "crumbRequestField": "Jenkins-Crumb"}'))
+ .willSetStateTo("Second Crumb"))
- def httpClient = new OkHttpClient()
+ wireMock.stubFor(post(urlPathEqualTo("/jenkins/scriptText"))
+ .inScenario("Invalid Crumb Retry")
+ .whenScenarioStateIs("Second Crumb")
+ .withHeader("Jenkins-Crumb", equalTo("the-second-crumb"))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withBody("ok")))
+
+ def httpClient = getUnsafeOkHttpClient()
def apiClient = new JenkinsApiClient(
- new Config(jenkins: new Config.JenkinsSchema(url: webServer.url("jenkins").toString())),
+ new Config(jenkins: new Config.JenkinsSchema(url: "${wireMock.baseUrl()}/jenkins")),
httpClient)
apiClient.setMaxRetries(3)
apiClient.setWaitPeriodInMs(0)
def result = apiClient.runScript("println('ok')")
assertThat(result).isEqualTo("ok")
- assertThat(crumbQueue.size()).isEqualTo(0)
+
+ wireMock.verify(2, getRequestedFor(urlPathEqualTo("/jenkins/crumbIssuer/api/json")))
+ wireMock.verify(2, postRequestedFor(urlPathEqualTo("/jenkins/scriptText")))
}
@Test
void 'retries on invalid crumb are limited'() {
- webServer.setDispatcher { request ->
- switch (request.path) {
- case "/jenkins/crumbIssuer/api/json":
- return new MockResponse().setBody('{"crumb": "the-invalid-crumb", "crumbRequestField": "Jenkins-Crumb"}')
- case "/jenkins/scriptText":
- def body = '{"servlet":"Stapler", "message":"No valid crumb was included in the request", "url":"/scriptText", "status":"403"}'
- return new MockResponse().setBody(body).setResponseCode(403)
- default:
- return new MockResponse().setStatus("404")
- }
- }
- webServer.start()
+ wireMock.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withBody('{"crumb": "the-invalid-crumb", "crumbRequestField": "Jenkins-Crumb"}')))
- def httpClient = new OkHttpClient()
+ wireMock.stubFor(post(urlPathEqualTo("/jenkins/scriptText"))
+ .willReturn(aResponse()
+ .withStatus(403)
+ .withBody('{"servlet":"Stapler", "message":"No valid crumb was included in the request", "url":"/scriptText", "status":"403"}')))
+
+ def httpClient = getUnsafeOkHttpClient()
def apiClient = new JenkinsApiClient(
- new Config(jenkins: new Config.JenkinsSchema(url: webServer.url("jenkins").toString())),
+ new Config(jenkins: new Config.JenkinsSchema(url: "${wireMock.baseUrl()}/jenkins")),
httpClient)
apiClient.setMaxRetries(3)
apiClient.setWaitPeriodInMs(0)
-
+
shouldFail(RuntimeException) {
apiClient.runScript("println('ok')")
}
- assertThat(webServer.requestCount).isEqualTo(3 /* fetch crumb */ + 3 /* call scriptText */)
+
+ wireMock.verify(3, getRequestedFor(urlPathEqualTo("/jenkins/crumbIssuer/api/json")))
+ wireMock.verify(3, postRequestedFor(urlPathEqualTo("/jenkins/scriptText")))
}
@Test
void 'retries when fetching crumb fails'() {
- def crumbRequestCounter = 0
- webServer.setDispatcher { request ->
- switch (request.path) {
- case "/jenkins/crumbIssuer/api/json":
- if (++crumbRequestCounter > 1) {
- return new MockResponse().setBody('{"crumb": "the-invalid-crumb", "crumbRequestField": "Jenkins-Crumb"}')
- } else {
- return new MockResponse().setBody('error').setResponseCode(401)
- }
- case "/jenkins/scriptText":
- return new MockResponse().setBody("ok")
- default:
- return new MockResponse().setStatus("404")
- }
- }
- webServer.start()
+ wireMock.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .inScenario("Crumb Fetch Retry")
+ .whenScenarioStateIs("Started")
+ .willReturn(aResponse()
+ .withStatus(401)
+ .withBody("error"))
+ .willSetStateTo("First Attempt Failed"))
+
+ wireMock.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .inScenario("Crumb Fetch Retry")
+ .whenScenarioStateIs("First Attempt Failed")
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withBody('{"crumb": "the-invalid-crumb", "crumbRequestField": "Jenkins-Crumb"}')))
+
+ wireMock.stubFor(post(urlPathEqualTo("/jenkins/scriptText"))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withBody("ok")))
- def httpClient = new OkHttpClient()
+ def httpClient = getUnsafeOkHttpClient()
def apiClient = new JenkinsApiClient(
- new Config(jenkins: new Config.JenkinsSchema(url: webServer.url("jenkins").toString())),
+ new Config(jenkins: new Config.JenkinsSchema(url: "${wireMock.baseUrl()}/jenkins")),
httpClient)
apiClient.setMaxRetries(3)
apiClient.setWaitPeriodInMs(0)
def result = apiClient.runScript("println('ok')")
assertThat(result).isEqualTo("ok")
- assertThat(webServer.requestCount).isEqualTo(2 /* fetch crumb */ + 1 /* call scriptText */)
+
+ wireMock.verify(2, getRequestedFor(urlPathEqualTo("/jenkins/crumbIssuer/api/json")))
+ wireMock.verify(1, postRequestedFor(urlPathEqualTo("/jenkins/scriptText")))
+ }
+
+ private static OkHttpClient getUnsafeOkHttpClient() {
+ try {
+ // Create a trust manager that does not validate certificate chains
+ final TrustManager[] trustAllCerts = [
+ new X509TrustManager() {
+ @Override
+ void checkClientTrusted(X509Certificate[] chain, String authType) {}
+ @Override
+ void checkServerTrusted(X509Certificate[] chain, String authType) {}
+ @Override
+ X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0]
+ }
+ }
+ ] as TrustManager[]
+
+ // Install the all-trusting trust manager
+ final SSLContext sslContext = SSLContext.getInstance("SSL")
+ sslContext.init(null, trustAllCerts, new SecureRandom())
+ final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory()
+
+ return new OkHttpClient.Builder()
+ .sslSocketFactory(sslSocketFactory, (X509TrustManager)trustAllCerts[0])
+ .hostnameVerifier { hostname, session -> true }
+ .build()
+ } catch (Exception e) {
+ throw new RuntimeException(e)
+ }
}
-}
+}
\ No newline at end of file
diff --git a/src/test/groovy/com/cloudogu/gitops/jenkins/JobManagerTest.groovy b/src/test/groovy/com/cloudogu/gitops/jenkins/JobManagerTest.groovy
index a82ad8b13..76027b657 100644
--- a/src/test/groovy/com/cloudogu/gitops/jenkins/JobManagerTest.groovy
+++ b/src/test/groovy/com/cloudogu/gitops/jenkins/JobManagerTest.groovy
@@ -1,97 +1,127 @@
package com.cloudogu.gitops.jenkins
import com.cloudogu.gitops.config.Config
+import com.github.tomakehurst.wiremock.WireMockServer
import okhttp3.OkHttpClient
-import okhttp3.mockwebserver.MockResponse
-import okhttp3.mockwebserver.MockWebServer
import org.junit.jupiter.api.Test
-import java.nio.charset.Charset
-
+import static com.github.tomakehurst.wiremock.client.WireMock.*
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options
import static groovy.test.GroovyAssert.shouldFail
import static org.assertj.core.api.Assertions.assertThat
import static org.mockito.ArgumentMatchers.anyString
import static org.mockito.Mockito.*
class JobManagerTest {
+
@Test
void 'creates credential'() {
- def server = new MockWebServer()
+ def wireMockServer = new WireMockServer(options().dynamicPort())
+ wireMockServer.start()
+
try {
- server.enqueue(new MockResponse().setBody('{"crumb":"the-crumb"}'))
- server.enqueue(new MockResponse())
+ wireMockServer.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .willReturn(okJson('{"crumb":"the-crumb"}')))
+
+ wireMockServer.stubFor(post(urlPathMatching(".*createCredentials.*"))
+ .willReturn(ok()))
+
def jobManager = new JobManager(new JenkinsApiClient(
- new Config(jenkins: new Config.JenkinsSchema(url: server.url("jenkins").toString())),
+ new Config(jenkins: new Config.JenkinsSchema(url: wireMockServer.baseUrl() + "/jenkins")),
new OkHttpClient()))
+
jobManager.createCredential('the-jobname', 'the-id', 'the-username', 'the-password', 'some description')
- assertThat(server.requestCount).isEqualTo(2)
- server.takeRequest() // crumb
- def request = server.takeRequest()
- assertThat(request.path).isEqualTo("/jenkins/job/the-jobname/credentials/store/folder/domain/_/createCredentials")
- assertThat(URLDecoder.decode(request.body.readString(Charset.defaultCharset()), "utf-8")).isEqualTo('json={"credentials":{"scope":"GLOBAL","id":"the-id","username":"the-username","password":"the-password","description":"some description","$class":"com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl"}}')
+ wireMockServer.verify(postRequestedFor(urlPathEqualTo("/jenkins/job/the-jobname/credentials/store/folder/domain/_/createCredentials")))
+
+ def requests = wireMockServer.findAll(postRequestedFor(urlPathMatching(".*createCredentials.*")))
+ assertThat(requests).hasSize(1)
+
+ def requestBody = requests[0].bodyAsString
+ assertThat(URLDecoder.decode(requestBody, "utf-8"))
+ .isEqualTo('json={"credentials":{"scope":"GLOBAL","id":"the-id","username":"the-username","password":"the-password","description":"some description","$class":"com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl"}}')
+
} finally {
- server.shutdown()
+ wireMockServer.stop()
}
}
@Test
void 'throw when creating credential fails'() {
- def server = new MockWebServer()
+ def wireMockServer = new WireMockServer(options().dynamicPort())
+ wireMockServer.start()
+
try {
- server.enqueue(new MockResponse().setBody('{"crumb":"the-crumb"}'))
- server.enqueue(new MockResponse().setResponseCode(404))
+ wireMockServer.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .willReturn(okJson('{"crumb":"the-crumb"}')))
+
+ wireMockServer.stubFor(post(urlPathMatching(".*createCredentials.*"))
+ .willReturn(aResponse().withStatus(404)))
+
def jobManager = new JobManager(new JenkinsApiClient(
- new Config(jenkins: new Config.JenkinsSchema(url: server.url("jenkins").toString())),
+ new Config(jenkins: new Config.JenkinsSchema(url: wireMockServer.baseUrl() + "/jenkins")),
new OkHttpClient()))
+
def exception = shouldFail(RuntimeException) {
jobManager.createCredential('the-jobname', 'the-id', 'the-username', 'the-password', 'some description')
}
assertThat(exception.getMessage()).isEqualTo('Could not create credential id=the-id,job=the-jobname. StatusCode: 404')
} finally {
- server.shutdown()
+ wireMockServer.stop()
}
}
@Test
void 'starts job'() {
- def server = new MockWebServer()
+ def wireMockServer = new WireMockServer(options().dynamicPort())
+ wireMockServer.start()
+
try {
- server.enqueue(new MockResponse().setBody('{"crumb":"the-crumb"}'))
- server.enqueue(new MockResponse().setResponseCode(200))
+ wireMockServer.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .willReturn(okJson('{"crumb":"the-crumb"}')))
+
+ wireMockServer.stubFor(post(urlPathMatching("/jenkins/job/the-jobname/build.*"))
+ .willReturn(ok()))
+
def jobManager = new JobManager(new JenkinsApiClient(
- new Config(jenkins: new Config.JenkinsSchema(url: server.url("jenkins").toString())),
+ new Config(jenkins: new Config.JenkinsSchema(url: wireMockServer.baseUrl() + "/jenkins")),
new OkHttpClient()))
+
jobManager.startJob('the-jobname')
- assertThat(server.requestCount).isEqualTo(2)
- server.takeRequest() // crumb
- def request = server.takeRequest()
- assertThat(request.path).isEqualTo("/jenkins/job/the-jobname/build?delay=0sec")
+ wireMockServer.verify(postRequestedFor(urlPathEqualTo("/jenkins/job/the-jobname/build"))
+ .withQueryParam("delay", equalTo("0sec")))
+
} finally {
- server.shutdown()
+ wireMockServer.stop()
}
}
@Test
void 'throw when starting job fails'() {
- def server = new MockWebServer()
+ def wireMockServer = new WireMockServer(options().dynamicPort())
+ wireMockServer.start()
+
try {
- server.enqueue(new MockResponse().setBody('{"crumb":"the-crumb"}'))
- server.enqueue(new MockResponse().setResponseCode(400))
+ wireMockServer.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .willReturn(okJson('{"crumb":"the-crumb"}')))
+
+ wireMockServer.stubFor(post(urlPathMatching("/jenkins/job/the-jobname/build.*"))
+ .willReturn(aResponse().withStatus(400)))
+
def jobManager = new JobManager(new JenkinsApiClient(
- new Config(jenkins: new Config.JenkinsSchema(url: server.url("jenkins").toString())),
+ new Config(jenkins: new Config.JenkinsSchema(url: wireMockServer.baseUrl() + "/jenkins")),
new OkHttpClient()))
+
def exception = shouldFail(RuntimeException) {
jobManager.startJob('the-jobname')
}
assertThat(exception.getMessage()).isEqualTo('Could not trigger build of Jenkins job: the-jobname. StatusCode: 400')
} finally {
- server.shutdown()
+ wireMockServer.stop()
}
}
-
@Test
void 'throws when job contains invalid characters'() {
def client = mock(JenkinsApiClient)
@@ -102,7 +132,7 @@ class JobManagerTest {
}
assertThat(exception.getMessage()).isEqualTo('Job name cannot contain quotes.')
}
-
+
@Test
void 'throws when job deletion fails'() {
def client = mock(JenkinsApiClient)
@@ -121,103 +151,115 @@ class JobManagerTest {
when(client.runScript(anyString())).thenReturn("null")
jobManager.deleteJob("foo")
-
- verify(client).runScript("print(Jenkins.instance.getItem('foo')?.delete())")
+ org.mockito.Mockito.verify(client).runScript("print(Jenkins.instance.getItem('foo')?.delete())")
}
@Test
void 'checks existing Job'() {
- def server = new MockWebServer()
+ def wireMockServer = new WireMockServer(options().dynamicPort())
+ wireMockServer.start()
+
try {
- server.enqueue(new MockResponse().setBody('{"crumb":"the-crumb"}'))
- server.enqueue(new MockResponse().setResponseCode(200))
+ wireMockServer.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .willReturn(okJson('{"crumb":"the-crumb"}')))
+
+ wireMockServer.stubFor(post(urlPathEqualTo("/jenkins/job/the-jobname"))
+ .willReturn(ok()))
+
def jobManager = new JobManager(new JenkinsApiClient(
- new Config(jenkins: new Config.JenkinsSchema(url: server.url("jenkins").toString())),
+ new Config(jenkins: new Config.JenkinsSchema(url: wireMockServer.baseUrl() + "/jenkins")),
new OkHttpClient()))
-
+
def exists = jobManager.jobExists('the-jobname')
-
+
assertThat(exists).isEqualTo(true)
- assertThat(server.requestCount).isEqualTo(2)
- server.takeRequest() // crumb
- def request = server.takeRequest()
- assertThat(request.path).isEqualTo("/jenkins/job/the-jobname")
+ wireMockServer.verify(postRequestedFor(urlPathEqualTo("/jenkins/job/the-jobname")))
} finally {
- server.shutdown()
+ wireMockServer.stop()
}
}
-
+
@Test
void 'checks non-existing Job'() {
- def server = new MockWebServer()
+ def wireMockServer = new WireMockServer(options().dynamicPort())
+ wireMockServer.start()
+
try {
- server.enqueue(new MockResponse().setBody('{"crumb":"the-crumb"}'))
- server.enqueue(new MockResponse().setResponseCode(404))
+ wireMockServer.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .willReturn(okJson('{"crumb":"the-crumb"}')))
+
+ wireMockServer.stubFor(post(urlPathEqualTo("/jenkins/job/the-jobname"))
+ .willReturn(aResponse().withStatus(404)))
+
def jobManager = new JobManager(new JenkinsApiClient(
- new Config(jenkins: new Config.JenkinsSchema(url: server.url("jenkins").toString())),
+ new Config(jenkins: new Config.JenkinsSchema(url: wireMockServer.baseUrl() + "/jenkins")),
new OkHttpClient()))
-
+
def exists = jobManager.jobExists('the-jobname')
-
assertThat(exists).isEqualTo(false)
- assertThat(server.requestCount).isEqualTo(2)
- server.takeRequest() // crumb
- def request = server.takeRequest()
- assertThat(request.path).isEqualTo("/jenkins/job/the-jobname")
+ wireMockServer.verify(postRequestedFor(urlPathEqualTo("/jenkins/job/the-jobname")))
} finally {
- server.shutdown()
+ wireMockServer.stop()
}
}
@Test
void 'creates Job'() {
- def server = new MockWebServer()
+ def wireMockServer = new WireMockServer(options().dynamicPort())
+ wireMockServer.start()
+
try {
- server.enqueue(new MockResponse().setBody('{"crumb":"the-crumb"}'))
- server.enqueue(new MockResponse().setResponseCode(404)) // jobExists
- server.enqueue(new MockResponse().setBody('{"crumb":"the-crumb"}'))
- server.enqueue(new MockResponse().setResponseCode(200))
+ wireMockServer.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .willReturn(okJson('{"crumb":"the-crumb"}')))
+ wireMockServer.stubFor(post(urlPathEqualTo("/jenkins/job/the-jobname"))
+ .willReturn(aResponse().withStatus(404)))
+ wireMockServer.stubFor(post(urlPathMatching("/jenkins/createItem.*"))
+ .willReturn(ok()))
+
def jobManager = new JobManager(new JenkinsApiClient(
- new Config(jenkins: new Config.JenkinsSchema(url: server.url("jenkins").toString())),
+ new Config(jenkins: new Config.JenkinsSchema(url: wireMockServer.baseUrl() + "/jenkins")),
new OkHttpClient()))
def created = jobManager.createJob('the-jobname', 'http://scm', 'ns', 'creds')
assertThat(created).isEqualTo(true)
- assertThat(server.requestCount).isEqualTo(4)
- server.takeRequest() // crumb
- server.takeRequest() // exists
- server.takeRequest() // crumb
- def request = server.takeRequest()
- assertThat(request.path).isEqualTo("/jenkins/createItem?name=the-jobname")
-
- def body = request.body.readUtf8()
- assertThat(body).contains('http://scm')
- assertThat(body).contains('ns')
- assertThat(body).contains('creds')
+
+ wireMockServer.verify(postRequestedFor(urlPathEqualTo("/jenkins/job/the-jobname")))
+ wireMockServer.verify(postRequestedFor(urlPathEqualTo("/jenkins/createItem"))
+ .withQueryParam("name", equalTo("the-jobname"))
+ .withRequestBody(containing('http://scm'))
+ .withRequestBody(containing('ns'))
+ .withRequestBody(containing('creds')))
+
} finally {
- server.shutdown()
+ wireMockServer.stop()
}
}
-
+
@Test
void 'ignores existing Job'() {
- def server = new MockWebServer()
+ def wireMockServer = new WireMockServer(options().dynamicPort())
+ wireMockServer.start()
+
try {
- server.enqueue(new MockResponse().setBody('{"crumb":"the-crumb"}'))
- server.enqueue(new MockResponse().setResponseCode(200)) // jobExists
+ wireMockServer.stubFor(get(urlPathEqualTo("/jenkins/crumbIssuer/api/json"))
+ .willReturn(okJson('{"crumb":"the-crumb"}')))
+
+ wireMockServer.stubFor(post(urlPathEqualTo("/jenkins/job/the-jobname"))
+ .willReturn(ok())) // 200 OK means "Job Exists"
+
def jobManager = new JobManager(new JenkinsApiClient(
- new Config(jenkins: new Config.JenkinsSchema(url: server.url("jenkins").toString())),
+ new Config(jenkins: new Config.JenkinsSchema(url: wireMockServer.baseUrl() + "/jenkins")),
new OkHttpClient()))
def created = jobManager.createJob('the-jobname', 'http://scm', 'ns', 'creds')
assertThat(created).isEqualTo(false)
- assertThat(server.requestCount).isEqualTo(2)
- server.takeRequest() // crumb
- server.takeRequest() // exists
+ wireMockServer.verify(postRequestedFor(urlPathEqualTo("/jenkins/job/the-jobname")))
+ wireMockServer.verify(0, postRequestedFor(urlPathEqualTo("/jenkins/createItem")))
+
} finally {
- server.shutdown()
+ wireMockServer.stop()
}
}
-}
+}
\ No newline at end of file
diff --git a/src/test/groovy/com/cloudogu/gitops/okhttp/RetryInterceptorTest.groovy b/src/test/groovy/com/cloudogu/gitops/okhttp/RetryInterceptorTest.groovy
index d2c857579..ff2683ce0 100644
--- a/src/test/groovy/com/cloudogu/gitops/okhttp/RetryInterceptorTest.groovy
+++ b/src/test/groovy/com/cloudogu/gitops/okhttp/RetryInterceptorTest.groovy
@@ -1,74 +1,142 @@
package com.cloudogu.gitops.okhttp
+import com.github.tomakehurst.wiremock.junit5.WireMockExtension
import okhttp3.OkHttpClient
import okhttp3.Request
-import okhttp3.mockwebserver.MockResponse
-import okhttp3.mockwebserver.MockWebServer
-import okhttp3.mockwebserver.SocketPolicy
-import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+import javax.net.ssl.HostnameVerifier
+import javax.net.ssl.SSLContext
+import javax.net.ssl.TrustManager
+import javax.net.ssl.X509TrustManager
+import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
+import static com.github.tomakehurst.wiremock.client.WireMock.*
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig
import static org.assertj.core.api.Assertions.assertThat
class RetryInterceptorTest {
- private MockWebServer webServer = new MockWebServer()
- @AfterEach
- void tearDown() {
- webServer.shutdown()
- }
+ @RegisterExtension
+ static WireMockExtension wireMock = WireMockExtension.newInstance()
+ .options(wireMockConfig()
+ .dynamicPort()
+ .dynamicHttpsPort())
+ .build()
@Test
void 'retries three times on 500'() {
- webServer.enqueue(new MockResponse().setResponseCode(500))
- webServer.enqueue(new MockResponse().setResponseCode(500))
- webServer.enqueue(new MockResponse().setResponseCode(200).setBody("Successful Result"))
+ wireMock.stubFor(get(urlEqualTo("/"))
+ .inScenario("Retry Scenario")
+ .whenScenarioStateIs("Started")
+ .willReturn(aResponse().withStatus(500))
+ .willSetStateTo("First Retry"))
+
+ wireMock.stubFor(get(urlEqualTo("/"))
+ .inScenario("Retry Scenario")
+ .whenScenarioStateIs("First Retry")
+ .willReturn(aResponse().withStatus(500))
+ .willSetStateTo("Second Retry"))
+
+ wireMock.stubFor(get(urlEqualTo("/"))
+ .inScenario("Retry Scenario")
+ .whenScenarioStateIs("Second Retry")
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withBody("Successful Result")))
def client = createClient()
+ def response = client.newCall(new Request.Builder().url(wireMock.baseUrl()).build()).execute()
+
+ assertThat(response.body().string()).isEqualTo("Successful Result")
+ wireMock.verify(3, getRequestedFor(urlEqualTo("/")))
+ }
+
+ @Test
+ void 'retries three times on 500 with HTTPS'() {
+ wireMock.stubFor(get(urlEqualTo("/secure"))
+ .inScenario("HTTPS Retry Scenario")
+ .whenScenarioStateIs("Started")
+ .willReturn(aResponse().withStatus(500))
+ .willSetStateTo("First Retry"))
+
+ wireMock.stubFor(get(urlEqualTo("/secure"))
+ .inScenario("HTTPS Retry Scenario")
+ .whenScenarioStateIs("First Retry")
+ .willReturn(aResponse().withStatus(500))
+ .willSetStateTo("Second Retry"))
+
+ wireMock.stubFor(get(urlEqualTo("/secure"))
+ .inScenario("HTTPS Retry Scenario")
+ .whenScenarioStateIs("Second Retry")
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withBody("Successful Result")))
- def response = client.newCall(new Request.Builder().url(webServer.url("")).build()).execute()
+ def client = createClient()
+ def httpsUrl = "https://localhost:${wireMock.httpsPort}/secure"
+ def response = client.newCall(new Request.Builder().url(httpsUrl).build()).execute()
assertThat(response.body().string()).isEqualTo("Successful Result")
+ wireMock.verify(3, getRequestedFor(urlEqualTo("/secure")))
}
@Test
void 'retries on timeout'() {
- def timeoutResponse = new MockResponse()
- timeoutResponse.socketPolicy(SocketPolicy.NO_RESPONSE)
- webServer.enqueue(timeoutResponse)
- webServer.enqueue(new MockResponse().setResponseCode(200).setBody("Successful Result"))
+ wireMock.stubFor(get(urlEqualTo("/"))
+ .inScenario("Timeout Scenario")
+ .whenScenarioStateIs("Started")
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withFixedDelay(100)) // Delay longer than read timeout
+ .willSetStateTo("After Timeout"))
+
+ wireMock.stubFor(get(urlEqualTo("/"))
+ .inScenario("Timeout Scenario")
+ .whenScenarioStateIs("After Timeout")
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withBody("Successful Result")))
def client = createClient()
-
- def response = client.newCall(new Request.Builder().url(webServer.url("")).build()).execute()
+ def response = client.newCall(new Request.Builder().url(wireMock.baseUrl()).build()).execute()
assertThat(response.body().string()).isEqualTo("Successful Result")
- assertThat(webServer.requestCount).isEqualTo(2)
+ wireMock.verify(2, getRequestedFor(urlEqualTo("/")))
}
@Test
void 'fails after third retry'() {
- webServer.enqueue(new MockResponse().setResponseCode(500))
- webServer.enqueue(new MockResponse().setResponseCode(500))
- webServer.enqueue(new MockResponse().setResponseCode(500))
- webServer.enqueue(new MockResponse().setResponseCode(500))
- webServer.enqueue(new MockResponse().setResponseCode(200).setBody("Successful Result"))
+ wireMock.stubFor(get(urlEqualTo("/"))
+ .willReturn(aResponse().withStatus(500)))
def client = createClient()
-
- def response = client.newCall(new Request.Builder().url(webServer.url("")).build()).execute()
+ def response = client.newCall(new Request.Builder().url(wireMock.baseUrl()).build()).execute()
assertThat(response.code()).isEqualTo(500)
- assertThat(webServer.takeRequest(1, TimeUnit.MILLISECONDS).path).isNotNull()
- assertThat(webServer.takeRequest(1, TimeUnit.MILLISECONDS).path).isNotNull()
+ wireMock.verify(4, getRequestedFor(urlEqualTo("/"))) // Initial request + 3 retries
}
private OkHttpClient createClient() {
+ // 1. Create a TrustManager that trusts everyone
+ def trustAllCerts = [
+ new X509TrustManager() {
+ void checkClientTrusted(X509Certificate[] chain, String authType) {}
+ void checkServerTrusted(X509Certificate[] chain, String authType) {}
+ X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0] }
+ }
+ ] as TrustManager[]
+
+ def sslContext = SSLContext.getInstance("TLS")
+ sslContext.init(null, trustAllCerts, new java.security.SecureRandom())
+
new OkHttpClient.Builder()
.addInterceptor(new RetryInterceptor(retries: 3, waitPeriodInMs: 0))
.readTimeout(50, TimeUnit.MILLISECONDS)
+ .sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
+ .hostnameVerifier({ hostname, session -> true } as HostnameVerifier)
.build()
}
-}
+}
\ No newline at end of file