Skip to content

Commit

Permalink
fix: support multi-line matching with plain text matcher #1579
Browse files Browse the repository at this point in the history
  • Loading branch information
uglyog committed Jul 26, 2022
1 parent 27e9c3f commit 021c0c7
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package au.com.dius.pact.consumer.junit5;

import au.com.dius.pact.consumer.MockServer;
import au.com.dius.pact.consumer.dsl.PactDslRootValue;
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
import au.com.dius.pact.core.model.PactSpecVersion;
import au.com.dius.pact.core.model.RequestResponsePact;
import au.com.dius.pact.core.model.annotations.Pact;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.hc.client5.http.fluent.Request;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import static au.com.dius.pact.consumer.dsl.LambdaDsl.newJsonArrayMinLike;
import static java.lang.String.format;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

@ExtendWith(PactConsumerTestExt.class)
@PactTestFor(providerName = "TextProvider", pactVersion = PactSpecVersion.V3)
public class Defect1579Test {
@Pact(consumer = "TextConsumer")
public RequestResponsePact articles(PactDslWithProvider builder) {
return builder
.given("A text generation job finished successfully")
.uponReceiving("A request to download text")
.pathFromProviderState("/textresult/${jobId}", "/textresult/dummyJobId")
.method("GET")
.willRespondWith()
.status(200)
.headers(Map.of("Content-Type", "text/plain"))
.body(PactDslRootValue.stringMatcher("^.+$", "whatever"))
.toPact();
}

@Test
@PactTestFor
void testApi(MockServer mockServer) throws IOException {
String response = Request.get(mockServer.getUrl() + "/textresult/dummyJobId")
.execute().returnContent().asString();
assertThat(response, is(equalTo("whatever")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package au.com.dius.pact.core.matchers

import au.com.dius.pact.core.model.ContentType
import au.com.dius.pact.core.model.OptionalBody
import au.com.dius.pact.core.model.generators.Generators
import au.com.dius.pact.core.model.matchingrules.MatchingRuleCategory
import au.com.dius.pact.core.model.matchingrules.RegexMatcher
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
Expand Down Expand Up @@ -33,10 +31,10 @@ class PlainTextContentMatcher : ContentMatcher {
}

fun compareText(expected: String, actual: String, context: MatchingContext): List<BodyItemMatchResult> {
val regexMatcher = context.matchers.matchingRules["$"]
val regex = regexMatcher?.rules?.first()
val matchers = context.matchers.matchingRules["$"]
val regexMatcher = matchers?.rules?.first()

if (regexMatcher == null || regexMatcher.rules.isEmpty() || regex !is RegexMatcher) {
if (matchers == null || matchers.rules.isEmpty() || regexMatcher !is RegexMatcher) {
logger.debug { "No regex for '$expected', using equality" }
return if (expected == actual) {
listOf(BodyItemMatchResult("$", emptyList()))
Expand All @@ -46,11 +44,12 @@ class PlainTextContentMatcher : ContentMatcher {
}
}

return if (actual.matches(Regex(regex.regex))) {
val regex = Regex(regexMatcher.regex, setOf(RegexOption.MULTILINE, RegexOption.DOT_MATCHES_ALL))
return if (regex.matches(actual)) {
emptyList()
} else {
listOf(BodyItemMatchResult("$", listOf(BodyMismatch(expected, actual,
"Expected body '$expected' to match '$actual' using regex '${regex.regex}' but did not match"))))
"Expected body '$expected' to match '$actual' using regex '${regexMatcher.regex}' but did not match"))))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,19 @@ class PlainTextContentMatcherSpec extends Specification {
'expected' | 'actual' | [body: ['$': [matchers: [[match: 'regex', regex: '\\w+']]]]] | true
'expected' | '12324' | [body: ['$': [matchers: [[match: 'integer']]]]] | false
}

@Unroll
def 'supports matching multiple line text'() {
expect:
matcher.compareText(expected, actual, new MatchingContext(
MatchingRulesImpl.fromJson(Json.INSTANCE.toJson(rules)).rulesForCategory('body'), true)
).every { it.result.empty }

where:

expected | actual | rules
'expected' | 'Hello\nWorld' | [body: ['$': [matchers: [[match: 'regex', regex: '(^\\w+$\n?)*']]]]]
'expected' | 'Hello\nWorld' | [body: ['$': [matchers: [[match: 'regex', regex: '^.+$']]]]]
'expected' | '12324\n12\n122' | [body: ['$': [matchers: [[match: 'regex', regex: '(^\\d+$\n?)+']]]]]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package au.com.dius.pact.provider.junit5

import au.com.dius.pact.provider.junitsupport.Provider
import au.com.dius.pact.provider.junitsupport.State
import au.com.dius.pact.provider.junitsupport.loader.PactFolder
import com.github.tomakehurst.wiremock.WireMockServer
import groovy.util.logging.Slf4j
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.TestTemplate
import org.junit.jupiter.api.extension.ExtendWith
import ru.lanwen.wiremock.ext.WiremockResolver
import ru.lanwen.wiremock.ext.WiremockUriResolver

import static com.github.tomakehurst.wiremock.client.WireMock.*

@Provider('TextProvider')
@PactFolder('pacts')
@ExtendWith([
WiremockResolver,
WiremockUriResolver
])
@Slf4j
class TextPlainProviderTest {

@TestTemplate
@ExtendWith(PactVerificationInvocationContextProvider)
void testTemplate(PactVerificationContext context) {
context.verifyInteraction()
}

@BeforeEach
void before(PactVerificationContext context, @WiremockResolver.Wiremock WireMockServer server,
@WiremockUriResolver.WiremockUri String uri) throws MalformedURLException {
context.setTarget(HttpTestTarget.fromUrl(new URL(uri)))

server.stubFor(
get(urlPathEqualTo('/textresult/12345'))
.willReturn(aResponse()
.withStatus(200)
.withHeader('Content-Type', 'text/plain')
.withBody('Hello \r\n World'))
)
}

@State('A text generation job finished successfully')
Map<String, Object> jobFinishedState() {
[jobId: '12345']
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"consumer": {
"name": "TextConsumer"
},
"interactions": [
{
"description": "A request to download text",
"providerStates": [
{
"name": "A text generation job finished successfully"
}
],
"request": {
"generators": {
"path": {
"dataType": "STRING",
"expression": "/textresult/${jobId}",
"type": "ProviderState"
}
},
"method": "GET",
"path": "/textresult/dummyJobId"
},
"response": {
"body": "whatever",
"headers": {
"Content-Type": "text/plain"
},
"matchingRules": {
"body": {
"$": {
"combine": "AND",
"matchers": [
{
"match": "regex",
"regex": "^.+$"
}
]
}
}
},
"status": 200
}
}
],
"metadata": {
"pact-jvm": {
"version": "4.3.12"
},
"pactSpecification": {
"version": "3.0.0"
}
},
"provider": {
"name": "TextProvider"
}
}

0 comments on commit 021c0c7

Please sign in to comment.