Skip to content

Commit 51d399e

Browse files
committed
feat: Add support for synchronous plugin interactions
1 parent a47b6fc commit 51d399e

10 files changed

+185
-16
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System;
2+
3+
namespace PactNet;
4+
5+
public interface ISynchronousPluginPactBuilderV4 : IPactBuilder, IDisposable
6+
{
7+
ISynchronousPluginRequestBuilderV4 UponReceiving(string description);
8+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Collections.Generic;
2+
3+
namespace PactNet;
4+
5+
public interface ISynchronousPluginRequestBuilderV4
6+
{
7+
/// <summary>
8+
/// Add a provider state
9+
/// </summary>
10+
/// <param name="description">Provider state description</param>
11+
/// <returns>Fluent builder</returns>
12+
ISynchronousPluginRequestBuilderV4 Given(string description);
13+
14+
/// <summary>
15+
/// Add a provider state with a parameter to the interaction
16+
/// </summary>
17+
/// <param name="description">Provider state description</param>
18+
/// <param name="name">Parameter name</param>
19+
/// <param name="value">Parameter value</param>
20+
ISynchronousPluginRequestBuilderV4 Given(string description, string name, string value);
21+
22+
/// <summary>
23+
/// Add plugin interaction content
24+
/// </summary>
25+
/// <param name="contentType">Content type</param>
26+
/// <param name="content">A dictionary containing the plugin content.</param>
27+
ISynchronousPluginRequestBuilderV4 WithContent(string contentType, Dictionary<string, object> content);
28+
}

src/PactNet/Drivers/IPactDriver.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
using PactNet.Drivers.Http;
22
using PactNet.Drivers.Message;
3+
using PactNet.Drivers.Plugins;
34
using PactNet.Interop;
45

56
namespace PactNet.Drivers
67
{
78
/// <summary>
8-
/// Driver for creating a new pact and
9+
/// Driver for creating a new pact and
910
/// </summary>
1011
internal interface IPactDriver
1112
{
@@ -32,5 +33,16 @@ internal interface IPactDriver
3233
/// </summary>
3334
/// <returns>Logs</returns>
3435
string DriverLogs();
36+
37+
/// <summary>
38+
/// Create a new plugin pact
39+
/// </summary>
40+
/// <param name="consumerName">Consumer name</param>
41+
/// <param name="providerName">Provider name</param>
42+
/// <param name="pluginName">Plugin name</param>
43+
/// <param name="pluginVersion">Plugin version</param>
44+
/// <param name="version">Specification version</param>
45+
/// <returns>Plugin pact driver</returns>
46+
IPluginPactDriver NewPluginPact(string consumerName, string providerName, string pluginName, string pluginVersion, PactSpecification version);
3547
}
3648
}
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace PactNet.Drivers.Plugins
1+
using System.Collections.Generic;
2+
3+
namespace PactNet.Drivers.Plugins
24
{
35
/// <summary>
46
/// Driver for plugin interactions
@@ -9,7 +11,7 @@ internal interface IPluginInteractionDriver : IProviderStateDriver
911
/// Add a plugin interaction content
1012
/// </summary>
1113
/// <param name="contentType">Content type</param>
12-
/// <param name="content">Content</param>
13-
void WithContent(string contentType, string content);
14+
/// <param name="content">A dictionary containing the plugin content.</param>
15+
void WithContent(string contentType, Dictionary<string, object> content);
1416
}
1517
}

