Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mefellows committed Aug 1, 2020
1 parent 142ed78 commit 98aff6f
Show file tree
Hide file tree
Showing 11 changed files with 438 additions and 203 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ bin:
ls -hl build/

clean:
rm -rf build output dist
rm -rf build output dist examples/v3/pacts/*

deps: snyk-install
@echo "--- 🐿 Fetching build dependencies "
Expand Down
4 changes: 2 additions & 2 deletions command/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import (
"github.com/spf13/cobra"
)

var version = "v1.4.3"
var Version = "v1.4.3"
var cliToolsVersion = "1.82.3"
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of Pact Go",
Long: `All software has versions. This is Pact Go's`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Pact Go CLI %s, using CLI tools version %s", version, cliToolsVersion)
fmt.Printf("Pact Go CLI %s, using CLI tools version %s", Version, cliToolsVersion)
},
}

Expand Down
32 changes: 21 additions & 11 deletions examples/v3/consumer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,31 @@ import (
v3 "github.com/pact-foundation/pact-go/v3"
)

type s = v3.String

// Example Pact: How to run me!
// 1. cd <pact-go>/examples
// 2. go test -v -run TestConsumer
func TestConsumer(t *testing.T) {
type User struct {
Name string `json:"name" pact:"example=billy"`
LastName string `json:"lastName" pact:"example=sampson"`
Date string `json:"datetime" pact:"generator=time"`
}

// Create Pact connecting to local Daemon
pact := &v3.MockProvider{
mockProvider := &v3.MockProvider{
Consumer: "MyConsumer",
Provider: "MyProvider",
Host: "localhost",
LogLevel: "DEBUG",
}
defer pact.Teardown()
mockProvider.Setup()
defer mockProvider.Teardown()

// Pass in test case
var test = func(config v3.MockServerConfig) error {
u := fmt.Sprintf("http://localhost:%d/foobar", pact.ServerPort)
u := fmt.Sprintf("http://localhost:%d/foobar", mockProvider.ServerPort)
req, err := http.NewRequest("GET", u, strings.NewReader(`{"name":"billy"}`))

// NOTE: by default, request bodies are expected to be sent with a Content-Type
Expand All @@ -52,26 +57,31 @@ func TestConsumer(t *testing.T) {
}

// Set up our expected interactions.
pact.
mockProvider.
AddInteraction().
Given("User foo exists").
UponReceiving("A request to get foo").
WithRequest(v3.Request{
Method: "GET",
Path: v3.String("/foobar"),
Headers: v3.MapMatcher{"Content-Type": v3.String("application/json"), "Authorization": v3.String("Bearer 1234")},
Body: map[string]string{
"name": "billy",
Path: s("/foobar"),
Headers: v3.MapMatcher{"Content-Type": s("application/json"), "Authorization": s("Bearer 1234")},
Body: v3.MapMatcher{
"name": s("billy"),
},
}).
WillRespondWith(v3.Response{
Status: 200,
Headers: v3.MapMatcher{"Content-Type": v3.String("application/json")},
Body: v3.Match(&User{}),
Headers: v3.MapMatcher{"Content-Type": s("application/json")},
// Body: v3.Match(&User{}),
Body: v3.MapMatcher{
"dateTime": s("Bearer 1234"),
"name": s("Bearer 1234"),
"lastName": s("Bearer 1234"),
},
})

// Verify
if err := pact.Verify(test); err != nil {
if err := mockProvider.Verify(test); err != nil {
log.Fatalf("Error on Verify: %v", err)
}
}
58 changes: 40 additions & 18 deletions v3/http.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
/*
package v3 contains the main Pact DSL used in the Consumer
collaboration test cases, and Provider contract test verification.
*/
//package v3 contains the main Pact DSL used in the Consumer
// collaboration test cases, and Provider contract test verification.
package v3

