Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions consumer/http_v4.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ func (i *V4UnconfiguredInteraction) UponReceiving(description string) *V4Unconfi
return i
}

// AddExternalReference records a reference to an external resource (such as a ticket or
// pull request) against the interaction. References appear under
// comments.references[group][name] in the Pact file. May be called multiple times.
func (i *V4UnconfiguredInteraction) AddExternalReference(group, name, value string) *V4UnconfiguredInteraction {
i.interaction.interaction.WithReference(group, name, value)

return i
}

// WithRequest provides a builder for the expected request
func (i *V4UnconfiguredInteraction) WithCompleteRequest(request Request) *V4InteractionWithCompleteRequest {
i.interaction.WithCompleteRequest(request)
Expand Down
20 changes: 20 additions & 0 deletions consumer/http_v4_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package consumer

import (
"fmt"
"net/http"
"os"
"strings"
"testing"
Expand Down Expand Up @@ -85,6 +86,25 @@ func TestHttpV4TypeSystem(t *testing.T) {

}

func TestV4HTTPAddExternalReference(t *testing.T) {
p, err := NewV4Pact(MockHTTPProviderConfig{
Consumer: "consumer",
Provider: "provider",
})
assert.NoError(t, err)

err = p.AddInteraction().
UponReceiving("a request with an external reference").
AddExternalReference("Jira", "TICKET-123", "https://jira.example.com/browse/TICKET-123").
WithRequest("GET", "/", func(b *V4RequestBuilder) {}).
WillRespondWith(200, func(b *V4ResponseBuilder) {}).
ExecuteTest(t, func(msc MockServerConfig) error {
_, err := http.Get(fmt.Sprintf("http://%s:%d/", msc.Host, msc.Port))
return err
})
assert.NoError(t, err)
}

var Like = matchers.Like
var EachLike = matchers.EachLike
var Term = matchers.Term
Expand Down
2 changes: 1 addition & 1 deletion installer/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@
var packages = map[string]packageInfo{
FFIPackage: {
libName: "libpact_ffi",
version: "0.4.28",
version: "0.5.4",
semverRange: ">= 0.4.0, < 1.0.0",
},
}
Expand Down Expand Up @@ -417,13 +417,13 @@
if err != nil {
return fmt.Errorf("failed to create output file; %w", err)
}
defer f.Close()

Check failure on line 420 in installer/installer.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `f.Close` is not checked (errcheck)

resp, err := http.Get(src)
if err != nil {
return fmt.Errorf("failed http call to %s; %w", src, err)
}
defer resp.Body.Close()

Check failure on line 426 in installer/installer.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `resp.Body.Close` is not checked (errcheck)

archive, err := gzip.NewReader(resp.Body)
if err != nil {
Expand Down Expand Up @@ -523,7 +523,7 @@
if err != nil {
return "", err
}
defer f.Close()

Check failure on line 526 in installer/installer.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `f.Close` is not checked (errcheck)

h := md5.New()
if _, err := io.Copy(h, f); err != nil {
Expand Down
16 changes: 16 additions & 0 deletions internal/native/message_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -596,3 +596,19 @@ func (m *MessageServer) WritePactFileForServer(port int, dir string, overwrite b
return fmt.Errorf("an unknown error ocurred when writing to pact file")
}
}

// WithReference records an external reference (e.g. a ticket or pull request)
// against the interaction. References are stored under comments.references[group][name]
// in the Pact file. This is a V4-only feature.
func (m *Message) WithReference(group, name, value string) *Message {
cGroup := C.CString(group)
defer free(cGroup)
cName := C.CString(name)
defer free(cName)
cValue := C.CString(value)
defer free(cValue)

C.pactffi_add_interaction_reference(m.handle, cGroup, cName, cValue)

return m
}
125 changes: 55 additions & 70 deletions internal/native/mock_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import (
"encoding/json"
"fmt"
"log"
"net"
"os"
"strconv"
"strings"
"unsafe"
)
Expand Down Expand Up @@ -134,52 +136,6 @@ func (m *MockServer) WithSpecificationVersion(version specificationVersion) {
C.pactffi_with_specification(m.pact.handle, C.int(version))
}

// CreateMockServer creates a new Mock Server from a given Pact file.
// Returns the port number it started on or an error if failed
func (m *MockServer) CreateMockServer(pact string, address string, tls bool) (int, error) {
log.Println("[DEBUG] mock server starting on address:", address)
cPact := C.CString(pact)
cAddress := C.CString(address)
defer free(cPact)
defer free(cAddress)
tlsEnabled := false
if tls {
tlsEnabled = true
}

p := C.pactffi_create_mock_server(cPact, cAddress, C.bool(tlsEnabled))

// | Error | Description |
// |-------|-------------|
// | -1 | A null pointer was received |
// | -2 | The pact JSON could not be parsed |
// | -3 | The mock server could not be started |
// | -4 | The method panicked |
// | -5 | The address is not valid |
// | -6 | Could not create the TLS configuration with the self-signed certificate |
port := int(p)
switch port {
case -1:
return 0, ErrInvalidMockServerConfig
case -2:
return 0, ErrInvalidPact
case -3:
return 0, ErrMockServerUnableToStart
case -4:
return 0, ErrMockServerPanic
case -5:
return 0, ErrInvalidAddress
case -6:
return 0, ErrMockServerTLSConfiguration
default:
if port > 0 {
log.Println("[DEBUG] mock server running on port:", port)
return port, nil
}
return port, fmt.Errorf("an unknown error (code: %v) occurred when starting a mock server for the test", port)
}
}

// Verify verifies that all interactions were successful. If not, returns a slice
// of Mismatch-es. Does not write the pact or cleanup server.
func (m *MockServer) Verify(port int, dir string) (bool, []MismatchedRequest) {
Expand Down Expand Up @@ -281,50 +237,63 @@ func libRustFree(str *C.char) {
}

// Start starts up the mock HTTP server on the given address:port and TLS config
// https://docs.rs/pact_mock_server_ffi/0.0.7/pact_mock_server_ffi/fn.create_mock_server_for_pact.html
func (m *MockServer) Start(address string, tls bool) (int, error) {
// https://docs.rs/pact_ffi/latest/pact_ffi/mock_server/fn.pactffi_create_mock_server_for_transport.html
func (m *MockServer) Start(address string, tlsEnabled bool) (int, error) {
if len(m.interactions) == 0 {
return 0, ErrNoInteractions
}

log.Println("[DEBUG] mock server starting on address:", address)
cAddress := C.CString(address)

host, portStr, err := net.SplitHostPort(address)
if err != nil {
return 0, ErrInvalidAddress
}
port, err := strconv.Atoi(portStr)
if err != nil {
return 0, ErrInvalidAddress
}

cAddress := C.CString(host)
defer free(cAddress)
tlsEnabled := false
if tls {
tlsEnabled = true

transport := "http"
if tlsEnabled {
transport = "https"
}
cTransport := C.CString(transport)
defer free(cTransport)

p := C.pactffi_create_mock_server_for_pact(m.pact.handle, cAddress, C.bool(tlsEnabled))
cConfig := C.CString("{}")
defer free(cConfig)

// | Error | Description |
// |-------|-------------|
// | -1 | A null pointer was received |
// | -2 | The pact JSON could not be parsed |
// | -3 | The mock server could not be started |
// | -4 | The method panicked |
// | -5 | The address is not valid |
// | -6 | Could not create the TLS configuration with the self-signed certificate |
port := int(p)
switch port {
p := C.pactffi_create_mock_server_for_transport(m.pact.handle, cAddress, C.ushort(port), cTransport, cConfig)

// | Error | Description
// |-------|-------------
// | -1 | An invalid handle was received. Handles should be created with pactffi_new_pact
// | -2 | transport_config is not valid JSON
// | -3 | The mock server could not be started
// | -4 | The method panicked
// | -5 | The address is not valid
msPort := int(p)
switch msPort {
case -1:
return 0, ErrInvalidMockServerConfig
case -2:
return 0, ErrInvalidPact
return 0, ErrInvalidMockServerConfig
case -3:
return 0, ErrMockServerUnableToStart
case -4:
return 0, ErrMockServerPanic
case -5:
return 0, ErrInvalidAddress
case -6:
return 0, ErrMockServerTLSConfiguration
default:
if port > 0 {
log.Println("[DEBUG] mock server running on port:", port)
return port, nil
if msPort > 0 {
log.Println("[DEBUG] mock server running on port:", msPort)
return msPort, nil
}
return port, fmt.Errorf("an unknown error (code: %v) occurred when starting a mock server for the test", port)
return msPort, fmt.Errorf("an unknown error (code: %v) occurred when starting a mock server for the test", msPort)
}
}

Expand Down Expand Up @@ -643,6 +612,22 @@ func (i *Interaction) WithStatus(status int) *Interaction {
return i
}

// WithReference records an external reference (e.g. a ticket or pull request)
// against the interaction. References are stored under comments.references[group][name]
// in the Pact file. This is a V4-only feature.
func (i *Interaction) WithReference(group, name, value string) *Interaction {
cGroup := C.CString(group)
defer free(cGroup)
cName := C.CString(name)
defer free(cName)
cValue := C.CString(value)
defer free(cValue)

C.pactffi_add_interaction_reference(i.handle, cGroup, cName, cValue)

return i
}

type stringLike interface {
String() string
}
Expand Down
Loading
Loading