Skip to content

Commit 93fe196

Browse files
committedOct 24, 2024·
fix: Log the error response bodies from the Pact Broker #1830
1 parent 3b25199 commit 93fe196

File tree

5 files changed

+84
-7
lines changed

5 files changed

+84
-7
lines changed
 

‎core/pactbroker/src/main/kotlin/au/com/dius/pact/core/pactbroker/HalClient.kt

+27-3
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ open class HalClient @JvmOverloads constructor(
210210
if (handler != null) {
211211
handler(it.code, it)
212212
} else if (it.code >= 300) {
213-
logger.error { "PUT JSON request failed with status ${it.code} ${it.reasonPhrase}" }
213+
logger.error { "POST JSON request failed with status ${it.code} ${it.reasonPhrase}" }
214214
Result.Err(RequestFailedException(it.code, if (it.entity != null) EntityUtils.toString(it.entity) else null))
215215
} else {
216216
true
@@ -343,14 +343,38 @@ open class HalClient @JvmOverloads constructor(
343343
when (response.code) {
344344
404 -> Result.Err(NotFoundHalResponse("No HAL document found at path '$path'"))
345345
else -> {
346-
val body = if (response.entity != null) EntityUtils.toString(response.entity) else null
346+
val body = handleResponseBody(response, path)
347347
Result.Err(RequestFailedException(response.code, body,
348-
"Request to path '$path' failed with response ${response.code}"))
348+
"Request to path '$path' failed with HTTP response ${response.code}"))
349349
}
350350
}
351351
}
352352
}
353353

354+
private fun handleResponseBody(response: ClassicHttpResponse, path: String): String? {
355+
var body: String? = null
356+
if (response.entity != null) {
357+
body = EntityUtils.toString(response.entity)
358+
val contentType = ContentType.parseLenient(response.entity.contentType)
359+
if (isJsonResponse(contentType)) {
360+
val json = handleWith<JsonValue> { JsonParser.parseString(body) }
361+
when (json) {
362+
is Result.Ok -> {
363+
logger.error { "Request to path '$path' failed with HTTP response ${response.code}" }
364+
logger.error { "JSON Response:\n${json.value.prettyPrint()}" }
365+
}
366+
367+
is Result.Err -> {
368+
logger.error { "Request to path '$path' failed with HTTP response ${response.code}: $body" }
369+
}
370+
}
371+
} else {
372+
logger.error { "Request to path '$path' failed with HTTP response ${response.code}: $body" }
373+
}
374+
}
375+
return body
376+
}
377+
354378
private fun fetchLink(link: String, options: Map<String, Any>): JsonValue.Object {
355379
val href = hrefForLink(link, options)
356380
return this.fetch(href, false).unwrap()

‎core/pactbroker/src/main/kotlin/au/com/dius/pact/core/pactbroker/PactBrokerClient.kt

+4
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,10 @@ open class PactBrokerClient(
473473
}
474474
}
475475

