Skip to content

Latest commit

Β 

History

History
306 lines (240 loc) Β· 14 KB

File metadata and controls

306 lines (240 loc) Β· 14 KB

Plugin drivers

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.

Conceptual Overview

Conceptual Overview

  1. Your code πŸ˜‰

  2. The test framework executing tests e.g. Jest, JUnit

  3. The Pact Client library used in the test e.g. Pact JS, Pact JVM

  4. 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

  5. Plugin driver is responsible for discovering, starting and orchestrating plugins.

  6. 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

  7. The pact file contains additional information

    • Required plugins
    • Transports
    • Interaction type
    • Markdown to display interactions

Locating plugins

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.

Plugin manifest file

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.

Getting the port of the plugin GRPC server

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"}

Init request to the plugin

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).

Feature Catalogue

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.

Catalogue entry types

The following describes the types of entries:

Content Matcher (content-matcher)

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 Generator (content-generator)

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"
    }
}

Matcher (matcher)

Implementation of a matching rule. Mainly used to match the fields and attributes in the bodies and payloads.

Interaction (interaction)

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.

Core catalogue entries

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

Plugin driver API

The plugin drivers are required to expose the following API to client language implementations:

Catalogue Manager

The catalogue manager stores all the catalogue entries from the core framework and any loaded plugins.

RegisterPluginEntries(name: String, catalogueList: List)

Function to add a list of entries for a plugin.

RegisterCoreEntries(entries: List)

Function to add the list of core framework entries.

Entries

Returns all the catalogue entries

LookupEntry(key: String)

Lookup an entry by key.

FindContentMatcher(contentType: ContentType)

Search the catalogue for a content matcher that supports the given content type.

FindContentGenerator(contentType: ContentType)

Search the catalogue for a content generator that supports the given content type.

RemovePluginEntries(name: String)

Remove all the entries for a plugin. This is needed when a plugin is unloaded.

Plugin Manager

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).

LoadPlugin(plugin: PluginDependency)

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

LookupPlugin(plugin: PluginDependency)

Look up a plugin given a plugin dependency (name, version and list of dependencies) in the global plugin register.

LoadPluginManifest(plugin: PluginDependency)

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.

LookupPluginManifest(plugin: PluginDependency)

Lookup the plugin manifest in the global plugin manifest registry.

InitialisePlugin(manifest: PactPluginManifest)

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.

StartPluginProcess(manifest: PactPluginManifest)

Starts the plugin using the entries from the manifest, then parsers the port and server key from the plugin process standard output.

ShutdownPlugins

Shut down all plugin processes.

PublishUpdatedCatalogue

Every time the catalogue is updated, this function must be called to publish the updated catalogue to all running plugins.