diff --git a/.gitignore b/.gitignore index b7e9141c7..221fa6c11 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,8 @@ _* pact-go pacts logs +log +tmp # IDE .vscode \ No newline at end of file diff --git a/client/message_verification_service.go b/client/message_verification_service.go new file mode 100644 index 000000000..1cc53cbcf --- /dev/null +++ b/client/message_verification_service.go @@ -0,0 +1,26 @@ +package client + +import ( + "log" +) + +// MessageVerificationService is a wrapper for the Pact Provider Verifier Service. +type MessageVerificationService struct { + ServiceManager +} + +// NewService creates a new MessageVerificationService with default settings. +// Named Arguments allowed: +// --consumer +// --provider +// --pact-dir +func (v *MessageVerificationService) NewService(args []string) Service { + v.Args = args + // Currently has an issue, see https://travis-ci.org/pact-foundation/pact-message-ruby/builds/357675751 + // v.Args = []string{"update", `{ "description": "a test mesage", "content": { "name": "Mary" } }`, "--consumer", "from", "--provider", "golang", "--pact-dir", "/tmp"} + + log.Printf("[DEBUG] starting message service with args: %v\n", v.Args) + v.Cmd = "pact-message" + + return v +} diff --git a/client/verification_service.go b/client/verification_service.go index 416bc73ef..f2fcf3c38 100644 --- a/client/verification_service.go +++ b/client/verification_service.go @@ -26,6 +26,7 @@ func (v *VerificationService) NewService(args []string) Service { v.Args = args v.Cmd = getVerifierCommandPath() + return v } diff --git a/command/install_test.go b/command/install_test.go deleted file mode 100644 index b6a660405..000000000 --- a/command/install_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package command - -import ( - "os" - "testing" -) - -func TestInstallCommand(t *testing.T) { - os.Args = []string{"install"} - err := installCmd.Execute() - if err != nil { - t.Fatalf("Error: %v", err) - } - installCmd.Run(nil, os.Args) -} diff --git a/dsl/client.go b/dsl/client.go index 01db007c7..3746edf03 100644 --- a/dsl/client.go +++ b/dsl/client.go @@ -26,6 +26,7 @@ var ( type PactClient struct { pactMockSvcManager client.Service verificationSvcManager client.Service + messageSvcManager client.Service // Track mock servers Servers []MockService @@ -37,17 +38,24 @@ type PactClient struct { Address string } -// NewClient creates a new Pact client manager -func NewClient(MockServiceManager client.Service, verificationServiceManager client.Service) *PactClient { +// newClient creates a new Pact client manager with the provided services +func newClient(MockServiceManager client.Service, verificationServiceManager client.Service, messageServiceManager client.Service) *PactClient { MockServiceManager.Setup() verificationServiceManager.Setup() + messageServiceManager.Setup() return &PactClient{ pactMockSvcManager: MockServiceManager, verificationSvcManager: verificationServiceManager, + messageSvcManager: messageServiceManager, } } +// NewClient creates a new Pact client manager with defaults +func NewClient() *PactClient { + return newClient(&client.MockService{}, &client.VerificationService{}, &client.MessageVerificationService{}) +} + // StartServer starts a remote Pact Mock Server. func (p *PactClient) StartServer(args []string, port int) *types.MockServer { log.Println("[DEBUG] client: starting a server with args:", args, "port:", port) @@ -166,6 +174,49 @@ func (p *PactClient) VerifyProvider(request types.VerifyRequest) (types.Provider return response, fmt.Errorf("error verifying provider: %s\n\nSTDERR:\n%s\n\nSTDOUT:\n%s", err, stdErr, stdOut) } +// UpdateMessagePact adds a pact message to a contract file +func (p *PactClient) UpdateMessagePact(request types.PactMessageRequest) error { + log.Println("[DEBUG] client: adding pact message...") + + // Convert request into flags, and validate request + err := request.Validate() + if err != nil { + return err + } + + svc := p.messageSvcManager.NewService(request.Args) + cmd := svc.Command() + + stdOutPipe, err := cmd.StdoutPipe() + if err != nil { + return err + } + stdErrPipe, err := cmd.StderrPipe() + if err != nil { + return err + } + err = cmd.Start() + if err != nil { + return err + } + stdOut, err := ioutil.ReadAll(stdOutPipe) + if err != nil { + return err + } + stdErr, err := ioutil.ReadAll(stdErrPipe) + if err != nil { + return err + } + + err = cmd.Wait() + + if err == nil { + return nil + } + + return fmt.Errorf("error creating message: %s\n\nSTDERR:\n%s\n\nSTDOUT:\n%s", err, stdErr, stdOut) +} + // Get a port given a URL func getPort(rawURL string) int { parsedURL, err := url.Parse(rawURL) diff --git a/dsl/client_test.go b/dsl/client_test.go index 0c0615bbb..f97892831 100644 --- a/dsl/client_test.go +++ b/dsl/client_test.go @@ -212,7 +212,7 @@ func createClient(success bool) (*PactClient, *ServiceMock) { } }() - d := NewClient(svc, svc) + d := newClient(svc, svc, svc) return d, svc } diff --git a/dsl/matcher.go b/dsl/matcher.go index d63a5e5c0..1d4967526 100644 --- a/dsl/matcher.go +++ b/dsl/matcher.go @@ -175,11 +175,7 @@ func (m Matcher) MarshalJSON() ([]byte, error) { // UnmarshalJSON is a custom decoder for Header type func (m *Matcher) UnmarshalJSON(data []byte) error { - if err := json.Unmarshal(data, &m); err != nil { - return err - } - - return nil + return json.Unmarshal(data, &m) } // MapMatcher allows a map[string]string-like object @@ -199,11 +195,7 @@ func (h MapMatcher) MarshalJSON() ([]byte, error) { // UnmarshalJSON is a custom decoder for Header type func (h *MapMatcher) UnmarshalJSON(data []byte) error { - if err := json.Unmarshal(data, &h); err != nil { - return err - } - - return nil + return json.Unmarshal(data, &h) } // Takes an object and converts it to a JSON representation diff --git a/dsl/message.go b/dsl/message.go new file mode 100644 index 000000000..4bf0d9b40 --- /dev/null +++ b/dsl/message.go @@ -0,0 +1,50 @@ +package dsl + +// Message is a representation of a single, unidirectional message +// e.g. MQ, pub/sub, Websocket, Lambda +// Message is the main implementation of the Pact Message interface. +type Message struct { + // Message Body + Content interface{} `json:"content,omitempty"` + + // Provider state to be written into the Pact file + State string `json:"providerState,omitempty"` + + // Message metadata + Metadata MapMatcher `json:"metadata,omitempty"` + + // Description to be written into the Pact file + Description string `json:"description"` + + Args []string `json:"-"` +} + +// Given specifies a provider state. Optional. +func (p *Message) Given(state string) *Message { + p.State = state + return p +} + +// ExpectsToReceive specifies the content it is expecting to be +// given from the Provider. The function must be able to handle this +// message for the interaction to succeed. +func (p *Message) ExpectsToReceive(description string) *Message { + p.Description = description + return p +} + +// WithMetadata specifies message-implementation specific metadata +// to go with the content +func (p *Message) WithMetadata(metadata MapMatcher) *Message { + p.Metadata = metadata + return p +} + +// WithContent specifies the details of the HTTP request that will be used to +// confirm that the Provider provides an API listening on the given interface. +// Mandatory. +func (p *Message) WithContent(content interface{}) *Message { + p.Content = toObject(content) + + return p +} diff --git a/dsl/pact.go b/dsl/pact.go index 571f19e82..86b814a1b 100644 --- a/dsl/pact.go +++ b/dsl/pact.go @@ -5,14 +5,18 @@ collaboration test cases, and Provider contract test verification. package dsl import ( + "encoding/json" + "errors" "fmt" + "io/ioutil" "log" + "net" + "net/http" "os" "path/filepath" "testing" "github.com/hashicorp/logutils" - "github.com/pact-foundation/pact-go/client" "github.com/pact-foundation/pact-go/install" "github.com/pact-foundation/pact-go/types" "github.com/pact-foundation/pact-go/utils" @@ -128,7 +132,7 @@ func (p *Pact) Setup(startMockServer bool) *Pact { } if p.pactClient == nil { - p.pactClient = NewClient(&client.MockService{}, &client.VerificationService{}) + p.pactClient = NewClient() } if p.PactFileWriteMode == "" { @@ -206,6 +210,12 @@ func (p *Pact) Teardown() *Pact { func (p *Pact) Verify(integrationTest func() error) error { p.Setup(true) log.Println("[DEBUG] pact verify") + + // Check if we are verifying messages or if we actually have interactions + if len(p.Interactions) == 0 { + return errors.New("there are no interactions to be verified") + } + mockServer := &MockService{ BaseURL: fmt.Sprintf("http://%s:%d", p.Host, p.Server.Port), Consumer: p.Consumer, @@ -301,7 +311,131 @@ var checkCliCompatibility = func() { err := installer.CheckInstallation() if err != nil { - log.Println("[DEBUG] asoetusaoteuasoet") - log.Fatal("[ERROR] CLI tools are out of date, please upgrade before continuing.") + log.Fatal("[ERROR] CLI tools are out of date, please upgrade before continuing") + } +} + +// VerifyMessageProducer accepts an instance of `*testing.T` +// running provider message verification with granular test reporting and +// automatic failure reporting for nice, simple tests. +// +// A Message Producer is analagous to Consumer in the HTTP Interaction model. +// It is the initiator of an interaction, and expects something on the other end +// of the interaction to respond - just in this case, not immediately. +func (p *Pact) VerifyMessageProducer(t *testing.T, request types.VerifyRequest, handlers map[string]func(...interface{}) (map[string]interface{}, error)) (types.ProviderVerifierResponse, error) { + + // Starts the message wrapper API with hooks back to the message handlers + // This maps the 'description' field of a message pact, to a function handler + // that will implement the message producer. This function must return an object and optionally + // and error. The object will be marshalled to JSON for comparison. + mux := http.NewServeMux() + + // TODO: make this dynamic + port := 9393 + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + + // Extract message + var message Message + body, err := ioutil.ReadAll(r.Body) + r.Body.Close() + + if err != nil { + // TODO: How should we respond back to the verifier in this case? 50x? + w.WriteHeader(http.StatusBadRequest) + return + } + + json.Unmarshal(body, &message) + + // Lookup key in function mapping + f, messageFound := handlers[message.Description] + + if !messageFound { + // TODO: How should we respond back to the verifier in this case? 50x? + w.WriteHeader(http.StatusNotFound) + return + } + + // Execute function handler + res, handlerErr := f() + + fmt.Printf("[DEBUG] f() returned: %v", res) + + if handlerErr != nil { + // TODO: How should we respond back to the verifier in this case? 50x? + fmt.Println("[ERROR] error handling function:", handlerErr) + w.WriteHeader(http.StatusServiceUnavailable) + return + } + + // Write the body back + resBody, errM := json.Marshal(res) + if errM != nil { + w.WriteHeader(http.StatusServiceUnavailable) + fmt.Println("[ERROR] error marshalling objcet:", errM) + return + } + fmt.Printf("[DEBUG] sending response body back to verifier %v", resBody) + + w.WriteHeader(http.StatusOK) + w.Write(resBody) + }) + + ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + log.Fatal(err) + } + defer ln.Close() + + log.Printf("[DEBUG] API handler starting: port %d (%s)", port, ln.Addr()) + go http.Serve(ln, mux) + + portErr := waitForPort(port, "tcp", "localhost", fmt.Sprintf(`Timed out waiting for Daemon on port %d - are you + sure it's running?`, port)) + + if portErr != nil { + t.Fatal("Error:", err) + return types.ProviderVerifierResponse{}, portErr + } + + res, err := p.VerifyProviderRaw(request) + + for _, example := range res.Examples { + t.Run(example.Description, func(st *testing.T) { + st.Log(example.FullDescription) + if example.Status != "passed" { + st.Errorf("%s\n", example.Exception.Message) + } + }) + } + + return res, err +} + +// VerifyMessageConsumer creates a new Pact _message_ interaction to build a testable +// interaction +// +// A Message Consumer is analagous to a Provider in the HTTP Interaction model. +// It is the receiver of an interaction, and needs to be able to handle whatever +// request was provided. +func (p *Pact) VerifyMessageConsumer(message *Message, handler func(...Message) error) error { + log.Printf("[DEBUG] verify message") + p.Setup(false) + + // Yield message, and send through handler function + // TODO: for now just call the handler + err := handler(*message) + if err != nil { + return err } + + // If no errors, update Message Pact + return p.pactClient.UpdateMessagePact(types.PactMessageRequest{ + Message: message, + Consumer: p.Consumer, + Provider: p.Provider, + PactFileWriteMode: p.PactFileWriteMode, + PactDir: p.PactDir, + }) } diff --git a/dsl/pact_test.go b/dsl/pact_test.go index 879ddb85f..fef9840ea 100644 --- a/dsl/pact_test.go +++ b/dsl/pact_test.go @@ -236,7 +236,7 @@ func TestPact_Teardown(t *testing.T) { } } -func TestPact_VerifyProvider(t *testing.T) { +func TestPact_VerifyProviderRaw(t *testing.T) { c, _ := createClient(true) defer stubPorts()() @@ -251,6 +251,37 @@ func TestPact_VerifyProvider(t *testing.T) { } } +func TestPact_VerifyProvider(t *testing.T) { + c, _ := createClient(true) + defer stubPorts()() + exampleTest := &testing.T{} + pact := &Pact{LogLevel: "DEBUG", pactClient: c} + + _, err := pact.VerifyProvider(exampleTest, types.VerifyRequest{ + ProviderBaseURL: "http://www.foo.com", + PactURLs: []string{"foo.json", "bar.json"}, + }) + + if err != nil { + t.Fatal("Error:", err) + } +} +func TestPact_VerifyProviderFail(t *testing.T) { + c, _ := createClient(false) + defer stubPorts()() + exampleTest := &testing.T{} + pact := &Pact{LogLevel: "DEBUG", pactClient: c} + + _, err := pact.VerifyProvider(exampleTest, types.VerifyRequest{ + ProviderBaseURL: "http://www.foo.com", + PactURLs: []string{"foo.json", "bar.json"}, + }) + + if err == nil { + t.Fatal("want error, got nil") + } +} + func TestPact_VerifyProviderBroker(t *testing.T) { s := setupMockBroker(false) defer s.Close() @@ -286,7 +317,7 @@ func TestPact_VerifyProviderBrokerNoConsumers(t *testing.T) { } } -func TestPact_VerifyProviderFail(t *testing.T) { +func TestPact_VerifyProviderRawFail(t *testing.T) { c, _ := createClient(false) defer stubPorts()() pact := &Pact{LogLevel: "DEBUG", pactClient: c} diff --git a/examples/messages/README.md b/examples/messages/README.md new file mode 100644 index 000000000..e70adacb4 --- /dev/null +++ b/examples/messages/README.md @@ -0,0 +1,34 @@ +# Example - Message Pact + +Implements a POC of the message implementation at https://gist.github.com/bethesque/c858e5c15649ae525ef0cc5264b8477c. + +## Requirements + +* Ruby 2.3.4+ +* Golang + +## Running + +``` +./run.sh +``` + +## Output + +Should look something like: + +``` +=== RUN TestPact_Provider +2018/02/27 09:36:45 [DEBUG] API handler starting: port 9393 ([::]:9393) +2018/02/27 09:36:45 [DEBUG] waiting for port 9393 to become available +2018/02/27 09:36:45 [DEBUG] daemon - verifying provider +2018/02/27 09:36:45 [DEBUG] starting verification service with args: [exec pact-provider-verifier message-pact.json --provider-base-url http://localhost:9393 --format json] +Calling 'text' function that would produce a message +=== RUN TestPact_Provider/has_matching_content +--- PASS: TestPact_Provider (1.94s) + --- PASS: TestPact_Provider/has_matching_content (0.00s) + pact.go:363: Verifying a pact between Foo and Bar A test message has matching content +PASS +ok github.com/pact-foundation/pact-go/examples/messages/provider 1.966s +--> Shutting down running processes +``` \ No newline at end of file diff --git a/examples/messages/consumer/README.md b/examples/messages/consumer/README.md new file mode 100644 index 000000000..a1b83cae0 --- /dev/null +++ b/examples/messages/consumer/README.md @@ -0,0 +1,8 @@ +# Finally. A Ruby Message Pact Spike. + +About time, hey? + +## Usage + + bundle install + ./run.sh diff --git a/examples/messages/consumer/message_pact_consumer_test.go b/examples/messages/consumer/message_pact_consumer_test.go new file mode 100644 index 000000000..6898d076d --- /dev/null +++ b/examples/messages/consumer/message_pact_consumer_test.go @@ -0,0 +1,84 @@ +package provider + +import ( + "errors" + "fmt" + "os" + "testing" + + "github.com/pact-foundation/pact-go/dsl" +) + +var like = dsl.Like +var eachLike = dsl.EachLike +var term = dsl.Term + +type s = dsl.String +type request = dsl.Request + +var commonHeaders = dsl.MapMatcher{ + "Content-Type": s("application/json; charset=utf-8"), +} + +var pact = createPact() + +func TestMessageConsumer_Success(t *testing.T) { + message := &dsl.Message{} + message. + Given("some state"). + ExpectsToReceive("some test case"). + WithMetadata(commonHeaders). + WithContent(map[string]interface{}{ + "foo": "bar", + }) + + err := pact.VerifyMessageConsumer(message, func(i ...dsl.Message) error { + t.Logf("[DEBUG] calling message handler func with arguments: %v \n", i) + + return nil + }) + + if err != nil { + t.Fatal("VerifyMessageConsumer failed:", err) + } +} +func TestMessageConsumer_Fail(t *testing.T) { + t.Skip() + message := &dsl.Message{} + message. + Given("some state"). + ExpectsToReceive("some test case"). + WithMetadata(commonHeaders). + WithContent(map[string]interface{}{ + "foo": "bar", + }) + + err := pact.VerifyMessageConsumer(message, func(i ...dsl.Message) error { + t.Logf("[DEBUG] calling message handler func with arguments: %v \n", i) + + return errors.New("something bad happened and I couldn't parse the message") + }) + + if err != nil { + t.Fatal("VerifyMessageConsumer failed:", err) + } +} + +// Configuration / Test Data +var dir, _ = os.Getwd() +var pactDir = fmt.Sprintf("%s/../../pacts", dir) +var logDir = fmt.Sprintf("%s/log", dir) + +// Setup the Pact client. +func createPact() dsl.Pact { + // Create Pact connecting to local Daemon + return dsl.Pact{ + Consumer: "billy", + Provider: "bobby", + LogDir: logDir, + // PactDir: pactDir, // TODO: this seems to cause an issue "NoMethodError: undefined method `content' for #" + PactDir: "/tmp", + LogLevel: "DEBUG", + PactFileWriteMode: "update", + } +} diff --git a/examples/messages/provider/README.md b/examples/messages/provider/README.md new file mode 100644 index 000000000..a1b83cae0 --- /dev/null +++ b/examples/messages/provider/README.md @@ -0,0 +1,8 @@ +# Finally. A Ruby Message Pact Spike. + +About time, hey? + +## Usage + + bundle install + ./run.sh diff --git a/examples/messages/provider/message-pact.json b/examples/messages/provider/message-pact.json new file mode 100644 index 000000000..864d217c0 --- /dev/null +++ b/examples/messages/provider/message-pact.json @@ -0,0 +1,11 @@ +{ + "consumer": {"name": "Foo"}, + "provider": {"name": "Bar"}, + "messages": [{ + "description": "a test message", + "content": { + "text": "Hello world!!" + } + } + ] +} \ No newline at end of file diff --git a/examples/messages/provider/message_pact_provider_test.go b/examples/messages/provider/message_pact_provider_test.go new file mode 100644 index 000000000..56da96386 --- /dev/null +++ b/examples/messages/provider/message_pact_provider_test.go @@ -0,0 +1,62 @@ +package provider + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/pact-foundation/pact-go/dsl" + "github.com/pact-foundation/pact-go/types" +) + +// The actual Provider test itself +func TestMessageProvider_Success(t *testing.T) { + pact := createPact() + + // Map test descriptions to message producer (handlers) + // TODO: need to agree on the interface for invoking the function + // do we want to pass in args? ...interface{} is a bit of a catch-all + // TODO: convert these all to types to ease readability + functionMappings := map[string]func(...interface{}) (map[string]interface{}, error){ + "a test message": func(...interface{}) (map[string]interface{}, error) { + fmt.Println("Calling 'text' function that would produce a message") + res := map[string]interface{}{ + "content": map[string]string{ + "text": "Hello world!!", + }, + } + return res, nil + }, + } + + // Verify the Provider with local Pact Files + // NOTE: these values don't matter right now, + // the verifier args are hard coded + // TODO: Add function mappings to the VerifyRequest type (or have separate one for producer) + // this can't happen until we remove the RPC shit, because functions can't be mapped + // over the wire + pact.VerifyMessageProducer(t, types.VerifyRequest{ + ProviderBaseURL: fmt.Sprintf("http://localhost:%d", port), + PactURLs: []string{filepath.ToSlash(fmt.Sprintf("%s/message-pact.json", pactDir))}, + }, functionMappings) +} + +// Configuration / Test Data +// var port, _ = utils.GetFreePort() +var port = 9393 +var dir, _ = os.Getwd() +var pactDir = fmt.Sprintf("%s/../pacts", dir) +var logDir = fmt.Sprintf("%s/log", dir) + +// Setup the Pact client. +func createPact() dsl.Pact { + // Create Pact connecting to local Daemon + return dsl.Pact{ + Consumer: "messageconsumer", + Provider: "messageprovider", + LogDir: logDir, + LogLevel: "DEBUG", + PactFileWriteMode: "update", + } +} diff --git a/install/installer.go b/install/installer.go index d05c0759f..5896e2d85 100644 --- a/install/installer.go +++ b/install/installer.go @@ -77,13 +77,6 @@ func (i *Installer) CheckVersion(binary, version string) error { return fmt.Errorf("version %s of %s does not match constraint %s", version, binary, versionRange) } -// InstallTools installs the CLI tools onto the host system -func (i *Installer) InstallTools() error { - log.Println("[INFO] Installing tools") - - return nil -} - // GetVersionForBinary gets the version of a given Ruby binary func (i *Installer) GetVersionForBinary(binary string) (version string, err error) { log.Println("[DEBUG] running binary", binary) diff --git a/install/installer_test.go b/install/installer_test.go index 24466da3e..ed1956b70 100644 --- a/install/installer_test.go +++ b/install/installer_test.go @@ -2,6 +2,7 @@ package install import ( "errors" + "reflect" "testing" ) @@ -31,6 +32,13 @@ func getInstaller(version string, err error) *Installer { return &Installer{testCommander{version, err}} } +func TestInstaller_NewInstaller(t *testing.T) { + i := NewInstaller() + + if reflect.TypeOf(i).String() != "*install.Installer" { + t.Fatal("want *install.Installer, got", reflect.TypeOf(i).String()) + } +} func TestInstaller_CheckVersion(t *testing.T) { i := getInstaller("1.5.0", nil) err := i.CheckVersion("pact-mock-service", "1.5.0") diff --git a/types/pact_message_request.go b/types/pact_message_request.go new file mode 100644 index 000000000..0f1fe8139 --- /dev/null +++ b/types/pact_message_request.go @@ -0,0 +1,56 @@ +package types + +import "encoding/json" + +// PactMessageRequest contains the response from the Pact Message +// CLI execution. +type PactMessageRequest struct { + + // Message is the object to be marshalled to JSON + Message interface{} + + // Consumer is the name of the message consumer + Consumer string + + // Provider is the name of the message provider + // TODO: do we always know this? Presumably not + Provider string + + // PactDir is the location of where pacts should be stored + PactDir string + + // PactFileWriteMode specifies how to write to the Pact file, for the life + // of a Mock Service. + // "overwrite" will always truncate and replace the pact after each run + // "update" will append to the pact file, which is useful if your tests + // are split over multiple files and instantiations of a Mock Server + // See https://github.com/pact-foundation/pact-ruby/blob/master/documentation/configuration.md#pactfile_write_mode + PactFileWriteMode string + + // Args are the arguments sent to to the message service + Args []string +} + +// Validate checks all things are well and constructs +// the CLI args to the message service +func (m *PactMessageRequest) Validate() error { + m.Args = []string{} + + body, err := json.Marshal(m.Message) + if err != nil { + return err + } + + m.Args = append(m.Args, []string{ + m.PactFileWriteMode, + string(body), + "--consumer", + m.Consumer, + "--provider", + m.Provider, + "--pact-dir", + m.PactDir, + }...) + + return nil +}