Skip to content

Commit

Permalink
feat(consumerdsl): working v1 consumer DSL interface
Browse files Browse the repository at this point in the history
  • Loading branch information
mefellows committed Jun 4, 2016
1 parent 3b859cf commit 7c3c70b
Show file tree
Hide file tree
Showing 26 changed files with 737 additions and 378 deletions.
15 changes: 15 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
engines:
fixme:
enabled: true
govet:
enabled: true
golint:
enabled: true
gofmt:
enabled: true

ratings:
paths:
- "**.go"

exclude_paths: []
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ pact-provider-verifier
*.idea
*.bak
_*
*.log
pact-go
pacts
logs
51 changes: 50 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ how to get going.

[![wercker status](https://app.wercker.com/status/273436f3ec1ec8e6ea348b81e93aeea1/s/master "wercker status")](https://app.wercker.com/project/bykey/273436f3ec1ec8e6ea348b81e93aeea1)
[![Coverage Status](https://coveralls.io/repos/github/mefellows/pact-go/badge.svg?branch=master)](https://coveralls.io/github/mefellows/pact-go?branch=master)
[![Code Climate](https://codeclimate.com/github/mefellows/pact-go/badges/gpa.svg)](https://codeclimate.com/github/mefellows/pact-go)
[![Issue Count](https://codeclimate.com/github/mefellows/pact-go/badges/issue_count.svg)](https://codeclimate.com/github/mefellows/pact-go)
[![GoDoc](https://godoc.org/github.com/mefellows/pact-go?status.svg)](https://godoc.org/github.com/mefellows/pact-go)

## Installation
Expand All @@ -39,11 +41,58 @@ Due to some design constraints, Pact Go runs a two-step process

1. Run `pact-go daemon` in a separate process/shell. The Consumer and Provider
DSLs communicate over a local (RPC) connection, and is transparent to clients.
1. Create your Pact Consumer/Provider Tests.
1. Create your Pact Consumer/Provider Tests. It defaults to run on port `6666`.

NOTE: The daemon is completely thread safe and it is safe to leave the daemon
running for long periods (e.g. on a CI server).

### Examples

```golang
import "github.com/mefellows/pact-go/dsl"
import ...
// 1. Start the daemon with `./pact-go daemon`
// 2. cd <pact-go>/examples
// 3. go run consumer.go
func main() {

// Create Pact connecting to local Daemon
pact := &dsl.Pact{
Port: 6666, // Ensure this port matches the daemon port!
Consumer: "My Consumer",
Provider: "My Provider",
}
defer pact.Teardown()

// Pass in test case
var test = func() error {
_, err := http.Get(fmt.Sprintf("http://localhost:%d/", pact.Server.Port))
return err
}

// Set up our interactions. Note we have multiple in this test case!
pact.
AddInteraction().
Given("Some state").
UponReceiving("Some name for the test").
WithRequest(&dsl.Request{
Method: "GET",
Path: "/",
}).
WillRespondWith(&dsl.Response{
Status: 200,
})

// Verify
err := pact.Verify(test)
if err != nil {
log.Fatalf("Error on Verify: %v", err)
}

// You should now have a pact file in the file `<pact-go>/pacts/my_consumer-my_provider.json`
}
```

## Contact

* Twitter: [@pact_up](https://twitter.com/pact_up)
Expand Down
33 changes: 10 additions & 23 deletions daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ type PublishRequest struct {
PactBrokerPassword string
}

// PactResponse contains the exit status and any message from the Broker.
type PactResponse struct {
// Response contains the exit status and any message from the Broker.
type Response struct {
// System exit code from the Publish task.
ExitCode int

Expand Down Expand Up @@ -99,7 +99,6 @@ func (d *Daemon) StartDaemon(port int) {
fmt.Println("Got signal:", s, ". Shutting down all services")

d.Shutdown()
fmt.Println("done")
}

// StopDaemon allows clients to programmatically shuts down the running Daemon
Expand All @@ -124,6 +123,7 @@ func (d *Daemon) StartServer(request *PactMockServer, reply *PactMockServer) err
server := &PactMockServer{}
port, svc := d.pactMockSvcManager.NewService(request.Args)
server.Port = port
server.Status = -1
cmd := svc.Start()
server.Pid = cmd.Process.Pid
*reply = *server
Expand All @@ -150,25 +150,12 @@ func (d *Daemon) ListServers(request PactMockServer, reply *PactListResponse) er

// StopServer stops the given mock server.
func (d *Daemon) StopServer(request *PactMockServer, reply *PactMockServer) error {
d.pactMockSvcManager.Stop(request.Pid)
*reply = *request
return nil
}

// Publish publishes Pact files from a given location (file/http).
func (d *Daemon) Publish(request *PublishRequest, reply *PactResponse) error {
*reply = *&PactResponse{
ExitCode: 0,
Message: "",
}
return nil
}

// Verify runs the Pact verification process against a given API Provider.
func (d *Daemon) Verify(request *VerifyRequest, reply *PactResponse) error {
*reply = *&PactResponse{
ExitCode: 0,
Message: "",
success, err := d.pactMockSvcManager.Stop(request.Pid)
if success == true {
request.Status = 0
} else {
request.Status = 1
}
return nil
*reply = *request
return err
}
108 changes: 29 additions & 79 deletions daemon/daemon_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package daemon

import (
"errors"
"fmt"
"log"
"net"
Expand Down Expand Up @@ -194,47 +195,47 @@ func TestStopServer(t *testing.T) {
}
}

func TestPublish(t *testing.T) {
daemon := &Daemon{}
req := PublishRequest{}
var res PactResponse
err := daemon.Publish(&req, &res)
if err != nil {
t.Fatalf("Error: %v", err)
}
func TestStopServer_Fail(t *testing.T) {
daemon, manager := createMockedDaemon()
var cmd *exec.Cmd
var res PactMockServer

if res.ExitCode != 0 {
t.Fatalf("Expected exit code to be 0 but got: %d", res.ExitCode)
for _, s := range manager.List() {
cmd = s
}

if res.Message != "" {
t.Fatalf("Expected message to be blank but got: %s", res.Message)
request := PactMockServer{
Pid: cmd.Process.Pid,
}
}

func TestPublish_Fail(t *testing.T) {
manager.ServiceStopError = errors.New("failed to stop server")

err := daemon.StopServer(&request, &res)
if err == nil {
t.Fatalf("Expected error but got none")
}
}

func TestVerification(t *testing.T) {
daemon := &Daemon{}
req := VerifyRequest{}
var res PactResponse
err := daemon.Verify(&req, &res)
if err != nil {
t.Fatalf("Error: %v", err)
}
func TestStopServer_FailedStatus(t *testing.T) {
daemon, manager := createMockedDaemon()
var cmd *exec.Cmd
var res PactMockServer

if res.ExitCode != 0 {
t.Fatalf("Expected exit code to be 0 but got: %d", res.ExitCode)
for _, s := range manager.List() {
cmd = s
}
request := PactMockServer{
Pid: cmd.Process.Pid,
}

manager.ServiceStopResult = false

daemon.StopServer(&request, &res)

if res.Message != "" {
t.Fatalf("Expected message to be blank but got: %s", res.Message)
if res.Status != 1 {
t.Fatalf("Expected exit status to be 1 but got: %d", res.Status)
}
}

// Integration style test: Can a client hit each endpoint?
func TestRPCClient_List(t *testing.T) {
daemon, _ := createMockedDaemon()
port, _ := utils.GetFreePort()
Expand All @@ -254,7 +255,6 @@ func TestRPCClient_List(t *testing.T) {
}
}

// Integration style test: Can a client hit each endpoint?
func TestRPCClient_StartServer(t *testing.T) {
daemon, _ := createMockedDaemon()
port, _ := utils.GetFreePort()
Expand All @@ -278,7 +278,6 @@ func TestRPCClient_StartServer(t *testing.T) {
}
}

// Integration style test: Can a client hit each endpoint?
func TestRPCClient_StopServer(t *testing.T) {
daemon, manager := createMockedDaemon()
port, _ := utils.GetFreePort()
Expand Down Expand Up @@ -309,55 +308,6 @@ func TestRPCClient_StopServer(t *testing.T) {
t.Fatalf("Expected non-zero port but got: %d", res.Port)
}
}

// Integration style test: Can a client hit each endpoint?
func TestRPCClient_Verify(t *testing.T) {
daemon, _ := createMockedDaemon()
port, _ := utils.GetFreePort()
defer waitForDaemonToShutdown(port, daemon, t)
go daemon.StartDaemon(port)
connectToDaemon(port, t)

client, err := rpc.DialHTTP("tcp", fmt.Sprintf(":%d", port))
var res PactResponse
err = client.Call("Daemon.Verify", &VerifyRequest{}, &res)
if err != nil {
log.Fatal("rpc error:", err)
}

if res.ExitCode != 0 {
t.Fatalf("Expected exit code to be 0, got: %d", res.ExitCode)
}
if res.Message != "" {
t.Fatalf("Expected message to be blank but got: %s", res.Message)
}
}

// Integration style test: Can a client hit each endpoint?
func TestRPCClient_Publish(t *testing.T) {
daemon, _ := createMockedDaemon()
port, _ := utils.GetFreePort()
defer waitForDaemonToShutdown(port, daemon, t)
go daemon.StartDaemon(port)
connectToDaemon(port, t)

client, err := rpc.DialHTTP("tcp", fmt.Sprintf(":%d", port))
var res PactResponse
err = client.Call("Daemon.Publish", &PublishRequest{}, &res)
if err != nil {
log.Fatal("rpc error:", err)
}

if res.ExitCode != 0 {
t.Fatalf("Expected exit code to be 0, got: %d", res.ExitCode)
}

if res.Message != "" {
t.Fatalf("Expected message to be blank but got: %s", res.Message)
}
}

// Integration style test: Can a client hit each endpoint?
func TestRPCClient_StopDaemon(t *testing.T) {
daemon, _ := createMockedDaemon()
port, _ := utils.GetFreePort()
Expand Down
11 changes: 6 additions & 5 deletions daemon/pact_mock_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@ type PactMockService struct {
func (m *PactMockService) NewService(args []string) (int, Service) {
port, _ := utils.GetFreePort()
// version := 2
// dir, _ := os.Getwd()
// dir = fmt.Sprintf(filepath.Join(dir, "../", "pacts"))
// logDir := fmt.Sprintf(filepath.Join(dir, "../", "logs"))
dir, _ := os.Getwd()
logDir := fmt.Sprintf(filepath.Join(dir, "logs"))
dir = fmt.Sprintf(filepath.Join(dir, "pacts"))
log.Println("Starting mock service on port:", port)

m.Args = []string{
fmt.Sprintf("--port %d", port),
// fmt.Sprintf("--pact-specification-version %d", version),
// fmt.Sprintf("--pact-dir %s", dir),
// fmt.Sprintf("--log-dir %s", logDir),
fmt.Sprintf("--pact-dir %s", dir),
fmt.Sprintf("--log %s/pact.log", logDir),
// fmt.Sprintf("--cors"),
// fmt.Sprintf("--ssl"),
}
m.Args = append(m.Args, args...)
Expand Down
7 changes: 7 additions & 0 deletions daemon/service_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ func (s *ServiceMock) Setup() {

// Stop a Service and returns the exit status.
func (s *ServiceMock) Stop(pid int) (bool, error) {
if _, ok := s.processes[pid]; ok {
s.processes[pid].Process.Kill()
}
s.ServiceStopCount++
return s.ServiceStopResult, s.ServiceStopError
}
Expand All @@ -42,6 +45,10 @@ func (s *ServiceMock) Start() *exec.Cmd {
s.ServiceStartCount++
cmd := s.ExecFunc()
cmd.Start()
if s.processes == nil {
s.processes = make(map[int]*exec.Cmd)
}
s.processes[cmd.Process.Pid] = cmd

return cmd
}
Expand Down
12 changes: 8 additions & 4 deletions dsl/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import (

var timeoutDuration = 1 * time.Second

// Client is the simplified remote interface to the Pact Daemon
// Client is the simplified remote interface to the Pact Daemon.
type Client interface {
StartServer() *daemon.PactMockServer
}

// PactClient is the default implementation of the Client interface.
type PactClient struct {
// Port the daemon is running on
// Port the daemon is running on.
Port int
}

Expand All @@ -32,9 +32,8 @@ func getHTTPClient(port int) (*rpc.Client, error) {
}

// Use this to wait for a daemon to be running prior
// to running tests
// to running tests.
func waitForPort(port int) error {
fmt.Printf("client - Waiting for daemon port: %d", port)
timeout := time.After(timeoutDuration)

for {
Expand All @@ -61,6 +60,11 @@ func (p *PactClient) StartServer() *daemon.PactMockServer {
log.Fatal("rpc error:", err)
}
}

if err == nil {
waitForPort(res.Port)
}

return &res
}

Expand Down
Loading

0 comments on commit 7c3c70b

Please sign in to comment.