src/PactNet/Drivers/Plugins/IPluginPactDriver.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
namespace PactNet.Drivers.Plugins
1+
using System;
2+
3+
namespace PactNet.Drivers.Plugins
24
{
35
/// <summary>
46
/// Driver for plugin-based pacts
57
/// </summary>
6-
internal interface IPluginPactDriver : ICompletedPactDriver
8+
internal interface IPluginPactDriver : ICompletedPactDriver, IDisposable
79
{
810
/// <summary>
911
/// Create a new sync interaction on the current pact

src/PactNet/Drivers/Plugins/PluginInteractionDriver.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using PactNet.Exceptions;
1+
using System.Collections.Generic;
2+
using System.Text.Json;
3+
using PactNet.Exceptions;
24
using PactNet.Interop;
35

46
namespace PactNet.Drivers.Plugins
@@ -36,13 +38,13 @@ public void GivenWithParam(string description, string name, string value)
3638
=> NativeInterop.GivenWithParam(this.interaction, description, name, value).CheckInteropSuccess();
3739

3840
/// <summary>
39-
/// Add a plugin interaction content
41+
/// Add plugin interaction content
4042
/// </summary>
4143
/// <param name="contentType">Content type</param>
42-
/// <param name="content">Content</param>
43-
public void WithContent(string contentType, string content)
44+
/// <param name="content">A dictionary containing the plugin content.</param>
45+
public void WithContent(string contentType, Dictionary<string, object> content)
4446
{
45-
uint code = NativeInterop.InteractionContents(this.interaction, InteractionPart.Request, contentType, content);
47+
uint code = NativeInterop.InteractionContents(this.interaction, InteractionPart.Request, contentType, JsonSerializer.Serialize(content));
4648

4749
if (code != 0)
4850
{

src/PactNet/Drivers/Plugins/PluginPactDriver.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
using PactNet.Interop;
1+
using System;
2+
using PactNet.Interop;
23

34
namespace PactNet.Drivers.Plugins
45
{
56
/// <summary>
67
/// Driver for plugin-based pacts
78
/// </summary>
8-
internal class PluginPactDriver : AbstractPactDriver, IPluginPactDriver
9+
internal class PluginPactDriver : AbstractPactDriver, IPluginPactDriver, IDisposable
910
{
1011
/// <summary>
1112
/// Initialize a new instance of the <see cref="PluginPactDriver"/> class.
@@ -25,5 +26,31 @@ public IPluginInteractionDriver NewSyncInteraction(string description)
2526
InteractionHandle interaction = NativeInterop.NewSyncMessageInteraction(this.pact, description);
2627
return new PluginInteractionDriver(interaction);
2728
}
29+
30+
31+
/// <summary>
32+
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
33+
/// </summary>
34+
public void Dispose()
35+
{
36+
this.ReleaseUnmanagedResources();
37+
GC.SuppressFinalize(this);
38+
}
39+
40+
/// <summary>
41+
/// Allows an object to try to free resources and perform other cleanup operations before it is reclaimed by garbage collection.
42+
/// </summary>
43+
~PluginPactDriver()
44+
{
45+
this.ReleaseUnmanagedResources();
46+
}
47+
48+
/// <summary>
49+
/// Release unmanaged resources
50+
/// </summary>
51+
private void ReleaseUnmanagedResources()
52+
{
53+
NativeInterop.CleanupPlugins(pact);
54+
}
2855
}
2956
}

src/PactNet/PactExtensions.cs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using PactNet.Drivers;
22
using PactNet.Drivers.Http;
33
using PactNet.Drivers.Message;
4+
using PactNet.Drivers.Plugins;
45
using PactNet.Interop;
56
using PactNet.Models;
67

@@ -23,7 +24,8 @@ public static class PactExtensions
2324
/// It is advised that the port is not specified whenever possible to allow PactNet to allocate a port dynamically
2425
/// and ensure there are no port clashes
2526
/// </remarks>
26-
public static IPactBuilderV2 WithHttpInteractions(this IPactV2 pact, int? port = null, IPAddress host = IPAddress.Loopback)
27+
public static IPactBuilderV2 WithHttpInteractions(this IPactV2 pact, int? port = null,
28+
IPAddress host = IPAddress.Loopback)
2729
{
2830
pact.Config.LogLevel.LogToBuffer();
2931

@@ -46,7 +48,8 @@ public static IPactBuilderV2 WithHttpInteractions(this IPactV2 pact, int? port =
4648
/// It is advised that the port is not specified whenever possible to allow PactNet to allocate a port dynamically
4749
/// and ensure there are no port clashes
4850
/// </remarks>
49-
public static IPactBuilderV3 WithHttpInteractions(this IPactV3 pact, int? port = null, IPAddress host = IPAddress.Loopback)
51+
public static IPactBuilderV3 WithHttpInteractions(this IPactV3 pact, int? port = null,
52+
IPAddress host = IPAddress.Loopback)
5053
{
5154
pact.Config.LogLevel.LogToBuffer();
5255

@@ -69,7 +72,8 @@ public static IPactBuilderV3 WithHttpInteractions(this IPactV3 pact, int? port =
6972
/// It is advised that the port is not specified whenever possible to allow PactNet to allocate a port dynamically
7073
/// and ensure there are no port clashes
7174
/// </remarks>
72-
public static IPactBuilderV4 WithHttpInteractions(this IPactV4 pact, int? port = null, IPAddress host = IPAddress.Loopback)
75+
public static IPactBuilderV4 WithHttpInteractions(this IPactV4 pact, int? port = null,
76+
IPAddress host = IPAddress.Loopback)
7377
{
7478
pact.Config.LogLevel.LogToBuffer();
7579

@@ -111,5 +115,26 @@ public static IMessagePactBuilderV4 WithMessageInteractions(this IPactV4 pact)
111115
var builder = new MessagePactBuilder(messagePact, pact.Config, PactSpecification.V4);
112116
return builder;
113117
}
118+
119+
/// <summary>
120+
/// Establish a new pact with synchronous plugin interactions.
121+
/// </summary>
122+
/// <param name="pact"></param>
123+
/// <param name="pluginName">Plugin name</param>
124+
/// <param name="pluginVersion">Plugin version</param>
125+
/// <param name="transport">The transport to use (i.e. http, https, grpc). Must be a valid UTF-8 NULL-terminated string, or NULL or empty, in which case http will be used.</param>
126+
/// <param name="port">Port for the mock server. If null, one will be assigned automatically</param>
127+
/// <param name="host">Host for the mock server</param>
128+
/// <returns></returns>
129+
public static ISynchronousPluginPactBuilderV4 WithSynchronousPluginInteractions(this IPactV4 pact,
130+
string pluginName, string pluginVersion, string transport = null, int? port = null,
131+
IPAddress host = IPAddress.Loopback)
132+
{
133+
pact.Config.LogLevel.LogToBuffer();
134+
IPactDriver driver = new PactDriver();
135+
var pluginDriver = driver.NewPluginPact(pact.Consumer, pact.Provider, pluginName, pluginVersion,
136+
PactSpecification.V4);
137+
return new SynchronousPluginPactBuilder(pluginDriver, pact.Config, port, host, transport);
138+
}
114139
}
115140
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using PactNet.Drivers.Plugins;
2+
using PactNet.Models;
3+
4+
namespace PactNet;
5+
6+
internal class SynchronousPluginPactBuilder(
7+
IPluginPactDriver pact,
8+
PactConfig config,
9+
int? port,
10+
IPAddress host,
11+
string transport = null)
12+
: AbstractPactBuilder(pact, config, port, host, transport), ISynchronousPluginPactBuilderV4
13+
{
14+
public ISynchronousPluginRequestBuilderV4 UponReceiving(string description)
15+
{
16+
return new SynchronousPluginRequestBuilder(pact.NewSyncInteraction(description));
17+
}
18+
19+
public void Dispose() => pact?.Dispose();
20+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System.Collections.Generic;
2+
using PactNet.Drivers.Plugins;
3+
4+
namespace PactNet;
5+
6+
internal class SynchronousPluginRequestBuilder(IPluginInteractionDriver interactionDriver)
7+
: ISynchronousPluginRequestBuilderV4
8+
{
9+
10+
/// <summary>
11+
/// Add a provider state
12+
/// </summary>
13+
/// <param name="description">Provider state description</param>
14+
/// <returns>Fluent builder</returns>
15+
public ISynchronousPluginRequestBuilderV4 Given(string description)
16+
{
17+
interactionDriver.Given(description);
18+
return this;
19+
}
20+
21+
/// <summary>
22+
/// Add a provider state with a parameter to the interaction
23+
/// </summary>
24+
/// <param name="description">Provider state description</param>
25+
/// <param name="name">Parameter name</param>
26+
/// <param name="value">Parameter value</param>
27+
public ISynchronousPluginRequestBuilderV4 Given(string description, string name, string value)
28+
{
29+
interactionDriver.GivenWithParam(description, name, value);
30+
return this;
31+
}
32+
33+
/// <summary>
34+
/// Add plugin interaction content
35+
/// </summary>
36+
/// <param name="contentType">Content type</param>
37+
/// <param name="content">A dictionary containing the plugin content.</param>
38+
public ISynchronousPluginRequestBuilderV4 WithContent(string contentType, Dictionary<string, object> content)
39+
{
40+
interactionDriver.WithContent(contentType, content);
41+
return this;
42+
}
43+
}

0 commit comments

Comments
 (0)