Plugin drivers provide the functionality to find, load and interface to the plugins for the Pact test framework. Each language with a Pact implementation requires a plugin driver to work with plugins.
Main plugin driver responsibilities:
- The ability to find plugins.
- Load plugins and extract the plugin manifests that describe what the plugin provides.
- Provide a catalogue of features provided by the plugins.
- Provide a messaging bus to facilitate communication between the language implementation and the plugins.
- Manage the plugin lifecycles.
-
Your code π
-
The test framework executing tests e.g. Jest, JUnit
-
The Pact Client library used in the test e.g. Pact JS, Pact JVM
-
The Shared Core (Rust) or the Java Plugin Driver (for JVM projects) manages the lifecycle of a Pact test and provides key capabilities to client libraries. It oversees the use of plugins via the Plugin Driver
-
Plugin driver is responsible for discovering, starting and orchestrating plugins.
-
Plugin may communicate to/from the SUT e.g. in the case of a new transport it will serve as the mock server, and will also issue verification requests at the Provider API
-
The pact file contains additional information
- Required plugins
- Transports
- Interaction type
- Markdown to display interactions
Plugins must be stored on the system in a Pact plugins directory, defined either by the PACT_PLUGIN_DIR environment
variable or in the default $HOME/.pact/plugins. Each plugin must be in a separate sub-directory (plugin name/version) and contain a plugin
manifest file pact-plugin.json.
The plugin manifest file describes what the plugin provides and how to load it. It is a JSON file with the following attributes:
| Attribute | Description |
|---|---|
| manifestVersion | Version of the manifest file format. Current is 1 |
| pluginInterfaceVersion | Version of the plugin interface the plugin supports. Current is 1 |
| name | Name of the plugin |
| version | Version of the plugin, following the semver format |
| executableType | Executable type of the plugin. Supported types are: exec (executable binary) |
| minimumRequiredVersion | Minimum required version of the runtime/interpreter to run the plugin |
| entryPoint | The main executable for the plugin |
| entryPoints | Optional map of additional entry points. This allows additional entry points for other operating systems (i.e. requiring a .bat file for Windows) |
| dependencies | List of system dependencies or plugins required to be able to execute this plugin |
| args | Optional, comma seperated list of arguments to pass to the plugin entrypoint |
Example of a manifest for a plugin written in Ruby that provides matching CSV files:
{
"manifestVersion": 1,
"pluginInterfaceVersion": 1,
"name": "pact-csv",
"version": "0.0.0",
"executableType": "exec",
"minimumRequiredVersion": "2.7.2",
"entryPoint": "bin/bundle",
"args": ["exec", "ruby", "main.rb"]
}See CSV Plugin and Protobuf plugin for examples of plugin manifests.
When the plugin is started (except for the DLL executable type), the plugin will print a JSON message to its standard output that contains the port that the plugin GRPC server is running on. The driver needs to poll the plugin standard output for this message.
The message will have the following attributes:
| Attribute | Description |
|---|---|
| port | The port number the GRPC server for the plugin is listening on |
| serverKey | A randomly generated key required to use as a bearer token when communicating with the plugin |
Example:
{"port": 12345, "serverKey": "b37d2d9a9ceb"}Once the port has been extracted from the plugin standard output, the driver must send a InitPluginRequest
message via GRPC to the plugin. The plugin will respond with a InitPluginResponse which will contain all the
catalogue entries for the features that the plugin provides. The driver needs to update its catalogue with these
entries and then publish the updated catalogue to all loaded plugins (including the new one).
Each entry in the catalogue is keyed based on the following structure: $providerType/$name?/$type/$key, where the
different parts are defined by:
| Attribute | Description |
|---|---|
| providerType | Denotes an entry from the core Pact framework (core) or from a plugin (plugin) |
| name | The name of the plugin (omitted for core entries) |
| type | The type of the entry. Valid values are: content-matcher, content-generator, matcher, interaction |
| key | Key for the type. It must be unique withing the entries for the plugin. |
For example, a plugin entry for matching CSV bodies would be plugin/csv/content-matcher/csv.
Each entry can also contain associated data in key/value form.
The following describes the types of entries:
Content matchers are responsible for matching request and response bodies and message payloads. Each entry must have
a content-types associated value with the list of content types the matcher supports (separated by semi-colons ;).
For example, the entry for a CSV content matcher would be:
{
type: EntryType::ContentMatcher,
key: "csv",
values: {
"content-types": "text/csv;application/csv"
}
}
Content matchers are responsible for constructing request and response bodies and message payloads. Each entry must have
a content-types associated value with the list of content types the generator supports (separated by semi-colons ;).
For example, the entry for a CSV content generator would be:
{
type: EntryType::ContentGenerator,
key: "csv",
values: {
"content-types": "text/csv;application/csv"
}
}
Implementation of a matching rule. Mainly used to match the fields and attributes in the bodies and payloads.
Provides a type of interaction. There is one entry per interaction type a Pact file can record: synchronous
request/response (request-response), asynchronous messages (one off or fire and forget, message) and synchronous
messages (request/response, like gRPC, synchronous-message).
The transport an interaction is carried over is a separate concern, covered by the transport entries - it is only
history that the request/response interaction is the original Pact one carried over HTTP or HTTPS.
The driver must provide the following entries from the Pact framework. Matching rule entries are keyed by the name
the rule carries in a request (the same string a MatchFieldRequest puts in rule.type); the Pact specification
version the rule was introduced in is a spec-version value on the entry rather than part of the key. Frameworks
register the rules and generators they implement, so the exact list varies - Pact-JVM adds the ignore-order,
min-ignore-order, max-ignore-order and min-max-ignore-order matching rules and the Null generator (all V4),
which the Rust implementation does not have. Generator keys are PascalCase because that is the type a generator
carries in a Pact file and in a request, the same way matching rule keys are the MatchingRule name.
| Key | Description |
|---|---|
core/transport/http |
Support Http/1.1 interactions (request/response) |
core/transport/https |
Support Http/1.1 + TLS interactions (request/response) |
core/interaction/request-response |
Support synchronous request/response interactions |
core/interaction/message |
Support message interactions |
core/interaction/synchronous-message |
Support synchronous message interactions |
core/matcher/equality |
Equality matcher (V1) |
core/matcher/regex |
Regex matcher (V2) |
core/matcher/type |
Type matcher (V2) |
core/matcher/min-type |
Minimum type matcher (V2) |
core/matcher/max-type |
Maximum type matcher (V2) |
core/matcher/min-max-type |
Minimum/maximum type matcher (V2) |
core/matcher/include |
Includes matcher (V3) |
core/matcher/number |
Number matcher (V3) |
core/matcher/integer |
Integer matcher (V3) |
core/matcher/decimal |
Decimal matcher (V3) |
core/matcher/null |
Null matcher (V3) |
core/matcher/date |
Date matcher (V3) |
core/matcher/time |
Time matcher (V3) |
core/matcher/datetime |
DateTime matcher (V3) |
core/matcher/content-type |
Content type matcher (V3) |
core/matcher/values |
Values matcher (V3) |
core/matcher/array-contains |
Array contains matcher (V4) |
core/matcher/boolean |
Boolean matcher (V4) |
core/matcher/status-code |
Status code matcher (V4) |
core/matcher/not-empty |
Not empty matcher (V4) |
core/matcher/semver |
Semantic version matcher (V4) |
core/matcher/each-key |
Each key matcher (V4) |
core/matcher/each-value |
Each value matcher (V4) |
core/generator/RandomInt |
Random integer generator (V3) |
core/generator/RandomDecimal |
Random decimal generator (V3) |
core/generator/RandomHexadecimal |
Random hexadecimal generator (V3) |
core/generator/RandomString |
Random string generator (V3) |
core/generator/RandomBoolean |
Random boolean generator (V3) |
core/generator/Regex |
Regex generator (V3) |
core/generator/Uuid |
UUID generator (V3) |
core/generator/Date |
Date generator (V3) |
core/generator/Time |
Time generator (V3) |
core/generator/DateTime |
DateTime generator (V3) |
core/generator/ProviderState |
Provider state generator (V3) |
core/generator/MockServerURL |
Mock server URL generator (V4) |
core/generator/ArrayContains |
Array contains generator (V4) |
core/content-matcher/xml |
Matcher for XML content types |
core/content-matcher/json |
Matcher for JSON content types |
core/content-matcher/text |
Matcher for Text content types |
core/content-matcher/multipart-form-data |
Matcher for Multipart Form POST content types |
core/content-matcher/form-urlencoded |
Matcher for URL-encoded Form POST content types |
core/content-generator/json |
Generator for JSON payloads |
The plugin drivers are required to expose the following API to client language implementations:
The catalogue manager stores all the catalogue entries from the core framework and any loaded plugins.
Function to add a list of entries for a plugin.
Function to add the list of core framework entries.
Returns all the catalogue entries
Lookup an entry by key.
Search the catalogue for a content matcher that supports the given content type.
Search the catalogue for a content generator that supports the given content type.
Remove all the entries for a plugin. This is needed when a plugin is unloaded.
The plugin manager is responsible for finding, loading and unloading plugins. It also provides the interface to call out to the plugin (gRPC stub or channel).
Load a plugin given a plugin dependency (name, version and list of dependencies). The plugin manager must keep track of all currently loaded plugins in a global plugin register so that the plugins are not loaded more than once.
Every time a plugin is loaded, send an anonymous event to Google Analytics to track the details of the loaded plugin .
To disable tracking, users can set the pact_do_not_track system property or environment variable to true.
The following attributes are sent to GA:
v: 1 // Version of the API
tid: UA-117778936-1 // Property ID
cid: <UUID> // Anonymous Client ID.
an: <NAME> // App name.
aid: <NAME> // App Id
av: <VERSION> // App version.
aip: true // Anonymise IP address
ds: <PLUGIN NAME> // Data source
cd1: <PLUGIN NAME> // Custom Dimension 1: library
cd2: CI or unknown // Custom Dimension 2: context
cd3: <OS>-<ARCH> // Custom Dimension 3: osarch
cd4: <PLUGIN NAME> // Custom Dimension 4: plugin_name
cd5: <PLUGIN VERSION> // Custom Dimension 5: plugin_version
el: Plugin loaded // Event
ec: Plugin // Category
ea: Loaded // Action
ev: 1 // Value
Look up a plugin given a plugin dependency (name, version and list of dependencies) in the global plugin register.
Return the plugin manifest given a plugin dependency (name, version and list of dependencies). Will first look in the global plugin manifest registry and then load the manifest from disk if not found in the registry.
Lookup the plugin manifest in the global plugin manifest registry.
Start the plugin by calling StartPluginProcess and send the InitPluginRequest message to the plugin. Adds the plugin
to the global plugin register and updates the Catalogue Manager with all entries from the plugin.
Starts the plugin using the entries from the manifest, then parsers the port and server key from the plugin process standard output.
Shut down all plugin processes.
Every time the catalogue is updated, this function must be called to publish the updated catalogue to all running plugins.
