Skip to content

Commit 425d3e8

Browse files
authoredFeb 27, 2020
Merge branch 'master' into master
2 parents a33c921 + 7e81778 commit 425d3e8

File tree

8 files changed

+305
-21
lines changed

8 files changed

+305
-21
lines changed
 

‎consumer/pact-jvm-consumer/src/test/java/au/com/dius/pact/consumer/PactDslJsonBodyTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ public void testLargeDateFormat() {
265265
.date("lastUpdate", DATE_FORMAT)
266266
.date("creationDate", DATE_FORMAT);
267267
JSONObject jsonObject = (JSONObject) response.getBody();
268-
assertThat(jsonObject.get("lastUpdate").toString(), matchesPattern("\\w{3}\\.?, \\d{2} \\w{3}\\.? \\d{4} \\d{2}:00:00 \\+\\d+ GMT"));
268+
assertThat(jsonObject.get("lastUpdate").toString(), matchesPattern("\\w{2,3}\\.?, \\d{2} \\w{3}\\.? \\d{4} \\d{2}:00:00 \\+\\d+ GMT"));
269269
}
270270

271271
@Test

‎provider/pact-jvm-provider-junit/README.md

+9-10
Original file line numberDiff line numberDiff line change
@@ -324,32 +324,31 @@ public class PactJUnitTest {
324324
}
325325
```
326326

327-
#### Filtering by Provider State
327+
#### Interaction Filtering
328328

329329
You can filter the interactions that are executed by adding a `@PactFilter` annotation to your test class. The pact
330-
filter annotation will then only verify interactions that have a matching provider state. You can provide multiple
331-
states to match with.
330+
filter annotation will then only verify interactions that have a matching value, by default provider state.
331+
You can provide multiple values to match with.
332+
333+
The filter criteria is defined by the filter property. The filter must implement the
334+
`au.com.dius.pact.provider.junit.filter.InteractionFilter` interface. Also check the `InteractionFilter` interface
335+
for default filter implementations.
332336

333337
For example:
334338

335339
```java
336340
@RunWith(PactRunner.class)
337-
@Provider("Activity Service")
338-
@PactBroker(host = "localhost", port = "80")
339-
@PactFilter('Activity 100 exists in the database')
341+
@PactFilter("Activity 100 exists in the database")
340342
public class PactJUnitTest {
341343

342-
@TestTarget
343-
public final Target target = new HttpTarget(5050);
344-
345344
}
346345
```
347346

348347
You can also use regular expressions with the filter. For example:
349348

350349
```java
351350
@RunWith(PactRunner.class)
352-
@PactFilter('Activity \\d+ exists in the database')
351+
@PactFilter(values = {"^\\/somepath.*"}, filter = InteractionFilter.ByRequestPath.class)
353352
public class PactJUnitTest {
354353

355354
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package au.com.dius.pact.provider.junit.filter
2+
3+
import au.com.dius.pact.provider.junit.PactRunner
4+
import au.com.dius.pact.provider.junit.Provider
5+
import au.com.dius.pact.provider.junit.State
6+
import au.com.dius.pact.provider.junit.StateChangeAction
7+
import au.com.dius.pact.provider.junit.loader.PactFilter
8+
import au.com.dius.pact.provider.junit.loader.PactFolder
9+
import au.com.dius.pact.provider.junit.target.HttpTarget
10+
import au.com.dius.pact.provider.junit.target.TestTarget
11+
import com.github.restdriver.clientdriver.ClientDriverRule
12+
import com.github.restdriver.clientdriver.RestClientDriver.giveEmptyResponse
13+
import com.github.restdriver.clientdriver.RestClientDriver.onRequestTo
14+
import org.hamcrest.MatcherAssert.assertThat
15+
import org.hamcrest.Matchers.`is`
16+
import org.hamcrest.Matchers.equalTo
17+
import org.junit.AfterClass
18+
import org.junit.Before
19+
import org.junit.BeforeClass
20+
import org.junit.ClassRule
21+
import org.junit.runner.RunWith
22+
23+
@RunWith(PactRunner::class)
24+
@Provider("providerWithMultipleInteractions")
25+
@PactFolder("pacts")
26+
@PactFilter("^\\/data.*", filter = InteractionFilter.ByRequestPath::class)
27+
class FilterByRequestPathTest {
28+
@TestTarget
29+
val target = HttpTarget(port = 8332)
30+
31+
@Before
32+
fun before() {
33+
embeddedService.addExpectation(
34+
onRequestTo("/data").withAnyParams(), giveEmptyResponse()
35+
)
36+
}
37+
38+
@State("state1")
39+
fun state1() {
40+
executedStates.add("state1")
41+
}
42+
43+
@State("state1", action = StateChangeAction.TEARDOWN)
44+
fun state1Teardown() {
45+
executedStates.add("state1 Teardown")
46+
}
47+
48+
companion object {
49+
@ClassRule
50+
@JvmField
51+
val embeddedService = ClientDriverRule(8332)
52+
53+
val executedStates = mutableListOf<String>()
54+
55+
@BeforeClass
56+
@JvmStatic
57+
fun beforeTest() {
58+
executedStates.clear()
59+
}
60+
61+
@AfterClass
62+
@JvmStatic
63+
fun afterTest() {
64+
assertThat(executedStates, `is`(equalTo(listOf("state1", "state1 Teardown"))))
65+
}
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package au.com.dius.pact.provider.junit.filter
2+
3+
import au.com.dius.pact.provider.junit.PactRunner
4+
import au.com.dius.pact.provider.junit.Provider
5+
import au.com.dius.pact.provider.junit.State
6+
import au.com.dius.pact.provider.junit.StateChangeAction
7+
import au.com.dius.pact.provider.junit.loader.PactFilter
8+
import au.com.dius.pact.provider.junit.loader.PactFolder
9+
import au.com.dius.pact.provider.junit.target.HttpTarget
10+
import au.com.dius.pact.provider.junit.target.TestTarget
11+
import com.github.restdriver.clientdriver.ClientDriverRule
12+
import com.github.restdriver.clientdriver.RestClientDriver.giveEmptyResponse
13+
import com.github.restdriver.clientdriver.RestClientDriver.onRequestTo
14+
import org.hamcrest.MatcherAssert.assertThat
15+
import org.hamcrest.Matchers.`is`
16+
import org.hamcrest.Matchers.equalTo
17+
import org.junit.AfterClass
18+
import org.junit.Before
19+
import org.junit.BeforeClass
20+
import org.junit.ClassRule
21+
import org.junit.runner.RunWith
22+
23+
@RunWith(PactRunner::class)
24+
@Provider("providerWithMultipleInteractions")
25+
@PactFolder("pacts")
26+
@PactFilter("state1")
27+
class FilterStateByDefaultTest {
28+
@TestTarget
29+
val target = HttpTarget(port = 8332)
30+
31+
@Before
32+
fun before() {
33+
embeddedService.addExpectation(
34+
onRequestTo("/data").withAnyParams(), giveEmptyResponse()
35+
)
36+
}
37+
38+
@State("state1")
39+
fun state1() {
40+
executedStates.add("state1")
41+
}
42+
43+
@State("state1", action = StateChangeAction.TEARDOWN)
44+
fun state1Teardown() {
45+
executedStates.add("state1 Teardown")
46+
}
47+
48+
companion object {
49+
@ClassRule
50+
@JvmField
51+
val embeddedService = ClientDriverRule(8332)
52+
53+
val executedStates = mutableListOf<String>()
54+
55+
@BeforeClass
56+
@JvmStatic
57+
fun beforeTest() {
58+
executedStates.clear()
59+
}
60+
61+
@AfterClass
62+
@JvmStatic
63+
fun afterTest() {
64+
assertThat(executedStates, `is`(equalTo(listOf("state1", "state1 Teardown"))))
65+
}
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package au.com.dius.pact.provider.junit.filter;
2+
3+
import au.com.dius.pact.core.model.Interaction;
4+
import au.com.dius.pact.core.model.RequestResponseInteraction;
5+
6+
import java.util.Arrays;
7+
import java.util.function.Predicate;
8+
9+
public interface InteractionFilter<I extends Interaction> {
10+
11+
Predicate<I> buildPredicate(String[] values);
12+
13+
/**
14+
* Filter interactions by any of their provider state. If one matches any of the values, the interaction
15+
* is kept and verified.
16+
*/
17+
class ByProviderState<I extends Interaction> implements InteractionFilter<I> {
18+
19+
@Override
20+
public Predicate<I> buildPredicate(String[] values) {
21+
return interaction -> Arrays.stream(values).anyMatch(
22+
value -> interaction.getProviderStates().stream().anyMatch(
23+
state -> state .getName() != null && state.getName().matches(value)
24+
)
25+
);
26+
}
27+
}
28+
29+
/**
30+
* Filter interactions by their request path, e.g. with value "^\\/somepath.*".
31+
*/
32+
class ByRequestPath<I extends Interaction> implements InteractionFilter<I> {
33+
34+
@Override
35+
public Predicate<I> buildPredicate(String[] values) {
36+
return interaction -> {
37+
if (interaction instanceof RequestResponseInteraction) {
38+
return Arrays.stream(values).anyMatch(value ->
39+
((RequestResponseInteraction) interaction).getRequest().getPath().matches(value)
40+
);
41+
} else {
42+
return false;
43+
}
44+
};
45+
}
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,33 @@
11
package au.com.dius.pact.provider.junit.loader;
22

3+
import au.com.dius.pact.provider.junit.filter.InteractionFilter;
4+
35
import java.lang.annotation.ElementType;
46
import java.lang.annotation.Inherited;
57
import java.lang.annotation.Retention;
68
import java.lang.annotation.RetentionPolicy;
79
import java.lang.annotation.Target;
810

911
/**
10-
* Annotation to filter pacts by provider state. Supports regular expressions.
12+
* Annotation to filter pacts. The default implementation is to filter by provider state.
13+
* The filter supports regular expressions.
1114
*/
1215
@Retention(RetentionPolicy.RUNTIME)
1316
@Target(ElementType.TYPE)
1417
@Inherited
1518
public @interface PactFilter {
19+
20+
/**
21+
* Values to use for filtering. Regular expressions are allowed, like "^state \\d".
22+
* If none of the provided values matches, the interaction is not verified.
23+
*/
1624
String[] value();
25+
26+
/**
27+
* Use this class as filter implementation. The class must implement the {@link InteractionFilter}
28+
* interface and provide a default constructor.
29+
*
30+
* The default value is filtering by provider state.
31+
*/
32+
Class<? extends InteractionFilter> filter() default InteractionFilter.ByProviderState.class;
1733
}

‎provider/pact-jvm-provider/src/main/kotlin/au/com/dius/pact/provider/junit/JUnitProviderTestSupport.kt

+9-9
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,24 @@ import au.com.dius.pact.core.model.Interaction
55
import au.com.dius.pact.core.model.Pact
66
import au.com.dius.pact.core.support.isNotEmpty
77
import au.com.dius.pact.provider.ProviderVerifier
8+
import au.com.dius.pact.provider.junit.filter.InteractionFilter
89
import au.com.dius.pact.provider.junit.loader.OverrideablePactLoader
910
import au.com.dius.pact.provider.junit.loader.PactFilter
1011
import au.com.dius.pact.provider.junit.loader.PactLoader
1112
import mu.KLogging
1213
import org.apache.commons.lang3.StringUtils
1314
import org.apache.commons.lang3.exception.ExceptionUtils
14-
import java.util.function.Predicate
15+
import kotlin.reflect.full.createInstance
1516

1617
object JUnitProviderTestSupport : KLogging() {
1718
fun <I> filterPactsByAnnotations(pacts: List<Pact<I>>, testClass: Class<*>): List<Pact<I>> where I : Interaction {
18-
val pactFilterValues = testClass.getAnnotation(PactFilter::class.java)?.value
19-
return if (pactFilterValues != null && pactFilterValues.any { it.isNotEmpty() }) {
20-
pacts.map { pact ->
21-
FilteredPact(pact, Predicate { interaction ->
22-
pactFilterValues.any { value -> interaction.providerStates.any { it.matches(value) } }
23-
})
24-
}.filter { pact -> pact.interactions.isNotEmpty() }
25-
} else pacts
19+
val pactFilter = testClass.getAnnotation(PactFilter::class.java) ?: return pacts
20+
if (pactFilter.value == null || pactFilter.value.all { it.isEmpty() }) return pacts
21+
22+
val interactionFilter = pactFilter.filter.createInstance() as InteractionFilter<I>
23+
return pacts.map { pact ->
24+
FilteredPact(pact, interactionFilter.buildPredicate(pactFilter.value))
25+
}.filter { pact -> pact.interactions.isNotEmpty() }
2626
}
2727

2828
@JvmStatic
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package au.com.dius.pact.provider.junit.filter;
2+
3+
import au.com.dius.pact.core.model.Interaction;
4+
import au.com.dius.pact.core.model.ProviderState;
5+
import au.com.dius.pact.core.model.Request;
6+
import au.com.dius.pact.core.model.RequestResponseInteraction;
7+
import au.com.dius.pact.core.model.messaging.Message;
8+
import org.junit.Assert;
9+
import org.junit.jupiter.api.Nested;
10+
import org.junit.jupiter.api.Test;
11+
12+
import java.lang.reflect.InvocationTargetException;
13+
import java.util.Arrays;
14+
import java.util.Collections;
15+
16+
class InteractionFilterTest {
17+
18+
@Nested
19+
class ByProviderState {
20+
21+
InteractionFilter<? super Interaction> interactionFilter =
22+
InteractionFilter.ByProviderState.class.getDeclaredConstructor().newInstance();
23+
24+
ByProviderState() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
25+
}
26+
27+
@Test
28+
public void filterRequestResponseInteraction() {
29+
RequestResponseInteraction interaction = new RequestResponseInteraction(
30+
"test",
31+
Arrays.asList(new ProviderState("state1"), new ProviderState("state2"))
32+
);
33+
34+
Assert.assertTrue(interactionFilter.buildPredicate(new String[]{"state1"}).test(interaction));
35+
Assert.assertFalse(interactionFilter.buildPredicate(new String[]{"noop"}).test(interaction));
36+
Assert.assertTrue(interactionFilter.buildPredicate(new String[]{"state1", "state2"}).test(interaction));
37+
Assert.assertTrue(interactionFilter.buildPredicate(new String[]{"noop", "state2"}).test(interaction));
38+
Assert.assertTrue(interactionFilter.buildPredicate(new String[]{"state1", "state2"}).test(interaction));
39+
Assert.assertFalse(interactionFilter.buildPredicate(new String[]{""}).test(interaction));
40+
}
41+
42+
@Test
43+
public void filterMessageInteraction() {
44+
Message interaction = new Message(
45+
"test",
46+
Arrays.asList(new ProviderState("state1"), new ProviderState("state2"))
47+
);
48+
49+
Assert.assertTrue(interactionFilter.buildPredicate(new String[]{"state1"}).test(interaction));
50+
Assert.assertFalse(interactionFilter.buildPredicate(new String[]{"noop"}).test(interaction));
51+
Assert.assertTrue(interactionFilter.buildPredicate(new String[]{"state1", "state2"}).test(interaction));
52+
Assert.assertTrue(interactionFilter.buildPredicate(new String[]{"noop", "state2"}).test(interaction));
53+
Assert.assertTrue(interactionFilter.buildPredicate(new String[]{"state1", "state2"}).test(interaction));
54+
Assert.assertFalse(interactionFilter.buildPredicate(new String[]{""}).test(interaction));
55+
}
56+
}
57+
58+
@Nested
59+
class ByRequestPath {
60+
61+
InteractionFilter<? super Interaction> interactionFilter =
62+
InteractionFilter.ByRequestPath.class.getDeclaredConstructor().newInstance();
63+
64+
ByRequestPath() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
65+
}
66+
67+
@Test
68+
public void filterRequestResponseInteraction() {
69+
RequestResponseInteraction interaction = new RequestResponseInteraction(
70+
"test",
71+
Collections.emptyList(),
72+
new Request("GET", "/some-path")
73+
);
74+
75+
Assert.assertTrue(interactionFilter.buildPredicate(new String[]{"\\/some-path"}).test(interaction));
76+
Assert.assertFalse(interactionFilter.buildPredicate(new String[]{"other"}).test(interaction));
77+
Assert.assertTrue(interactionFilter.buildPredicate(new String[]{"\\/some-path.*"}).test(interaction));
78+
Assert.assertTrue(interactionFilter.buildPredicate(new String[]{".*some-path"}).test(interaction));
79+
Assert.assertFalse(interactionFilter.buildPredicate(new String[]{""}).test(interaction));
80+
}
81+
82+
@Test
83+
public void filterMessageInteraction() {
84+
Message interaction = new Message("test", Collections.emptyList());
85+
Assert.assertFalse(interactionFilter.buildPredicate(new String[]{".*"}).test(interaction));
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)
Please sign in to comment.