Template project to help bootstrap a new Pact Plugin for the Pact framework.
Features:
- Stubbed gRPC methods ready to implement
- Automated release procedure
- Support for recommended common platform/targets
- Levelled logging for observability
TODO
- Support Matchers and Generators (requires FFI package support)
βββ README.md # This file!
βββ Makefile # Build configuration (β
fill me in!)
βββ Program.cs # The gRPC server handler
βββ Services
β βββ PactPluginService.cs # The gRPC server implementation (β
fill me in!)
βββ Protos # Plugin configuration file - set to use the binary distributable
β βββ plugin.proto # Location of protobuf for the Pact Plugin Framework
βββ pact-plugin.json
βββ build # This is where your binary distributions will be output
βββ GrpcPactPlugin # This is will be the name of the plugin, yours will change
βββ GrpcPactPlugin.csproj # The project config file
βββ Properties
β βββ launchSettings.json # The project dependencies file
βββ appsettings.json # The project settings file
βββ evans.sh # A quick tool to help you test your plugin
βββ test
βββ pact-js # A test with Pact-JS to exercise the plugin
βββ .github/workflows # This holds your CI build and release configuration
βββ RELEASING.md # Instructions on how to release π
The protoc compiler must be installed for this plugin
See .NET specific instructions here and the official Microsoft docs page for knowledge about construction Protobuf messages in .NET
- Clone this repository
- Create a new repository in GitHub. The name of the plugin should be
pact-<PROJECT>-plugin
e.g.pact-protobuf-plugin
- Push this code to your new repository
Run:
make build
which is an alias for
dotnet build
To ensure the dependencies and vendoring are correct.
In the top of the Makefile
set PROJECT
to your plugin's name.
PROJECT
should map to <PROJECT>
in your GitHub repository.
NOTE: It's important that the name of your GitHub project and the PROJECT
variable must align, to create artifacts discoverable to the CLI tooling, such as the Plugin CLI.
This is how the users of your plugin will write the plugin specific interaction details.
For example, take the following HTTP interaction:
await pact
.addInteraction()
.given('the Matt protocol exists')
.uponReceiving('an HTTP request to /matt')
.usingPlugin({
plugin: 'matt',
version: '0.0.4',
})
.withRequest('POST', '/matt', (builder) => {
builder.pluginContents('application/matt', mattRequest); // <- request
})
.willRespondWith(200, (builder) => {
builder.pluginContents('application/matt', mattResponse); // <- response
})
.executeTest((mockserver) => {
...
The user needs to specify the request and response body portion of the request.
Because the use cases for plugins are so wide and varied, the framework does not impose limits on this data structure and is something you need to design.
This being said, most plugins have opted to use a JSON structure.
This structure is represented in our GoLang template in configuration.go
Think about how you would like your user to specify the interaction details for the various interaction types.
Here is an example for a TCP plugin with a custom text protocol:
Set the expected response from the API:
mattMessage := `{"response": {"body": "hellotcp"}}`
Set the request/response all in one go:
mattMessage := `{"request": {"body": "hellotcp"}, "response":{"body":"tcpworld"}}`
Separate out the body on the request/response part of the interaction:
mattRequest := `{"request": {"body": "hello"}}`
mattResponse := `{"response":{"body":"world"}}`
Open PactPluginService.cs
and update the relevant RPC functions.
Depending on your use case, some of the RPC calls won't be required, each method is well signposted to help you along.
You should log regularly. Debugging gRPC calls from the framework can be challenging, as the plugin is started asynchronously by the Plugin Driver behind the scenes.
There are two ways to log:
- Stdout - all stdout (e.g.
print
) is pulled into the general Pact logs for the framework you're running - To file. All calls to
log
will be written to a file
The log setup has three main features:
- It works with the native
C#
package - It logs to a file relative to plugin execution in
log/plugin.log
- It is levelled, at the direction of the plugin driver (that is, the log level will pass in from the driver which will restrict the levels logged in this plugin)
To write something to stdout, you simply call the Console.WriteLine
method
log(message)
Follow the steps in Releasing to publish a new version of your Plugin.
The following command will build the plugin, and install into the correct plugin directory for local development:
make install_local
You can then reference your plugin in local tests to try it out.
If a new protobuf definition is required (e.g. to support a new feature), copy it into the Protos
folder and run the following Make task:
Need to add annotation in the protofile
option csharp_namespace = "GrpcPactPlugin"
for an idiomatic naming convention for C#
make proto
It will update the definitions in the ./obj/Debug/net6.0
packages. Note this may result in a breaking change, depending on the version. So upgrade carefully and ensure you have appropriate tests
This code base should automatically create artifacts for the following OS/Architecture combiations:
OS | Architecture | Supported |
---|---|---|
OSX | x86_64 | β |
OSX | arm | β |
Linux | x86_64 | β |
Linux | arm | β |
Windows | x86_64 | β |
Windows | arm | β |
- Install .NET 6+
- Open the Project.
- Run
dotnet run
- Build with
dotnet build
- Different options for different archs
https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-build
- For full list see https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.NETCore.Platforms/src/runtime.json
- Different options for different archs