Skip to content

Commit 9948313

Browse files
authored
Bug/replace mockwebserver with wiremock (#331)
* first initial replacement * fix RetryInterceptor * outsource dependency to variable
1 parent 1d01378 commit 9948313

File tree

9 files changed

+488
-303
lines changed

9 files changed

+488
-303
lines changed

pom.xml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
<groovy.version>4.0.12</groovy.version>
3535
<jsonschema.version>4.35.0</jsonschema.version>
3636
<kubernetes.client.java.version>24.0.0</kubernetes.client.java.version>
37+
<wiremock.version>3.13.2</wiremock.version>
3738
</properties>
3839

3940
<scm>
@@ -205,9 +206,9 @@
205206
</dependency>
206207

207208
<dependency>
208-
<groupId>com.squareup.okhttp3</groupId>
209-
<artifactId>mockwebserver</artifactId>
210-
<version>${okhttpVersion}</version>
209+
<groupId>org.wiremock</groupId>
210+
<artifactId>wiremock</artifactId>
211+
<version>${wiremock.version}</version>
211212
<scope>test</scope>
212213
</dependency>
213214

src/main/groovy/com/cloudogu/gitops/dependencyinjection/HttpClientFactory.groovy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import okhttp3.logging.HttpLoggingInterceptor
1414
import org.jetbrains.annotations.NotNull
1515
import org.slf4j.LoggerFactory
1616

17+
import javax.net.ssl.HostnameVerifier
1718
import javax.net.ssl.SSLContext
1819
import javax.net.ssl.SSLSocketFactory
1920
import javax.net.ssl.X509TrustManager
@@ -35,6 +36,8 @@ class HttpClientFactory {
3536
builder.sslSocketFactory(context.socketFactory, context.trustManager)
3637
}
3738

39+
builder.hostnameVerifier({ hostname, session -> true } as HostnameVerifier)
40+
3841
return builder.build()
3942
}
4043

src/main/groovy/com/cloudogu/gitops/jenkins/JenkinsApiClient.groovy

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,14 @@ class JenkinsApiClient {
2222
Config config,
2323
@Named("jenkins") OkHttpClient client
2424
) {
25-
this.client = client
25+
26+
if (config.application.insecure) {
27+
this.client = client.newBuilder()
28+
.hostnameVerifier({ hostname, session -> true })
29+
.build()
30+
} else {
31+
this.client = client
32+
}
2633
this.config = config
2734
}
2835

src/main/groovy/com/cloudogu/gitops/okhttp/RetryInterceptor.groovy

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,46 +15,65 @@ class RetryInterceptor implements Interceptor {
1515
private int retries
1616
private int waitPeriodInMs
1717

18-
// Number of retries in uncommonly high, because we might have to outlive a unexpected Jenkins restart
18+
// Number of retries in uncommonly high, because we might have to outlive a unexpected Jenkins restart
1919
RetryInterceptor(int retries = 180, int waitPeriodInMs = 2000) {
2020
this.waitPeriodInMs = waitPeriodInMs
2121
this.retries = retries
2222
}
2323

2424
@Override
2525
Response intercept(@NotNull Chain chain) throws IOException {
26-
def i = 0;
26+
def i = 0
2727
Response response = null
28+
IOException lastException = null
29+
2830
do {
2931
try {
3032
response = chain.proceed(chain.request())
33+
3134
if (response.code() !in getStatusCodesToRetry()) {
32-
break
35+
// Success or non-retriable error - return the response
36+
return response
3337
}
3438

3539
log.trace("Retry HTTP Request to {} due to status code {}", chain.request().url().toString(), response.code())
40+
response.close()
3641

3742
} catch (SocketTimeoutException e) {
38-
// fallthrough to retry
43+
lastException = e
3944
log.trace("Retry HTTP Request to {} due to SocketTimeoutException: {}", chain.request().url().toString(), e.message)
4045
}
41-
response?.close()
42-
Thread.sleep(waitPeriodInMs)
46+
47+
// Wait before next retry (but not after the last attempt)
48+
if (i < retries) {
49+
Thread.sleep(waitPeriodInMs)
50+
}
4351
++i
44-
} while(i < retries)
4552

46-
return response
53+
} while(i <= retries)
54+
55+
// If we got here, all retries failed
56+
if (response != null) {
57+
// Return the last failed response
58+
return response
59+
} else if (lastException != null) {
60+
// All attempts resulted in timeout - throw the last exception
61+
throw lastException
62+
} else {
63+
// This should never happen, but as a safety net
64+
throw new IOException("Request failed after ${retries} retries")
65+
}
4766
}
4867

