diff --git a/src/PactNet.Abstractions/Generators/Generate.cs b/src/PactNet.Abstractions/Generators/Generate.cs
new file mode 100644
index 00000000..405a4b25
--- /dev/null
+++ b/src/PactNet.Abstractions/Generators/Generate.cs
@@ -0,0 +1,15 @@
+namespace PactNet.Generators;
+
+public static class Generate
+{
+ ///
+ /// Generates a value that is looked up from the provider state context using the given expression
+ ///
+ /// Example value
+ /// String expression
+ /// Generator
+ public static IGenerator ProviderState(string example, string expression)
+ {
+ return new ProviderStateGenerator(example, expression);
+ }
+}
diff --git a/src/PactNet.Abstractions/Generators/IGenerator.cs b/src/PactNet.Abstractions/Generators/IGenerator.cs
new file mode 100644
index 00000000..454a41f4
--- /dev/null
+++ b/src/PactNet.Abstractions/Generators/IGenerator.cs
@@ -0,0 +1,13 @@
+using Newtonsoft.Json;
+using PactNet.Matchers;
+
+namespace PactNet.Generators;
+
+public interface IGenerator : IMatcher
+{
+ ///
+ /// Type of the generator
+ ///
+ [JsonProperty("pact:generator:type")]
+ string GeneratorType { get; }
+}
diff --git a/src/PactNet.Abstractions/Generators/ProviderStateGenerator.cs b/src/PactNet.Abstractions/Generators/ProviderStateGenerator.cs
new file mode 100644
index 00000000..498575c7
--- /dev/null
+++ b/src/PactNet.Abstractions/Generators/ProviderStateGenerator.cs
@@ -0,0 +1,24 @@
+using Newtonsoft.Json;
+
+namespace PactNet.Generators;
+
+public class ProviderStateGenerator : IGenerator
+{
+ public string Type => "type";
+
+ public dynamic Value { get; }
+
+ public string GeneratorType => "ProviderState";
+
+ ///
+ /// Expression to lookup in provider state context
+ ///
+ [JsonProperty("expression")]
+ public string Expression { get; }
+
+ public ProviderStateGenerator(dynamic example, string expression)
+ {
+ this.Value = example;
+ this.Expression = expression;
+ }
+}
diff --git a/src/PactNet.Abstractions/IRequestBuilder.cs b/src/PactNet.Abstractions/IRequestBuilder.cs
index c4350764..e5b349a4 100644
--- a/src/PactNet.Abstractions/IRequestBuilder.cs
+++ b/src/PactNet.Abstractions/IRequestBuilder.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Net.Http;
using Newtonsoft.Json;
+using PactNet.Generators;
using PactNet.Matchers;
namespace PactNet
@@ -116,6 +117,22 @@ public interface IRequestBuilderV3
/// Fluent builder
IRequestBuilderV3 WithRequest(string method, string path);
+ ///
+ /// Set the request
+ ///
+ /// Request method
+ /// Path value generator
+ /// Fluent builder
+ IRequestBuilderV3 WithRequest(HttpMethod method, IGenerator generator);
+
+ ///
+ /// Set the request
+ ///
+ /// Request method
+ /// Path value generator
+ /// Fluent builder
+ IRequestBuilderV3 WithRequest(string method, IGenerator generator);
+
///
/// Add a query string parameter
///
@@ -125,6 +142,15 @@ public interface IRequestBuilderV3
/// You can add a query parameter with the same key multiple times
IRequestBuilderV3 WithQuery(string key, string value);
+ ///
+ /// Add a query string parameter
+ ///
+ /// Query parameter key
+ /// Query parameter value generator
+ /// Fluent builder
+ /// You can add a query parameter with the same key multiple times
+ IRequestBuilderV3 WithQuery(string key, IGenerator generator);
+
///
/// Add a request header
///
diff --git a/src/PactNet/RequestBuilder.cs b/src/PactNet/RequestBuilder.cs
index 9716700b..5942fad6 100644
--- a/src/PactNet/RequestBuilder.cs
+++ b/src/PactNet/RequestBuilder.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Net.Http;
using Newtonsoft.Json;
+using PactNet.Generators;
using PactNet.Interop;
using PactNet.Matchers;
@@ -145,6 +146,15 @@ IRequestBuilderV3 IRequestBuilderV3.Given(string providerState, IDictionary this.WithRequest(method, path);
+ ///
+ /// Set the request
+ ///
+ /// Request method
+ /// Path value generator
+ /// Fluent builder
+ IRequestBuilderV3 IRequestBuilderV3.WithRequest(HttpMethod method, IGenerator generator)
+ => this.WithRequest(method, generator);
+
///
/// Set the request
///
@@ -154,6 +164,15 @@ IRequestBuilderV3 IRequestBuilderV3.WithRequest(HttpMethod method, string path)
IRequestBuilderV3 IRequestBuilderV3.WithRequest(string method, string path)
=> this.WithRequest(method, path);
+ ///
+ /// Set the request
+ ///
+ /// Request method
+ /// Path value generator
+ /// Fluent builder
+ IRequestBuilderV3 IRequestBuilderV3.WithRequest(string method, IGenerator generator)
+ => this.WithRequest(method, generator);
+
///
/// Add a query string parameter
///
@@ -164,6 +183,16 @@ IRequestBuilderV3 IRequestBuilderV3.WithRequest(string method, string path)
IRequestBuilderV3 IRequestBuilderV3.WithQuery(string key, string value)
=> this.WithQuery(key, value);
+ ///
+ /// Add a query string parameter
+ ///
+ /// Query parameter key
+ /// Query parameter value generator
+ /// Fluent builder
+ /// You can add a query parameter with the same key multiple times
+ IRequestBuilderV3 IRequestBuilderV3.WithQuery(string key, IGenerator generator)
+ => this.WithQuery(key, generator);
+
///
/// Add a request header
///
@@ -244,6 +273,15 @@ internal RequestBuilder Given(string providerState, IDictionary
internal RequestBuilder WithRequest(HttpMethod method, string path)
=> this.WithRequest(method.Method, path);
+ ///
+ /// Set the request
+ ///
+ /// Request method
+ /// Path value generator
+ /// Fluent builder
+ internal RequestBuilder WithRequest(HttpMethod method, IGenerator generator)
+ => this.WithRequest(method.Method, generator);
+
///
/// Set the request
///
@@ -258,6 +296,18 @@ internal RequestBuilder WithRequest(string method, string path)
return this;
}
+ ///
+ /// Set the request
+ ///
+ /// Request method
+ /// Path value generator
+ /// Fluent builder
+ internal RequestBuilder WithRequest(string method, IGenerator generator)
+ {
+ var serialised = JsonConvert.SerializeObject(generator, this.defaultSettings);
+ return this.WithRequest(method, serialised);
+ }
+
///
/// Add a query string parameter
///
@@ -274,6 +324,20 @@ internal RequestBuilder WithQuery(string key, string value)
return this;
}
+ ///
+ /// Add a query string parameter
+ ///
+ /// Query parameter key
+ /// Query parameter value generator
+ /// Fluent builder
+ /// You can add a query parameter with the same key multiple times
+ internal RequestBuilder WithQuery(string key, IGenerator generator)
+ {
+ var serialised = JsonConvert.SerializeObject(generator, this.defaultSettings);
+ return this.WithQuery(key, serialised);
+ }
+
+
///
/// Add a request header
///
diff --git a/tests/PactNet.Abstractions.Tests/Generators/GenerateTests.cs b/tests/PactNet.Abstractions.Tests/Generators/GenerateTests.cs
new file mode 100644
index 00000000..8e0c5ff4
--- /dev/null
+++ b/tests/PactNet.Abstractions.Tests/Generators/GenerateTests.cs
@@ -0,0 +1,20 @@
+using FluentAssertions;
+using PactNet.Generators;
+using Xunit;
+
+namespace PactNet.Abstractions.Tests.Generators
+{
+ public class GenerateTests
+ {
+ [Fact]
+ public void ProviderState_WhenCalled_ReturnsGenerator()
+ {
+ const string example = "/ticket/WO1FN";
+ const string expression = @"/ticket/${pnr}";
+
+ var matcher = Generate.ProviderState(example, expression);
+
+ matcher.Should().BeEquivalentTo(new ProviderStateGenerator(example, expression));
+ }
+ }
+}
diff --git a/tests/PactNet.Abstractions.Tests/Generators/ProviderStateGeneratorTests.cs b/tests/PactNet.Abstractions.Tests/Generators/ProviderStateGeneratorTests.cs
new file mode 100644
index 00000000..c0a3c393
--- /dev/null
+++ b/tests/PactNet.Abstractions.Tests/Generators/ProviderStateGeneratorTests.cs
@@ -0,0 +1,25 @@
+using FluentAssertions;
+using Newtonsoft.Json;
+using PactNet.Generators;
+using PactNet.Matchers;
+using Xunit;
+
+namespace PactNet.Abstractions.Tests.Generators
+{
+ public class ProviderStateGeneratorTests
+ {
+ [Fact]
+ public void Ctor_WhenCalled_SerialisesCorrectly()
+ {
+ const string example = "hello@tester.com";
+ const string expression = "${email}";
+
+ var generator = new ProviderStateGenerator(example, expression);
+
+ string actual = JsonConvert.SerializeObject(generator);
+ string expected = $@"{{""pact:matcher:type"":""type"",""value"":""{example}"",""pact:generator:type"":""ProviderState"",""expression"":""{expression}""}}";
+
+ actual.Should().BeEquivalentTo(expected);
+ }
+ }
+}
diff --git a/tests/PactNet.Tests/RequestBuilderTests.cs b/tests/PactNet.Tests/RequestBuilderTests.cs
index 0a3851a7..f3357fea 100644
--- a/tests/PactNet.Tests/RequestBuilderTests.cs
+++ b/tests/PactNet.Tests/RequestBuilderTests.cs
@@ -6,6 +6,7 @@
using Moq;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
+using PactNet.Generators;
using PactNet.Interop;
using Xunit;
using Match = PactNet.Matchers.Match;
@@ -66,6 +67,18 @@ public void WithRequest_HttpMethod_AddsRequest()
this.mockServer.Verify(s => s.WithRequest(this.handle, "POST", "/some/path"));
}
+ [Fact]
+ public void WithRequest_HttpMethod_ProviderStateGenerator_AddsRequest()
+ {
+ const string example = "/some/example-path";
+ const string expression = "/some/${path}";
+ const string expectedValue = $@"{{""pact:matcher:type"":""type"",""value"":""{example}"",""pact:generator:type"":""ProviderState"",""expression"":""{expression}""}}";
+
+ this.builder.WithRequest(HttpMethod.Post, Generate.ProviderState(example, expression));
+
+ this.mockServer.Verify(s => s.WithRequest(this.handle, "POST", expectedValue));
+ }
+
[Fact]
public void WithRequest_String_AddsRequest()
{
@@ -74,6 +87,18 @@ public void WithRequest_String_AddsRequest()
this.mockServer.Verify(s => s.WithRequest(this.handle, "POST", "/some/path"));
}
+ [Fact]
+ public void WithRequest_String_ProviderStateGenerator_AddsRequest()
+ {
+ const string example = "/some/example-path";
+ const string expression = "/some/${path}";
+ const string expectedValue = $@"{{""pact:matcher:type"":""type"",""value"":""{example}"",""pact:generator:type"":""ProviderState"",""expression"":""{expression}""}}";
+
+ this.builder.WithRequest("POST", Generate.ProviderState(example, expression));
+
+ this.mockServer.Verify(s => s.WithRequest(this.handle, "POST", expectedValue));
+ }
+
[Fact]
public void WithQuery_WhenCalled_AddsQueryParam()
{
@@ -82,6 +107,15 @@ public void WithQuery_WhenCalled_AddsQueryParam()
this.mockServer.Verify(s => s.WithQueryParameter(this.handle, "name", "value", 0));
}
+ [Fact]
+ public void WithQuery_Generator_WhenCalled_AddsQueryParam()
+ {
+ const string expectedValue = $@"{{""pact:matcher:type"":""type"",""value"":""example"",""pact:generator:type"":""ProviderState"",""expression"":""${{value}}""}}";
+
+ this.builder.WithQuery("name", Generate.ProviderState("example", "${value}"));
+ this.mockServer.Verify(s => s.WithQueryParameter(this.handle, "name", expectedValue, 0));
+ }
+
[Fact]
public void WithQuery_RepeatedQuery_SetsIndex()
{
@@ -94,6 +128,20 @@ public void WithQuery_RepeatedQuery_SetsIndex()
this.mockServer.Verify(s => s.WithQueryParameter(this.handle, "other", "value", 0));
}
+ [Fact]
+ public void WithQuery_Generator_RepeatedQuery_SetsIndex()
+ {
+ const string expectedValue2 = $@"{{""pact:matcher:type"":""type"",""value"":""value2"",""pact:generator:type"":""ProviderState"",""expression"":""${{value}}""}}";
+
+ this.builder.WithQuery("name", "value1");
+ this.builder.WithQuery("name", Generate.ProviderState("value2", "${value}"));
+ this.builder.WithQuery("other", "value");
+
+ this.mockServer.Verify(s => s.WithQueryParameter(this.handle, "name", "value1", 0));
+ this.mockServer.Verify(s => s.WithQueryParameter(this.handle, "name", expectedValue2, 1));
+ this.mockServer.Verify(s => s.WithQueryParameter(this.handle, "other", "value", 0));
+ }
+
[Fact]
public void WithHeader_Matcher_WhenCalled_AddsSerialisedHeaderParam()
{
@@ -104,6 +152,16 @@ public void WithHeader_Matcher_WhenCalled_AddsSerialisedHeaderParam()
this.mockServer.Verify(s => s.WithRequestHeader(this.handle, "name", expectedValue, 0));
}
+ [Fact]
+ public void WithHeader_Generator_WhenCalled_AddsSerialisedHeaderParam()
+ {
+ var expectedValue = "{\"pact:matcher:type\":\"type\",\"value\":\"header\",\"pact:generator:type\":\"ProviderState\",\"expression\":\"${header}\"}";
+
+ this.builder.WithHeader("name", Generate.ProviderState("header", "${header}"));
+
+ this.mockServer.Verify(s => s.WithRequestHeader(this.handle, "name", expectedValue, 0));
+ }
+
[Fact]
public void WithHeader_RepeatedMatcherHeader_SetsIndex()
{