// TODO: setup a proper state machine to prevent actions
Expand Down Expand Up @@ -54,10 +52,6 @@ type MockProvider struct {
// Defaults to `<cwd>/pacts`.
PactDir string `json:"-"`

// Specify which version of the Pact Specification should be used (1 or 2).
// Defaults to 2.
SpecificationVersion int `json:"pactSpecificationVersion,string"`

// Host is the address of the Mock and Verification Service runs on
// Examples include 'localhost', '127.0.0.1', '[::1]'
// Defaults to 'localhost'
Expand Down Expand Up @@ -128,10 +122,6 @@ func (p *MockProvider) Setup() *MockProvider {
p.PactDir = fmt.Sprintf(filepath.Join(dir, "pacts"))
}

if p.SpecificationVersion == 0 {
p.SpecificationVersion = 2
}

if p.ClientTimeout == 0 {
p.ClientTimeout = 10 * time.Second
}
Expand Down Expand Up @@ -174,6 +164,8 @@ func (p *MockProvider) Setup() *MockProvider {
// p.Server = p.pactClient.StartServer(args, port)
// }

native.Init()

return p
}

Expand All @@ -195,17 +187,21 @@ func (p *MockProvider) setupLogging() {

// Teardown stops the Pact Mock Server. This usually is called on completion
// of each test suite.
func (p *MockProvider) Teardown() *MockProvider {
func (p *MockProvider) Teardown() error {
log.Println("[DEBUG] teardown")
if p.ServerPort != 0 {
err := native.WritePactFile(p.ServerPort, p.PactDir)
if err != nil {
return err
}

if native.CleanupMockServer(p.ServerPort) {
p.ServerPort = 0
} else {
log.Println("[DEBUG] unable to teardown server")
}
}
return p
return nil
}

// Verify runs the current test case against a Mock Service.
Expand All @@ -215,13 +211,17 @@ func (p *MockProvider) Verify(integrationTest func(MockServerConfig) error) erro
p.Setup()

// Start server
fmt.Println("[DEBUG] Sending pact file:", formatJSONObject(p))
serialisedPact := NewPactFile(p.Consumer, p.Provider, p.Interactions)
fmt.Println("[DEBUG] Sending pact file:", formatJSONObject(serialisedPact))

// TODO: wire this better
port := native.CreateMockServer(formatJSONObject(p), "0.0.0.0:0", false)
port := native.CreateMockServer(formatJSONObject(serialisedPact), "0.0.0.0:0", false)

// TODO: not sure we want this?
p.ServerPort = port

// TODO: use cases for having server running post integration test?
// Probably not...
defer native.CleanupMockServer(port)

// Run the integration test
Expand All @@ -238,15 +238,37 @@ func (p *MockProvider) Verify(integrationTest func(MockServerConfig) error) erro
if !res {
return fmt.Errorf("pact validation failed: %+v %+v", res, mismatches)
}
p.WritePact()

return nil
}

func (p *MockProvider) displayMismatches(mismatches []native.Mismatch) {
// TODO: pretty print this to make it really easy to understand the problems
// See existing Pact/Ruby code examples
// What about the Rust/Elm compiler feedback, they are pretty great too.
func (p *MockProvider) displayMismatches(mismatches []native.MismatchedRequest) {
if len(mismatches) > 0 {
log.Println("[INFO] pact validation failed, errors: ")
for _, m := range mismatches {
log.Println(m)
formattedRequest := fmt.Sprintf("%s %s", m.Request.Method, m.Request.Path)
switch m.Type {
case "missing-request":
fmt.Printf("Expected request to: %s, but did not receive one\n", formattedRequest)
case "request-not-found":
fmt.Printf("Unexpected request was received: %s\n", formattedRequest)
default:
// TODO:
}

for _, detail := range m.Mismatches {
switch detail.Type {
case "HeaderMismatch":
fmt.Printf("Comparing Header: '%s'\n", detail.Key)
fmt.Println(detail.Mismatch)
fmt.Println("Expected:", detail.Expected)
fmt.Println("Actual:", detail.Actual)
}
}
}
}
}
Expand Down
26 changes: 15 additions & 11 deletions v3/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ func (m eachLike) Type() MatcherClass {
return ArrayMinLikeMatcher
}

func (m eachLike) MatchingRule() matcherType {
matcher := matcherType{
func (m eachLike) MatchingRule() ruleValue {
matcher := ruleValue{
"match": "type",
}

Expand Down Expand Up @@ -103,8 +103,8 @@ func (m like) Type() MatcherClass {
return LikeMatcher
}

func (m like) MatchingRule() matcherType {
return matcherType{
func (m like) MatchingRule() ruleValue {
return ruleValue{
"match": "type",
}
}
Expand All @@ -124,8 +124,8 @@ func (m term) Type() MatcherClass {
return RegexMatcher
}

func (m term) MatchingRule() matcherType {
return matcherType{
func (m term) MatchingRule() ruleValue {
return ruleValue{
"match": "regex",
"regex": m.Data.Matcher.Regex,
}
Expand Down Expand Up @@ -260,7 +260,7 @@ type Matcher interface {
Type() MatcherClass

// Generate the matching rule for this Matcher
MatchingRule() matcherType
MatchingRule() ruleValue
}

// MatcherClass is used to differentiate the various matchers when serialising
Expand Down Expand Up @@ -297,8 +297,8 @@ func (s S) Type() MatcherClass {
return LikeMatcher
}

func (s S) MatchingRule() matcherType {
return matcherType{
func (s S) MatchingRule() ruleValue {
return ruleValue{
"match": "type",
}
}
Expand All @@ -323,8 +323,8 @@ func (s StructMatcher) Type() MatcherClass {
return LikeMatcher
}

func (s StructMatcher) MatchingRule() matcherType {
return matcherType{
func (s StructMatcher) MatchingRule() ruleValue {
return ruleValue{
"match": "type",
}
}
Expand Down Expand Up @@ -358,6 +358,7 @@ func objectToString(obj interface{}) string {
// Supported Tag Formats
// Minimum Slice Size: `pact:"min=2"`
// String RegEx: `pact:"example=2000-01-01,regex=^\\d{4}-\\d{2}-\\d{2}$"`
// TODO: support generators
func Match(src interface{}) Matcher {
return match(reflect.TypeOf(src), getDefaults())
}
Expand Down Expand Up @@ -507,3 +508,6 @@ func pluckParams(srcType reflect.Type, pactTag string) params {
func triggerInvalidPactTagPanic(tag string, err error) {
panic(fmt.Sprintf("match: encountered invalid pact tag %q . . . parsing failed with error: %v", tag, err))
}

// Generators
// https://github.com/pact-foundation/pact-specification/tree/version-3#introduce-example-generators
Loading

0 comments on commit 98aff6f

Please sign in to comment.