4968
private List<Integer> getStatusCodesToRetry() {
5069
return [
51-
// list of codes if from curl --retry
52-
408, // Request Timeout
53-
429, // Too Many Requests
54-
500, // Internal Server Error
55-
502, // Bad Gateway
56-
503, // Service Unavailable
57-
504, // Gateway Timeout
70+
// list of codes from curl --retry
71+
408, // Request Timeout
72+
429, // Too Many Requests
73+
500, // Internal Server Error
74+
502, // Bad Gateway
75+
503, // Service Unavailable
76+
504, // Gateway Timeout
5877
]
5978
}
60-
}
79+
}

src/test/groovy/com/cloudogu/gitops/common/MockWebServerHttpsFactory.groovy

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,66 @@
11
package com.cloudogu.gitops.git.providers.scmmanager.api
22

3-
import com.cloudogu.gitops.common.MockWebServerHttpsFactory
43
import com.cloudogu.gitops.config.Credentials
5-
import okhttp3.mockwebserver.MockResponse
6-
import okhttp3.mockwebserver.MockWebServer
7-
import org.junit.jupiter.api.AfterEach
4+
import com.github.tomakehurst.wiremock.junit5.WireMockExtension
85
import org.junit.jupiter.api.Test
6+
import org.junit.jupiter.api.extension.RegisterExtension
97

108
import javax.net.ssl.SSLHandshakeException
119

10+
import static com.github.tomakehurst.wiremock.client.WireMock.*
11+
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig
1212
import static groovy.test.GroovyAssert.shouldFail
1313
import static org.assertj.core.api.Assertions.assertThat
1414

1515
class UsersApiTest {
16-
private MockWebServer webServer = new MockWebServer()
17-
private Credentials credentials = new Credentials("user", "pass")
1816

19-
@AfterEach
20-
void tearDown() {
21-
webServer.shutdown()
22-
}
17+
@RegisterExtension
18+
static WireMockExtension wireMock = WireMockExtension.newInstance()
19+
.options(wireMockConfig()
20+
.dynamicPort()
21+
.dynamicHttpsPort())
22+
.build()
23+
24+
private Credentials credentials = new Credentials("user", "pass")
2325

2426
@Test
2527
void 'allows self-signed certificates when using insecure option'() {
26-
webServer.useHttps(MockWebServerHttpsFactory.createSocketFactory().sslSocketFactory(), false)
27-
28-
def api = usersApi(true)
29-
webServer.enqueue(new MockResponse().setResponseCode(204))
28+
wireMock.stubFor(delete(urlPathEqualTo("/scm/api/v2/users/test-user"))
29+
.willReturn(aResponse().withStatus(204)))
3030

31+
def api = usersApi(true, true) // insecure=true, useHttps=true
3132
def resp = api.delete('test-user').execute()
3233

3334
assertThat(resp.isSuccessful()).isTrue()
34-
assertThat(webServer.requestCount).isEqualTo(1)
35+
wireMock.verify(1, deleteRequestedFor(urlPathEqualTo("/scm/api/v2/users/test-user")))
3536
}
3637

3738
@Test
3839
void 'does not allow self-signed certificates by default'() {
39-
webServer.useHttps(MockWebServerHttpsFactory.createSocketFactory().sslSocketFactory(), false)
40+
wireMock.stubFor(delete(urlPathEqualTo("/scm/api/v2/users/test-user"))
41+
.willReturn(aResponse().withStatus(204)))
4042

41-
def api = usersApi(false)
43+
def api = usersApi(false, true) // insecure=false, useHttps=true
4244

4345
shouldFail(SSLHandshakeException) {
4446
api.delete('test-user').execute()
4547
}
46-
assertThat(webServer.requestCount).isEqualTo(0)
47-
}
4848

49+
wireMock.verify(0, deleteRequestedFor(urlPathEqualTo("/scm/api/v2/users/test-user")))
50+
}
4951

50-
private UsersApi usersApi(boolean insecure) {
51-
def client = new ScmManagerApiClient(apiBaseUrl(), credentials, insecure)
52+
private UsersApi usersApi(boolean insecure, boolean useHttps = false) {
53+
def client = new ScmManagerApiClient(apiBaseUrl(useHttps), credentials, insecure)
5254
return client.usersApi()
5355
}
5456

55-
private String apiBaseUrl() {
56-
return "${webServer.url('scm')}/api/"
57+
private String apiBaseUrl(boolean useHttps) {
58+
if (useHttps) {
59+
// Use the proper HTTPS port from WireMock
60+
def httpsPort = wireMock.httpsPort
61+
return "https://localhost:${httpsPort}/scm/api/"
62+
} else {
63+
return "${wireMock.baseUrl()}/scm/api/"
64+
}
5765
}
58-
5966
}

0 commit comments

Comments
 (0)