476+
@Deprecated(
477+
"use version that takes a list of ConsumerVersionSelectors",
478+
replaceWith = ReplaceWith("fetchConsumersWithSelectorsV2")
479+
)
476480
override fun fetchConsumersWithSelectors(
477481
providerName: String,
478482
selectors: List<ConsumerVersionSelector>,

‎core/pactbroker/src/test/groovy/au/com/dius/pact/core/pactbroker/HalClientSpec.groovy

+22-3
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ class HalClientSpec extends Specification {
289289

290290
@Unroll
291291
@SuppressWarnings('UnnecessaryGetter')
292-
def 'post URL returns #success if the response is #status'() {
292+
def 'post to URL returns #success if the response is #status'() {
293293
given:
294294
def mockClient = Mock(CloseableHttpClient)
295295
client.httpClient = mockClient
@@ -308,7 +308,7 @@ class HalClientSpec extends Specification {
308308
'failure' | 400 | Result.Err
309309
}
310310

311-
def 'post URL returns a failure result if an exception is thrown'() {
311+
def 'post to URL returns a failure result if an exception is thrown'() {
312312
given:
313313
def mockClient = Mock(CloseableHttpClient)
314314
client.httpClient = mockClient
@@ -322,7 +322,7 @@ class HalClientSpec extends Specification {
322322
}
323323

324324
@SuppressWarnings('UnnecessaryGetter')
325-
def 'post URL delegates to a handler if one is supplied'() {
325+
def 'post to URL delegates to a handler if one is supplied'() {
326326
given:
327327
def mockClient = Mock(CloseableHttpClient)
328328
client.httpClient = mockClient
@@ -523,4 +523,23 @@ class HalClientSpec extends Specification {
523523
handler.handleResponse(mockResponse)
524524
}
525525
}
526+
527+
@Issue('1830')
528+
def 'post to URL handles any error responses'() {
529+
given:
530+
def mockClient = Mock(CloseableHttpClient)
531+
client.httpClient = mockClient
532+
def mockResponse = Mock(ClassicHttpResponse) {
533+
getCode() >> 400
534+
getEntity() >> new StringEntity('{"error": ["it went bang!"]}', ContentType.APPLICATION_JSON)
535+
}
536+
client.pathInfo = JsonParser.INSTANCE.parseString('{"_links":{"path":{"href":"http://localhost:8080/"}}}')
537+
538+
when:
539+
def result = client.postJson('path', [:], '"body"')
540+
541+
then:
542+
1 * mockClient.execute(_, _, _) >> { req, c, handler -> handler.handleResponse(mockResponse) }
543+
result instanceof Result.Err
544+
}
526545
}

‎provider/src/main/kotlin/au/com/dius/pact/provider/junitsupport/loader/PactBrokerLoader.kt

+19-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import au.com.dius.pact.core.pactbroker.ConsumerVersionSelectors
1212
import au.com.dius.pact.core.pactbroker.IPactBrokerClient
1313
import au.com.dius.pact.core.pactbroker.PactBrokerClient
1414
import au.com.dius.pact.core.pactbroker.PactBrokerClientConfig
15+
import au.com.dius.pact.core.pactbroker.RequestFailedException
1516
import au.com.dius.pact.core.support.Result
1617
import au.com.dius.pact.core.support.Utils.permutations
1718
import au.com.dius.pact.core.support.expressions.DataType
@@ -249,7 +250,24 @@ open class PactBrokerLoader(
249250
providerBranch, pending, wipSinceDate)
250251
var consumers = when (result) {
251252
is Result.Ok -> result.value
252-
is Result.Err -> throw result.error
253+
is Result.Err -> {
254+
when (val exception = result.error) {
255+
is RequestFailedException -> {
256+
logger.error(exception) {
257+
if (exception.body.isNotEmpty()) {
258+
"Failed to load Pacts from the Pact broker: ${exception.message} (HTTP Status ${exception.status})" +
259+
"\nResponse: ${exception.body}"
260+
} else {
261+
"Failed to load Pacts from the Pact broker: ${exception.message} (HTTP Status ${exception.status})"
262+
}
263+
}
264+
}
265+
else -> {
266+
logger.error(exception) { "Failed to load Pacts from the Pact broker " }
267+
}
268+
}
269+
throw result.error
270+
}
253271
}
254272

255273
if (failIfNoPactsFound && consumers.isEmpty()) {

‎provider/src/test/groovy/au/com/dius/pact/provider/junitsupport/loader/PactBrokerLoaderSpec.groovy

+12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import au.com.dius.pact.core.pactbroker.IPactBrokerClient
88
import au.com.dius.pact.core.pactbroker.InvalidHalResponse
99
import au.com.dius.pact.core.pactbroker.InvalidNavigationRequest
1010
import au.com.dius.pact.core.pactbroker.PactBrokerResult
11+
import au.com.dius.pact.core.pactbroker.RequestFailedException
1112
import au.com.dius.pact.core.support.expressions.DataType
1213
import au.com.dius.pact.core.support.expressions.ExpressionParser
1314
import au.com.dius.pact.core.support.expressions.SystemPropertyResolver
@@ -1368,6 +1369,17 @@ class PactBrokerLoaderSpec extends Specification {
13681369
thrown(InvalidNavigationRequest)
13691370
}
13701371

1372+
@Issue('#1830')
1373+
def 'Handles error responses from the broker'() {
1374+
when:
1375+
pactBrokerLoader().load('test')
1376+
1377+
then:
1378+
1 * brokerClient.fetchConsumersWithSelectorsV2('test', [], [], '', false, '') >>
1379+
new Result.Err(new RequestFailedException(400, '{"error": "selectors are invalid"}', 'Request to broker failed'))
1380+
thrown(RequestFailedException)
1381+
}
1382+
13711383
void 'Does not enable insecure TLS when not set in PactBroker annotation and not using the fallback system property'() {
13721384
given:
13731385
pactBrokerLoader = {

0 commit comments

Comments
 (0)
Please sign in to comment.