diff --git a/command/root_test.go b/command/root_test.go index 305afb291..aa786e684 100644 --- a/command/root_test.go +++ b/command/root_test.go @@ -54,7 +54,7 @@ func captureOutput(action func()) string { action() - _ = w.Close() + w.Close() out, _ := io.ReadAll(r) os.Stderr = rescueStderr diff --git a/consumer/http.go b/consumer/http.go index 303087f23..77c36fd7a 100644 --- a/consumer/http.go +++ b/consumer/http.go @@ -201,7 +201,7 @@ func (p *httpMockProvider) displayMismatches(t *testing.T, mismatches []native.M fmt.Println("\t\tDiff:") log.Println("[INFO] pact validation failed, errors: ") for _, m := range mismatches { - formattedRequest := fmt.Sprintf("%s %s", m.Method, m.Path) + formattedRequest := fmt.Sprintf("%s %s", m.Request.Method, m.Request.Path) switch m.Type { case "missing-request": fmt.Printf("\t\texpected: \t%s (Expected request that was not received)\n", formattedRequest) diff --git a/consumer/http_v2.go b/consumer/http_v2.go index 276a403ef..6d4c224fa 100644 --- a/consumer/http_v2.go +++ b/consumer/http_v2.go @@ -35,7 +35,7 @@ func NewV2Pact(config MockHTTPProviderConfig) (*V2HTTPMockProvider, error) { // AddInteraction to the pact func (p *V2HTTPMockProvider) AddInteraction() *V2UnconfiguredInteraction { log.Println("[DEBUG] pact add V2 interaction") - interaction := p.mockserver.NewInteraction("") + interaction := p.httpMockProvider.mockserver.NewInteraction("") i := &V2UnconfiguredInteraction{ interaction: &Interaction{ diff --git a/consumer/http_v3.go b/consumer/http_v3.go index 32d25fb9c..bccce0bac 100644 --- a/consumer/http_v3.go +++ b/consumer/http_v3.go @@ -35,7 +35,7 @@ func NewV3Pact(config MockHTTPProviderConfig) (*V3HTTPMockProvider, error) { // AddInteraction to the pact func (p *V3HTTPMockProvider) AddInteraction() *V3UnconfiguredInteraction { log.Println("[DEBUG] pact add V3 interaction") - interaction := p.mockserver.NewInteraction("") + interaction := p.httpMockProvider.mockserver.NewInteraction("") i := &V3UnconfiguredInteraction{ interaction: &Interaction{ diff --git a/consumer/http_v4.go b/consumer/http_v4.go index 55a79c9a4..8dd986b79 100644 --- a/consumer/http_v4.go +++ b/consumer/http_v4.go @@ -36,7 +36,7 @@ func NewV4Pact(config MockHTTPProviderConfig) (*V4HTTPMockProvider, error) { // AddInteraction to the pact func (p *V4HTTPMockProvider) AddInteraction() *V4UnconfiguredInteraction { log.Println("[DEBUG] pact add V4 interaction") - interaction := p.mockserver.NewInteraction("") + interaction := p.httpMockProvider.mockserver.NewInteraction("") i := &V4UnconfiguredInteraction{ interaction: &Interaction{ @@ -92,15 +92,6 @@ 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) diff --git a/consumer/http_v4_test.go b/consumer/http_v4_test.go index efb421bde..33165496b 100644 --- a/consumer/http_v4_test.go +++ b/consumer/http_v4_test.go @@ -2,7 +2,6 @@ package consumer import ( "fmt" - "net/http" "os" "strings" "testing" @@ -86,25 +85,6 @@ 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 diff --git a/examples/avro/codec.go b/examples/avro/codec.go index 4ad4798ac..fb4d2aca8 100644 --- a/examples/avro/codec.go +++ b/examples/avro/codec.go @@ -1,12 +1,11 @@ package avro import ( + "os" - "github.com/linkedin/goavro/v2" ) -//nolint:unused // Retained as a reusable helper for the example package. func getCodec() *goavro.Codec { schema, err := os.ReadFile("user.avsc") if err != nil { diff --git a/examples/grpc/routeguide/server/server.go b/examples/grpc/routeguide/server/server.go index 0751994b1..fa59e7ddf 100644 --- a/examples/grpc/routeguide/server/server.go +++ b/examples/grpc/routeguide/server/server.go @@ -40,8 +40,9 @@ import ( "github.com/pact-foundation/pact-go/v2/examples/grpc/routeguide/data" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" + "google.golang.org/grpc/status" + + "github.com/golang/protobuf/proto" pb "github.com/pact-foundation/pact-go/v2/examples/grpc/routeguide" ) @@ -54,15 +55,6 @@ var ( port = flag.Int("port", 50051, "The server port") ) -// Keep example flags/entrypoint available for standalone usage. -var ( - _ = tls - _ = certFile - _ = keyFile - _ = port - _ = main -) - type routeGuideServer struct { pb.UnimplementedRouteGuideServer savedFeatures []*pb.Feature // read-only after initialized @@ -255,9 +247,7 @@ func main() { } grpcServer := grpc.NewServer(opts...) pb.RegisterRouteGuideServer(grpcServer, NewServer()) - if err := grpcServer.Serve(lis); err != nil { - log.Fatalf("failed to serve: %v", err) - } + grpcServer.Serve(lis) } // exampleData is a copy of testdata/route_guide_db.json. It's to avoid diff --git a/installer/installer.go b/installer/installer.go index 5e421d089..30965e1fa 100644 --- a/installer/installer.go +++ b/installer/installer.go @@ -302,7 +302,7 @@ var setMacOSInstallName = func(file string) error { return fmt.Errorf("error setting install name on pact lib: %s", err) } - log.Println("[DEBUG] output from command", string(stdoutStderr)) + log.Println("[DEBUG] output from command", stdoutStderr) return err } @@ -388,7 +388,7 @@ const ( var packages = map[string]packageInfo{ FFIPackage: { libName: "libpact_ffi", - version: "0.5.6", + version: "0.4.28", semverRange: ">= 0.4.0, < 1.0.0", }, } @@ -417,17 +417,13 @@ func (d *defaultDownloader) download(src string, dst string) error { if err != nil { return fmt.Errorf("failed to create output file; %w", err) } - defer func() { - _ = f.Close() - }() + defer f.Close() resp, err := http.Get(src) if err != nil { return fmt.Errorf("failed http call to %s; %w", src, err) } - defer func() { - _ = resp.Body.Close() - }() + defer resp.Body.Close() archive, err := gzip.NewReader(resp.Body) if err != nil { @@ -527,9 +523,7 @@ func (d *defaultHasher) hash(src string) (string, error) { if err != nil { return "", err } - defer func() { - _ = f.Close() - }() + defer f.Close() h := md5.New() if _, err := io.Copy(h, f); err != nil { diff --git a/internal/native/message_server.go b/internal/native/message_server.go index e32b84cca..bd6e6ec3a 100644 --- a/internal/native/message_server.go +++ b/internal/native/message_server.go @@ -232,7 +232,7 @@ func (m *Message) WithContents(part interactionPart, contentType string, body [] defer free(cHeader) cBody := C.CString(string(body)) - defer free(cBody) + defer free(cBody) res := C.pactffi_with_body(m.handle, C.int(part), cHeader, cBody) log.Println("[DEBUG] response from pactffi_interaction_contents", (bool(res))) @@ -264,7 +264,7 @@ func (m *MessageServer) UsingPlugin(pluginName string, pluginVersion string) err return ErrHandleNotFound default: if res != 0 { - return fmt.Errorf("an unknown error (code: %v) occurred when adding a plugin for the test. Received error code", res) + return fmt.Errorf("an unknown error (code: %v) occurred when adding a plugin for the test. Received error code:", res) } } @@ -302,7 +302,7 @@ func (m *Message) WithPluginInteractionContents(part interactionPart, contentTyp return ErrPluginSpecificError default: if res != 0 { - return fmt.Errorf("an unknown error (code: %v) occurred when adding a plugin for the test. Received error code", res) + return fmt.Errorf("an unknown error (code: %v) occurred when adding a plugin for the test. Received error code:", res) } } @@ -543,7 +543,10 @@ func (m *MessageServer) WritePactFile(dir string, overwrite bool) error { cDir := C.CString(dir) defer free(cDir) - overwritePact := overwrite + overwritePact := false + if overwrite { + overwritePact = true + } res := int(C.pactffi_write_message_pact_file(m.messagePact.handle, cDir, C.bool(overwritePact))) @@ -569,7 +572,10 @@ func (m *MessageServer) WritePactFileForServer(port int, dir string, overwrite b cDir := C.CString(dir) defer free(cDir) - overwritePact := overwrite + overwritePact := false + if overwrite { + overwritePact = true + } res := int(C.pactffi_write_pact_file(C.int(port), cDir, C.bool(overwritePact))) @@ -590,19 +596,3 @@ 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 -} diff --git a/internal/native/message_server_test.go b/internal/native/message_server_test.go index 73f7d140b..0431b8e05 100644 --- a/internal/native/message_server_test.go +++ b/internal/native/message_server_test.go @@ -406,9 +406,7 @@ func TestGrpcPluginInteraction(t *testing.T) { if err != nil { l.Fatalf("did not connect: %v", err) } - defer func() { - _ = conn.Close() - }() + defer conn.Close() c := NewPactPluginClient(conn) // Contact the server and print out its response. @@ -482,9 +480,7 @@ func TestGrpcPluginInteraction_ErrorResponse(t *testing.T) { if err != nil { l.Fatalf("did not connect: %v", err) } - defer func() { - _ = conn.Close() - }() + defer conn.Close() c := NewPactPluginClient(conn) // Contact the server and print out its response. diff --git a/internal/native/mock_server.go b/internal/native/mock_server.go index b32321ee0..38ab72d36 100644 --- a/internal/native/mock_server.go +++ b/internal/native/mock_server.go @@ -11,9 +11,7 @@ import ( "encoding/json" "fmt" "log" - "net" "os" - "strconv" "strings" "unsafe" ) @@ -136,6 +134,52 @@ 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) { @@ -237,36 +281,21 @@ func libRustFree(str *C.char) { } // Start starts up the mock HTTP server on the given address:port and TLS config -// https://docs.rs/pact_ffi/latest/pact_ffi/mock_server/fn.pactffi_create_mock_server_for_transport.html +// 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) { if len(m.interactions) == 0 { return 0, ErrNoInteractions } log.Println("[DEBUG] mock server starting on address:", address) - - host, portStr, err := net.SplitHostPort(address) - if err != nil { - return 0, ErrInvalidAddress - } - requestedPort, err := strconv.Atoi(portStr) - if err != nil { - return 0, ErrInvalidAddress - } - cHost := C.CString(host) - defer free(cHost) - var transport string + cAddress := C.CString(address) + defer free(cAddress) + tlsEnabled := false if tls { - transport = "https" - } else { - transport = "http" + tlsEnabled = true } - cTransport := C.CString(transport) - defer free(cTransport) - - cConfig := (*C.char)(nil) - msPort := int(C.pactffi_create_mock_server_for_transport(m.pact.handle, cHost, C.ushort(requestedPort), cTransport, cConfig)) + p := C.pactffi_create_mock_server_for_pact(m.pact.handle, cAddress, C.bool(tlsEnabled)) // | Error | Description | // |-------|-------------| @@ -276,23 +305,26 @@ func (m *MockServer) Start(address string, tls bool) (int, error) { // | -4 | The method panicked | // | -5 | The address is not valid | // | -6 | Could not create the TLS configuration with the self-signed certificate | - switch msPort { + port := int(p) + switch port { case -1: return 0, ErrInvalidMockServerConfig case -2: - return 0, ErrInvalidMockServerConfig + 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 msPort > 0 { - log.Println("[DEBUG] mock server running on port:", msPort) - return msPort, nil + if port > 0 { + log.Println("[DEBUG] mock server running on port:", port) + return port, nil } - return msPort, fmt.Errorf("an unknown error (code: %v) occurred when starting a mock server for the test", msPort) + return port, fmt.Errorf("an unknown error (code: %v) occurred when starting a mock server for the test", port) } } @@ -380,7 +412,7 @@ func (m *MockServer) UsingPlugin(pluginName string, pluginVersion string) error return ErrHandleNotFound default: if res != 0 { - return fmt.Errorf("an unknown error (code: %v) occurred when adding a plugin for the test. Received error code", res) + return fmt.Errorf("an unknown error (code: %v) occurred when adding a plugin for the test. Received error code:", res) } } @@ -436,7 +468,7 @@ func (i *Interaction) WithPluginInteractionContents(part interactionPart, conten return ErrPluginSpecificError default: if res != 0 { - return fmt.Errorf("an unknown error (code: %v) occurred when adding a plugin for the test. Received error code", res) + return fmt.Errorf("an unknown error (code: %v) occurred when adding a plugin for the test. Received error code:", res) } } @@ -611,22 +643,6 @@ 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 } @@ -762,11 +778,11 @@ var ( // Log Errors var ( - ErrCantSetLogger = fmt.Errorf("can't set logger (applying the logger failed, perhaps because one is applied already)") - ErrNoLogger = fmt.Errorf("no logger has been initialized (call `logger_init` before any other log function)") - ErrSpecifierNotUtf8 = fmt.Errorf("the sink specifier was not UTF-8 encoded") - ErrUnknownSinkType = fmt.Errorf(`the sink type specified is not a known type (known types: "buffer", "stdout", "stderr", or "file /some/path")`) - ErrMissingFilePath = fmt.Errorf("no file path was specified in a file-type sink specification") - ErrCantOpenSinkToFile = fmt.Errorf("opening a sink to the specified file path failed (check permissions)") + ErrCantSetLogger = fmt.Errorf("can't set logger (applying the logger failed, perhaps because one is applied already).") + ErrNoLogger = fmt.Errorf("no logger has been initialized (call `logger_init` before any other log function).") + ErrSpecifierNotUtf8 = fmt.Errorf("The sink specifier was not UTF-8 encoded.") + ErrUnknownSinkType = fmt.Errorf(`the sink type specified is not a known type (known types: "buffer", "stdout", "stderr", or "file /some/path").`) + ErrMissingFilePath = fmt.Errorf("no file path was specified in a file-type sink specification.") + ErrCantOpenSinkToFile = fmt.Errorf("opening a sink to the specified file path failed (check permissions).") ErrCantConstructSink = fmt.Errorf("can't construct the log sink") ) diff --git a/internal/native/mock_server_test.go b/internal/native/mock_server_test.go index 7e53d8057..aa79a6373 100644 --- a/internal/native/mock_server_test.go +++ b/internal/native/mock_server_test.go @@ -17,34 +17,9 @@ func init() { Init("") } -// newSimpleMockServer creates a mock server with a simple GET /foobar → 200 interaction -// using the programmatic API, replacing the removed CreateMockServer function. -func newSimpleMockServer(t *testing.T) (*MockServer, int) { - t.Helper() - m := NewHTTPPact("consumer", "provider") - m.NewInteraction("Some name for the test"). - UponReceiving("Some name for the test"). - Given("Some state"). - WithRequest("GET", "/foobar"). - WithStatus(200) - port, err := m.Start("0.0.0.0:0", false) - if err != nil { - t.Fatalf("failed to start mock server: %v", err) - } - return m, port -} - func TestMockServer_CreateAndCleanupMockServer(t *testing.T) { - m := NewHTTPPact("consumer", "provider") - m.NewInteraction("Some complex interaction"). - UponReceiving("Some complex interaction"). - Given("Some state"). - WithRequest("GET", "/foobar"). - WithStatus(200) - port, err := m.Start("0.0.0.0:0", false) - if err != nil { - t.Fatal("failed to start mock server:", err) - } + m := MockServer{} + port, _ := m.CreateMockServer(pactComplex, "0.0.0.0:0", false) defer m.CleanupMockServer(port) if port <= 0 { @@ -53,7 +28,8 @@ func TestMockServer_CreateAndCleanupMockServer(t *testing.T) { } func TestMockServer_MismatchesSuccess(t *testing.T) { - m, port := newSimpleMockServer(t) + m := MockServer{} + port, _ := m.CreateMockServer(pactSimple, "0.0.0.0:0", false) defer m.CleanupMockServer(port) res, err := http.Get(fmt.Sprintf("http://localhost:%d/foobar", port)) @@ -72,7 +48,8 @@ func TestMockServer_MismatchesSuccess(t *testing.T) { } func TestMockServer_MismatchesFail(t *testing.T) { - m, port := newSimpleMockServer(t) + m := MockServer{} + port, _ := m.CreateMockServer(pactSimple, "0.0.0.0:0", false) defer m.CleanupMockServer(port) mismatches := m.MockServerMismatchedRequests(port) @@ -85,7 +62,8 @@ func TestMockServer_VerifySuccess(t *testing.T) { tmpPactFolder, err := os.MkdirTemp("", "pact-go") assert.NoError(t, err) - m, port := newSimpleMockServer(t) + m := MockServer{} + port, _ := m.CreateMockServer(pactSimple, "0.0.0.0:0", false) defer m.CleanupMockServer(port) _, err = http.Get(fmt.Sprintf("http://localhost:%d/foobar", port)) @@ -106,7 +84,8 @@ func TestMockServer_VerifySuccess(t *testing.T) { func TestMockServer_VerifyFail(t *testing.T) { tmpPactFolder, err := os.MkdirTemp("", "pact-go") assert.NoError(t, err) - m, port := newSimpleMockServer(t) + m := MockServer{} + port, _ := m.CreateMockServer(pactSimple, "0.0.0.0:0", false) success, mismatches := m.Verify(port, tmpPactFolder) if success { @@ -122,7 +101,8 @@ func TestMockServer_WritePactfile(t *testing.T) { tmpPactFolder, err := os.MkdirTemp("", "pact-go") assert.NoError(t, err) - m, port := newSimpleMockServer(t) + m := MockServer{} + port, _ := m.CreateMockServer(pactSimple, "0.0.0.0:0", false) defer m.CleanupMockServer(port) _, err = http.Get(fmt.Sprintf("http://localhost:%d/foobar", port)) @@ -175,6 +155,7 @@ func TestHandleBasedHTTPTests(t *testing.T) { _, err = http.Get(fmt.Sprintf("http://0.0.0.0:%d/products", port)) assert.NoError(t, err) + mismatches := m.MockServerMismatchedRequests(port) if len(mismatches) != 0 { t.Fatalf("want 0 mismatches, got '%d'", len(mismatches)) @@ -242,3 +223,73 @@ func TestPluginInteraction(t *testing.T) { err = m.WritePactFile(port, tmpPactFolder) assert.NoError(t, err) } + +var pactSimple = `{ + "consumer": { + "name": "consumer" + }, + "provider": { + "name": "provider" + }, + "interactions": [ + { + "description": "Some name for the test", + "request": { + "method": "GET", + "path": "/foobar" + }, + "response": { + "status": 200 + }, + "description": "Some name for the test", + "provider_state": "Some state" + }] +}` + +var pactComplex = `{ + "consumer": { + "name": "consumer" + }, + "provider": { + "name": "provider" + }, + "interactions": [ + { + "request": { + "method": "GET", + "path": "/foobar", + "body": { + "pass": 1234, + "user": { + "address": "some address", + "name": "someusername", + "phone": 12345678, + "plaintext": "plaintext" + } + } + }, + "response": { + "status": 200 + }, + "description": "Some name for the test", + "provider_state": "Some state", + "matchingRules": { + "$.body.pass": { + "match": "regex", + "regex": "\\d+" + }, + "$.body.user.address": { + "match": "regex", + "regex": "\\s+" + }, + "$.body.user.name": { + "match": "regex", + "regex": "\\s+" + }, + "$.body.user.phone": { + "match": "regex", + "regex": "\\d+" + } + } + }] +}` diff --git a/internal/native/pact.h b/internal/native/pact.h index 342a601e8..c5865d040 100644 --- a/internal/native/pact.h +++ b/internal/native/pact.h @@ -7,7 +7,7 @@ #ifndef pact_ffi_h #define pact_ffi_h -/* Generated with cbindgen:0.29.2 */ +/* Generated with cbindgen:0.26.0 */ /* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ @@ -657,32 +657,18 @@ int pactffi_get_error_message(char *buffer, /** * Convenience function to direct all logging to stdout. - * - * This function is equivalent to using [`pactffi_logger_init`] followed by the - * [`pactffi_logger_attach_sink`] with the appropriate sink specifier, and then - * [`pactffi_logger_apply`]. */ int pactffi_log_to_stdout(int level_filter); /** * Convenience function to direct all logging to stderr. - * - * This function is equivalent to using [`pactffi_logger_init`] followed by the - * [`pactffi_logger_attach_sink`] with the appropriate sink specifier, and then - * [`pactffi_logger_apply`]. */ int pactffi_log_to_stderr(int level_filter); /** * Convenience function to direct all logging to a file. * - * - * This function is equivalent to using [`pactffi_logger_init`] followed by the - * [`pactffi_logger_attach_sink`] with the appropriate sink specifier, and then - * [`pactffi_logger_apply`]. - * * # Safety - * * This function will fail if the file_name pointer is invalid or does not point to a NULL * terminated string. */ @@ -690,13 +676,6 @@ int pactffi_log_to_file(const char *file_name, int level_filter); /** * Convenience function to direct all logging to a task local memory buffer. - * - * This function is equivalent to using [`pactffi_logger_init`] followed by the - * [`pactffi_logger_attach_sink`] with the appropriate sink specifier, and then - * [`pactffi_logger_apply`]. - * - * The contents of the buffer can be fetched using - * [`pactffi_fetch_log_buffer`]. */ int pactffi_log_to_buffer(int level_filter); @@ -742,7 +721,7 @@ void pactffi_logger_init(void); * - `-1`: Can't set logger (applying the logger failed, perhaps because one is applied already). * - `-2`: No logger has been initialized (call `pactffi_logger_init` before any other log function). * - `-3`: The sink specifier was not UTF-8 encoded. - * - `-4`: The sink type specified is not a known type (known types: "stdout", "stderr", "buffer", or "file /some/path"). + * - `-4`: The sink type specified is not a known type (known types: "stdout", "stderr", or "file /some/path"). * - `-5`: No file path was specified in a file-type sink specification. * - `-6`: Opening a sink to the specified file path failed (check permissions). * @@ -761,12 +740,6 @@ int pactffi_logger_attach_sink(const char *sink_specifier, * * This function will install a global tracing subscriber. Any attempts to modify the logger * after the call to `logger_apply` will fail. - * - * # Error Handling - * - * The return error codes are as follows: - * - * - `-1`: Can't set logger (applying the logger failed, perhaps because one is applied already). */ int pactffi_logger_apply(void); @@ -820,7 +793,7 @@ struct PactInteractionIterator *pactffi_pact_model_interaction_iterator(struct P /** * Returns the Pact specification enum that the Pact is for. */ -enum PactSpecification pactffi_pact_spec_version(const struct Pact *pact); +int pactffi_pact_spec_version(const struct Pact *pact); /** * Frees the memory used by the Pact interaction model @@ -3116,6 +3089,35 @@ struct ProviderStateIterator *pactffi_sync_message_get_provider_state_iter(struc */ void pactffi_string_delete(char *string); +/** + * [DEPRECATED] External interface to create a HTTP mock server. A pointer to the pact JSON as a NULL-terminated C + * string is passed in, as well as the port for the mock server to run on. A value of 0 for the + * port will result in a port being allocated by the operating system. The port of the mock server is returned. + * + * * `pact_str` - Pact JSON + * * `addr_str` - Address to bind to in the form name:port (i.e. 127.0.0.1:0) + * * `tls` - boolean flag to indicate of the mock server should use TLS (using a self-signed certificate) + * + * This function is deprecated and replaced with `pactffi_create_mock_server_for_transport`. + * + * # Errors + * + * Errors are returned as negative values. + * + * | 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 | + * + */ +int32_t pactffi_create_mock_server(const char *pact_str, + const char *addr_str, + bool tls); + /** * Fetch the CA Certificate used to generate the self-signed certificate for the TLS mock server. * @@ -3128,6 +3130,34 @@ void pactffi_string_delete(char *string); */ char *pactffi_get_tls_ca_certificate(void); +/** + * [DEPRECATED] External interface to create a HTTP mock server. A Pact handle is passed in, + * as well as the port for the mock server to run on. A value of 0 for the port will result in a + * port being allocated by the operating system. The port of the mock server is returned. + * + * * `pact` - Handle to a Pact model created with created with `pactffi_new_pact`. + * * `addr_str` - Address to bind to in the form name:port (i.e. 127.0.0.1:0). Must be a valid UTF-8 NULL-terminated string. + * * `tls` - boolean flag to indicate of the mock server should use TLS (using a self-signed certificate) + * + * This function is deprecated and replaced with `pactffi_create_mock_server_for_transport`. + * + * # Errors + * + * Errors are returned as negative values. + * + * | Error | Description | + * |-------|-------------| + * | -1 | An invalid handle was received. Handles should be created with `pactffi_new_pact` | + * | -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 | + * + */ +int32_t pactffi_create_mock_server_for_pact(PactHandle pact, + const char *addr_str, + bool tls); + /** * Create a mock server for the provided Pact handle and transport. If the transport is not * provided (it is a NULL pointer or an empty string), will default to an HTTP transport. The @@ -3518,12 +3548,11 @@ bool pactffi_with_specification(PactHandle pact, int pactffi_handle_get_pact_spec_version(PactHandle pact); /** - * Sets the additional metadata on the Pact file. Common uses are to add the client library - * details such as the name and version. Returns false if the interaction or Pact can't be - * modified (i.e. the mock server for it has already started) or the namespace is readonly. + * Sets the additional metadata on the Pact file. Common uses are to add the client library details such as the name and version + * Returns false if the interaction or Pact can't be modified (i.e. the mock server for it has already started) * * * `pact` - Handle to a Pact model - * * `namespace` - the top level metadata key to set any key values on + * * `namespace` - the top level metadat key to set any key values on * * `name` - the key to set * * `value` - the value to set */ @@ -3696,43 +3725,30 @@ bool pactffi_response_status_v2(InteractionHandle interaction, const char *status); /** - * Adds the body for the interaction. Returns false if the interaction or Pact - * can't be modified (i.e. the mock server for it has already started) - * - * * `part` - The part of the interaction to add the body to (Request or - * Response). This is ignored for asynchronous message interactions. - * * `content_type` - The content type of the body, or `NULL` to use the - * internal logic. - * * `body` - The body contents. For JSON payloads, matching rules can be - * embedded in the body. See - * [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/master/rust/pact_ffi/IntegrationJson.md) - * - * The payload's content type is determined as follows, whichever is first: - * - * - The `content_type` argument to this function if provided. If the provided - * value fails to parse, and error is logged and it will be ignored. - * - The `Content-Type` header for HTTP interaction, or `contentType` metadata - * entry for message interactions. - * - From automatic detection of the body contents. - * - Defaults to `text/plain` as a last resort. + * Adds the body for the interaction. Returns false if the interaction or Pact can't be + * modified (i.e. the mock server for it has already started) * - * Furthermore, the `Content-Type` header or `contentType` metadata entry will - * be updated with the above determined content type, _unless_ it is already - * set. + * * `part` - The part of the interaction to add the body to (Request or Response). + * * `content_type` - The content type of the body. Defaults to `text/plain`. Will be ignored if a content type + * header is already set. + * * `body` - The body contents. For JSON payloads, matching rules can be embedded in the body. See + * [IntegrationJson.md](https://github.com/pact-foundation/pact-reference/blob/master/rust/pact_ffi/IntegrationJson.md) * - * This function will overwrite the body contents if they exist, with the - * exception of the response part of synchronous message interactions, where a - * new response will be appended. + * For HTTP and async message interactions, this will overwrite the body. With asynchronous messages, the + * part parameter will be ignored. With synchronous messages, the request contents will be overwritten, + * while a new response will be appended to the message. * * # Safety * - * The interaction contents and content type must either be NULL pointers, or - * point to valid UTF-8 encoded NULL-terminated strings. Otherwise, behaviour - * is undefined. + * The interaction contents and content type must either be NULL pointers, or point to valid + * UTF-8 encoded NULL-terminated strings. Otherwise, behaviour is undefined. * * # Error Handling * - * If the contents is a NULL pointer, it will set the body contents as null. + * If the contents is a NULL pointer, it will set the body contents as null. If the content + * type is a null pointer, or can't be parsed, it will set the content type as TEXT. + * Returns false if the interaction or Pact can't be modified (i.e. the mock server for it has + * already started) or an error has occurred. */ bool pactffi_with_body(InteractionHandle interaction, int part, @@ -3776,14 +3792,6 @@ bool pactffi_with_binary_body(InteractionHandle interaction, size_t size); /** - *
- * - * This function is deprecated. Use [`pactffi_with_binary_body`] in order to - * set the binary body, and use [`pactffi_with_matching_rules`] to set the - * matching rules to ensure that only the content type is being matched. - * - *
- * * Adds a binary file as the body with the expected content type and example contents. Will use * a mime type matcher to match the body. Returns false if the interaction or Pact can't be * modified (i.e. the mock server for it has already started) @@ -3982,8 +3990,7 @@ bool pactffi_set_pending(InteractionHandle interaction, bool pending); * * # Safety * - * The key parameter must be a valid pointer to a NULL terminated UTF-8. - * The value parameter must be a valid pointer to a NULL terminated UTF-8, + * The comments parameter must be a valid pointer to a NULL terminated UTF-8, * or NULL if the comment is to be cleared. */ bool pactffi_set_comment(InteractionHandle interaction, const char *key, const char *value); @@ -4006,33 +4013,11 @@ bool pactffi_set_comment(InteractionHandle interaction, const char *key, const c * * # Safety * - * The comment parameter must be a valid pointer to a NULL terminated UTF-8. + * The comments parameter must be a valid pointer to a NULL terminated UTF-8, + * or NULL if the comment is to be cleared. */ bool pactffi_add_text_comment(InteractionHandle interaction, const char *comment); -/** - * Add an external reference to the interaction. The reference will be stored in the Pact - * file comments under the `references` key, grouped by `group`. For instance, you could - * store the AsyncAPI operation ID that the interaction corresponds to as an external reference. - * - * * `interaction` - Interaction handle to add the reference to. - * * `group` - Group name for the reference (e.g. `"asyncapi"`). - * * `name` - Name of the reference entry within the group (e.g. `"operationId"`). - * * `value` - Value of the reference. This may be any valid JSON value (parsed automatically), - * or a plain string if JSON parsing fails. - * - * This function will return `true` if the reference was successfully added. All parameters - * must be valid UTF-8 null-terminated strings. - * - * # Safety - * - * All parameters must be valid pointers to NULL terminated UTF-8 strings. - */ -bool pactffi_add_interaction_reference(InteractionHandle interaction, - const char *group, - const char *name, - const char *value); - /** * Get an iterator over all the messages of the Pact. The returned iterator needs to be * freed with `pactffi_pact_message_iter_delete`. @@ -4137,14 +4122,11 @@ void pactffi_message_expects_to_receive(MessageHandle message, const char *descr void pactffi_message_given(MessageHandle message, const char *description); /** - * Adds a parameter key and value to a provider state to the Message. If the provider state - * does not exist, a new one will be created, otherwise the parameter will be merged into the - * existing one. The parameter value will be parsed as JSON. + * Adds a provider state to the Message with a parameter key and value. * - * # Parameters * * `description` - The provider state description. It needs to be unique. * * `name` - Parameter name. - * * `value` - Parameter value as JSON. + * * `value` - Parameter value. */ void pactffi_message_given_with_param(MessageHandle message, const char *description, @@ -4315,13 +4297,32 @@ unsigned int pactffi_free_pact_handle(PactHandle pact); */ unsigned int pactffi_free_message_pact_handle(MessagePactHandle pact); +/** + * External interface to verifier a provider + * + * * `args` - the same as the CLI interface, except newline delimited + * + * # Errors + * + * Errors are returned as non-zero numeric values. + * + * | Error | Description | + * |-------|-------------| + * | 1 | The verification process failed, see output for errors | + * | 2 | A null pointer was received | + * | 3 | The method panicked | + * | 4 | Invalid arguments were provided to the verification process | + * + * # Safety + * + * Exported functions are inherently unsafe. Deal. + */ +int32_t pactffi_verify(const char *args); + /** * Get a Handle to a newly created verifier. You should call `pactffi_verifier_shutdown` when * done with the verifier to free all allocated resources. * - * By default, verification results will not be published. To enable publishing, use - * `pactffi_verifier_set_publish_options` to set the required values and enable it. - * * Deprecated: This function is deprecated. Use `pactffi_verifier_new_for_application` which allows the * calling application/framework name and version to be specified. * @@ -4337,10 +4338,7 @@ struct VerifierHandle *pactffi_verifier_new(void); /** * Get a Handle to a newly created verifier. You should call `pactffi_verifier_shutdown` when - * done with the verifier to free all allocated resources. - * - * By default, verification results will not be published. To enable publishing, use - * `pactffi_verifier_set_publish_options` to set the required values and enable it. + * done with the verifier to free all allocated resources * * # Safety * @@ -4476,19 +4474,16 @@ int pactffi_verifier_set_coloured_output(struct VerifierHandle *handle, int pactffi_verifier_set_no_pacts_is_error(struct VerifierHandle *handle, unsigned char is_error); /** - * Set the options used when publishing verification results to the Pact Broker. By default, - * verification results will not be published unless this function is called. + * Set the options used when publishing verification results to the Pact Broker * * # Args * * - `handle` - The pact verifier handle to update * - `provider_version` - Version of the provider to publish - * - `build_url` - URL to the build which ran the verification [OPTIONAL] - * - `provider_tags` - Collection of tags for the provider [OPTIONAL] - * - `provider_tags_len` - Number of provider tags supplied [OPTIONAL] - * - `provider_branch` - Name of the branch used for verification [OPTIONAL] - * - * For optional args, a NULL pointer can be used. + * - `build_url` - URL to the build which ran the verification + * - `provider_tags` - Collection of tags for the provider + * - `provider_tags_len` - Number of provider tags supplied + * - `provider_branch` - Name of the branch used for verification * * # Safety * @@ -4528,12 +4523,6 @@ void pactffi_verifier_add_custom_header(struct VerifierHandle *handle, const char *header_name, const char *header_value); -/** - * Sets whether redirects should be automatically followed. Setting the `follow` parameter - * to zero will disable following redirects. - */ -void pactffi_verifier_set_follow_redirects(struct VerifierHandle *handle, unsigned char follow); - /** * Adds a Pact file as a source to verify. * @@ -4609,29 +4598,26 @@ void pactffi_verifier_broker_source(struct VerifierHandle *handle, * If a username and password is given, then basic authentication will be used when fetching * the pact file. If a token is provided, then bearer token authentication will be used. * - * This function will return zero unless any of the consumer version selectors are not valid - * JSON, in which case, it will return -1. - * * # Safety * * All string fields must contain valid UTF-8. Invalid UTF-8 * will be replaced with U+FFFD REPLACEMENT CHARACTER. * */ -int pactffi_verifier_broker_source_with_selectors(struct VerifierHandle *handle, - const char *url, - const char *username, - const char *password, - const char *token, - unsigned char enable_pending, - const char *include_wip_pacts_since, - const char *const *provider_tags, - unsigned short provider_tags_len, - const char *provider_branch, - const char *const *consumer_version_selectors, - unsigned short consumer_version_selectors_len, - const char *const *consumer_version_tags, - unsigned short consumer_version_tags_len); +void pactffi_verifier_broker_source_with_selectors(struct VerifierHandle *handle, + const char *url, + const char *username, + const char *password, + const char *token, + unsigned char enable_pending, + const char *include_wip_pacts_since, + const char *const *provider_tags, + unsigned short provider_tags_len, + const char *provider_branch, + const char *const *consumer_version_selectors, + unsigned short consumer_version_selectors_len, + const char *const *consumer_version_tags, + unsigned short consumer_version_tags_len); /** * Runs the verification. @@ -4642,6 +4628,57 @@ int pactffi_verifier_broker_source_with_selectors(struct VerifierHandle *handle, */ int pactffi_verifier_execute(struct VerifierHandle *handle); +/** + * External interface to retrieve the options and arguments available when calling the CLI interface, + * returning them as a JSON string. + * + * The purpose is to then be able to use in other languages which wrap the FFI library, to implement + * the same CLI functionality automatically without manual maintenance of arguments, help descriptions + * etc. + * + * # Example structure + * ```json + * { + * "options": [ + * { + * "long": "scheme", + * "help": "Provider URI scheme (defaults to http)", + * "possible_values": [ + * "http", + * "https" + * ], + * "default_value": "http" + * "multiple": false, + * }, + * { + * "long": "file", + * "short": "f", + * "help": "Pact file to verify (can be repeated)", + * "multiple": true + * }, + * { + * "long": "user", + * "help": "Username to use when fetching pacts from URLS", + * "multiple": false, + * "env": "PACT_BROKER_USERNAME" + * } + * ], + * "flags": [ + * { + * "long": "disable-ssl-verification", + * "help": "Disables validation of SSL certificates", + * "multiple": false + * } + * ] + * } + * ``` + * + * # Safety + * + * Exported functions are inherently unsafe. + */ +const char *pactffi_verifier_cli_args(void); + /** * Extracts the logs for the verification run. This needs the memory buffer log sink to be * setup before the verification is executed. The returned string will need to be freed with @@ -4679,39 +4716,6 @@ const char *pactffi_verifier_output(const struct VerifierHandle *handle, unsigne */ const char *pactffi_verifier_json(const struct VerifierHandle *handle); -/** - * Add a plugin to be used by the test. The plugin needs to be installed correctly for this - * function to work. - * - * * `plugin_name` is the name of the plugin to load. - * * `plugin_version` is the version of the plugin to load. It is optional, and can be NULL. - * * `completion_delay` is an arbitrary delay specified in milliseconds to add before the - * function returns to allow asynchronous tasks to complete. - * - * Returns zero on success, and a positive integer value on failure. - * - * Note that plugins run as separate processes, so will need to be cleaned up afterwards by - * calling `pactffi_cleanup_plugins` otherwise you will have plugin processes left running. - * - * # Safety - * - * `plugin_name` must be a valid pointer to a NULL terminated string. `plugin_version` may be null, - * and if not NULL must also be a valid pointer to a NULL terminated string. Invalid - * pointers will result in undefined behaviour. - * - * # Errors - * - * * `1` - A general panic was caught. - * * `2` - Failed to load the plugin. - * * `3` - Pact Handle is not valid. - * - * When an error errors, LAST_ERROR will contain the error message. - */ -unsigned int pactffi_using_plugin_with_delay(PactHandle pact, - const char *plugin_name, - const char *plugin_version, - uint64_t completion_delay); - /** * Add a plugin to be used by the test. The plugin needs to be installed correctly for this * function to work. @@ -4929,4 +4933,4 @@ const char *pactffi_matches_json_value(const struct MatchingRule *matching_rule, const char *actual_value, uint8_t cascaded); -#endif /* pact_ffi_h */ +#endif /* pact_ffi_h */ diff --git a/internal/native/plugin.go b/internal/native/plugin.go index 8ebad1592..02a212498 100644 --- a/internal/native/plugin.go +++ b/internal/native/plugin.go @@ -4,10 +4,10 @@ import "fmt" // Plugin Errors var ( - ErrPluginGenericPanic = fmt.Errorf("a general panic was caught") - ErrPluginMockServerStarted = fmt.Errorf("the mock server has already been started") - ErrPluginInteractionHandleInvalid = fmt.Errorf("the interaction handle is invalid") - ErrPluginInvalidContentType = fmt.Errorf("the content type is not valid") - ErrPluginInvalidJson = fmt.Errorf("the contents JSON is not valid JSON") - ErrPluginSpecificError = fmt.Errorf("the plugin returned an error") + ErrPluginGenericPanic = fmt.Errorf("A general panic was caught.") + ErrPluginMockServerStarted = fmt.Errorf("The mock server has already been started.") + ErrPluginInteractionHandleInvalid = fmt.Errorf("The interaction handle is invalid. ") + ErrPluginInvalidContentType = fmt.Errorf("The content type is not valid.") + ErrPluginInvalidJson = fmt.Errorf("The contents JSON is not valid JSON.") + ErrPluginSpecificError = fmt.Errorf("The plugin returned an error.") ) diff --git a/internal/native/verifier.go b/internal/native/verifier.go index d6ff3df30..8605f9dd2 100644 --- a/internal/native/verifier.go +++ b/internal/native/verifier.go @@ -8,6 +8,7 @@ import "C" import ( "fmt" "log" + "strings" "unsafe" ) @@ -15,6 +16,31 @@ type Verifier struct { handle *C.VerifierHandle } +func (v *Verifier) Verify(args []string) error { + log.Println("[DEBUG] executing verifier FFI with args", args) + cargs := C.CString(strings.Join(args, "\n")) + defer free(cargs) + result := C.pactffi_verify(cargs) + + /// | Error | Description | + /// |-------|-------------| + /// | 1 | The verification process failed, see output for errors | + /// | 2 | A null pointer was received | + /// | 3 | The method panicked | + switch int(result) { + case 0: + return nil + case 1: + return ErrVerifierFailed + case 2: + return ErrInvalidVerifierConfig + case 3: + return ErrVerifierPanic + default: + return fmt.Errorf("an unknown error (%d) ocurred when verifying the provider (this indicates a defect in the framework)", int(result)) + } +} + // Version returns the current semver FFI interface version func (v *Verifier) Version() string { return Version() diff --git a/internal/native/verifier_test.go b/internal/native/verifier_test.go index 5d6b3cb33..714d65551 100644 --- a/internal/native/verifier_test.go +++ b/internal/native/verifier_test.go @@ -18,6 +18,29 @@ func TestVerifier_Version(t *testing.T) { fmt.Println("version: ", Version()) } +func TestVerifier_Verify(t *testing.T) { + t.Run("invalid args returns an error", func(t *testing.T) { + + v := Verifier{} + args := []string{ + "--file", + "/non/existent/path.json", + "--hostname", + "localhost", + "--port", + "55827", + "--state-change-url", + "http://localhost:55827/__setup/", + "--loglevel", + "info", + } + + res := v.Verify(args) + + assert.Error(t, res) + }) +} + func TestVerifier_NewForApplication(t *testing.T) { v := NewVerifier("pact-go", "test") diff --git a/matchers/matcher.go b/matchers/matcher.go index c9b93baf2..f0bdb9e34 100644 --- a/matchers/matcher.go +++ b/matchers/matcher.go @@ -298,8 +298,8 @@ func MatchV2(src interface{}) Matcher { // match recursively traverses the provided type and outputs a // matcher string for it that is compatible with the Pact dsl. func match(srcType reflect.Type, params params) Matcher { - switch srcType.Kind() { - case reflect.Pointer: + switch kind := srcType.Kind(); kind { + case reflect.Ptr: return match(srcType.Elem(), params) case reflect.Slice, reflect.Array: return EachLike(match(srcType.Elem(), getDefaults()), params.slice.min) diff --git a/message/v3/asynchronous_message.go b/message/v3/asynchronous_message.go index 77085ebac..acf0af190 100644 --- a/message/v3/asynchronous_message.go +++ b/message/v3/asynchronous_message.go @@ -12,6 +12,7 @@ import ( "github.com/pact-foundation/pact-go/v2/command" "github.com/pact-foundation/pact-go/v2/internal/native" + mockserver "github.com/pact-foundation/pact-go/v2/internal/native" logging "github.com/pact-foundation/pact-go/v2/log" "github.com/pact-foundation/pact-go/v2/models" ) @@ -22,7 +23,7 @@ import ( // e.g. MQ, pub/sub, Websocket, Lambda // AsynchronousMessageBuilder is the main implementation of the Pact AsynchronousMessageBuilder interface. type AsynchronousMessageBuilder struct { - messageHandle *native.Message + messageHandle *mockserver.Message messagePactV3 *AsynchronousPact // Type to Marshal content into when sending back to the consumer @@ -77,7 +78,7 @@ type AsynchronousMessageBuilderWithContents struct { // WithBinaryContent accepts a binary payload func (m *UnconfiguredAsynchronousMessageBuilder) WithBinaryContent(contentType string, body []byte) *AsynchronousMessageBuilderWithContents { - m.rootBuilder.messageHandle.WithContents(native.INTERACTION_PART_REQUEST, contentType, body) + m.rootBuilder.messageHandle.WithContents(mockserver.INTERACTION_PART_REQUEST, contentType, body) return &AsynchronousMessageBuilderWithContents{ rootBuilder: m.rootBuilder, @@ -86,7 +87,7 @@ func (m *UnconfiguredAsynchronousMessageBuilder) WithBinaryContent(contentType s // WithContent specifies the payload in bytes that the consumer expects to receive func (m *UnconfiguredAsynchronousMessageBuilder) WithContent(contentType string, body []byte) *AsynchronousMessageBuilderWithContents { - m.rootBuilder.messageHandle.WithContents(native.INTERACTION_PART_REQUEST, contentType, body) + m.rootBuilder.messageHandle.WithContents(mockserver.INTERACTION_PART_REQUEST, contentType, body) return &AsynchronousMessageBuilderWithContents{ rootBuilder: m.rootBuilder, @@ -134,7 +135,7 @@ type AsynchronousPact struct { config Config // Reference to the native rust handle - messageserver *native.MessageServer + messageserver *mockserver.MessageServer } // Deprecated: use NewAsynchronousPact @@ -164,7 +165,7 @@ func (p *AsynchronousPact) validateConfig() error { p.config.PactDir = filepath.Join(dir, "pacts") } - p.messageserver = native.NewMessageServer(p.config.Consumer, p.config.Provider) + p.messageserver = mockserver.NewMessageServer(p.config.Consumer, p.config.Provider) p.messageserver.WithMetadata("pact-go", "version", strings.TrimPrefix(command.Version, "v")) return nil @@ -203,12 +204,12 @@ func (p *AsynchronousPact) verifyMessageConsumerRaw(messageToVerify *Asynchronou // 1. Strip out the matchers // Reify the message back to its "example/generated" form body, err := messageToVerify.messageHandle.GetMessageRequestContents() - log.Println("[DEBUG] reified message raw", string(body)) + log.Println("[DEBUG] reified message raw", body) if err != nil { return fmt.Errorf("unexpected response from message server, this is a bug in the framework") } - log.Println("[DEBUG] reified message raw", string(body)) + log.Println("[DEBUG] reified message raw", body) var m MessageContents // err = json.Unmarshal(body, &m) diff --git a/message/v4/asynchronous_message.go b/message/v4/asynchronous_message.go index 9e436d5c8..76bfa13a2 100644 --- a/message/v4/asynchronous_message.go +++ b/message/v4/asynchronous_message.go @@ -12,6 +12,7 @@ import ( "github.com/pact-foundation/pact-go/v2/command" "github.com/pact-foundation/pact-go/v2/internal/native" + mockserver "github.com/pact-foundation/pact-go/v2/internal/native" logging "github.com/pact-foundation/pact-go/v2/log" "github.com/pact-foundation/pact-go/v2/models" ) @@ -21,7 +22,7 @@ import ( // Builder 3: Async with plugin content + transport type AsynchronousMessageBuilder struct { - messageHandle *native.Message + messageHandle *mockserver.Message pact *AsynchronousPact // Type to Marshal content into when sending back to the consumer @@ -46,15 +47,6 @@ func (m *AsynchronousMessageBuilder) GivenWithParameter(state models.ProviderSta return m } -// 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 (m *AsynchronousMessageBuilder) AddExternalReference(group, name, value string) *AsynchronousMessageBuilder { - m.messageHandle.WithReference(group, name, value) - - return m -} - // 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. @@ -179,7 +171,7 @@ type AsynchronousMessageWithContents struct { // WithContent specifies the payload in bytes that the consumer expects to receive func (m *UnconfiguredAsynchronousMessageBuilder) WithContent(contentType string, body []byte) *AsynchronousMessageWithContents { - m.rootBuilder.messageHandle.WithContents(native.INTERACTION_PART_REQUEST, contentType, body) + m.rootBuilder.messageHandle.WithContents(mockserver.INTERACTION_PART_REQUEST, contentType, body) return &AsynchronousMessageWithContents{ rootBuilder: m.rootBuilder, @@ -227,7 +219,7 @@ type AsynchronousPact struct { config Config // Reference to the native rust handle - messageserver *native.MessageServer + messageserver *mockserver.MessageServer } func NewAsynchronousPact(config Config) (*AsynchronousPact, error) { @@ -254,8 +246,8 @@ func (p *AsynchronousPact) validateConfig() error { p.config.PactDir = filepath.Join(dir, "pacts") } - p.messageserver = native.NewMessageServer(p.config.Consumer, p.config.Provider) - p.messageserver.WithSpecificationVersion(native.SPECIFICATION_VERSION_V4) + p.messageserver = mockserver.NewMessageServer(p.config.Consumer, p.config.Provider) + p.messageserver.WithSpecificationVersion(mockserver.SPECIFICATION_VERSION_V4) p.messageserver.WithMetadata("pact-go", "version", strings.TrimPrefix(command.Version, "v")) return nil @@ -328,7 +320,7 @@ func getAsynchronousMessageWithContents(message *native.Message) (AsynchronousMe }, nil } -func getAsynchronousMessageWithReifiedContents(message *native.Message, reifiedType interface{}) (AsynchronousMessage, error) { +func getAsynchronousMessageWithReifiedContents(message *mockserver.Message, reifiedType interface{}) (AsynchronousMessage, error) { var m AsynchronousMessage var err error diff --git a/message/v4/asynchronous_message_test.go b/message/v4/asynchronous_message_test.go index 57a89a81f..e01114825 100644 --- a/message/v4/asynchronous_message_test.go +++ b/message/v4/asynchronous_message_test.go @@ -40,25 +40,6 @@ func TestAsyncTypeSystem(t *testing.T) { } -func TestAsyncAddExternalReference(t *testing.T) { - p, _ := NewAsynchronousPact(Config{ - Consumer: "asyncconsumer", - Provider: "asyncprovider", - PactDir: "/tmp/", - }) - - err := p.AddAsynchronousMessage(). - AddExternalReference("GitHub", "PR-456", "https://github.com/org/repo/pull/456"). - ExpectsToReceive("a message with an external reference"). - WithJSONContent(map[string]string{"event": "user.created"}). - ConsumedBy(func(mc AsynchronousMessage) error { - return nil - }). - Verify(t) - - assert.NoError(t, err) -} - // Sync - with plugin, but no transport // TODO: ExecuteTest has been disabled for now, because it's not very useful func TestAsyncTypeSystem_CsvPlugin_Matcher(t *testing.T) { diff --git a/message/v4/synchronous_message.go b/message/v4/synchronous_message.go index 6afa1081b..d5c350074 100644 --- a/message/v4/synchronous_message.go +++ b/message/v4/synchronous_message.go @@ -10,6 +10,7 @@ import ( "github.com/pact-foundation/pact-go/v2/command" "github.com/pact-foundation/pact-go/v2/internal/native" + mockserver "github.com/pact-foundation/pact-go/v2/internal/native" logging "github.com/pact-foundation/pact-go/v2/log" "github.com/pact-foundation/pact-go/v2/models" ) @@ -62,15 +63,6 @@ type UnconfiguredSynchronousMessageBuilder struct { pact *SynchronousPact } -// 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 (m *UnconfiguredSynchronousMessageBuilder) AddExternalReference(group, name, value string) *UnconfiguredSynchronousMessageBuilder { - m.messageHandle.WithReference(group, name, value) - - return m -} - // UsingPlugin enables a plugin for use in the current test case func (m *UnconfiguredSynchronousMessageBuilder) UsingPlugin(config PluginConfig) *SynchronousMessageWithPlugin { err := m.pact.mockserver.UsingPlugin(config.Plugin, config.Version) @@ -311,7 +303,7 @@ func (m *SynchronousPact) validateConfig() error { } m.mockserver = native.NewMessageServer(m.config.Consumer, m.config.Provider) - m.mockserver.WithSpecificationVersion(native.SPECIFICATION_VERSION_V4) + m.mockserver.WithSpecificationVersion(mockserver.SPECIFICATION_VERSION_V4) m.mockserver.WithMetadata("pact-go", "version", strings.TrimPrefix(command.Version, "v")) return nil diff --git a/message/v4/synchronous_message_test.go b/message/v4/synchronous_message_test.go index 53765610e..3b1d4ac24 100644 --- a/message/v4/synchronous_message_test.go +++ b/message/v4/synchronous_message_test.go @@ -44,27 +44,6 @@ func TestSyncTypeSystem_NoPlugin(t *testing.T) { assert.NoError(t, err) } -func TestSyncAddExternalReference(t *testing.T) { - p, _ := NewSynchronousPact(Config{ - Consumer: "consumer", - Provider: "provider", - }) - - err := p.AddSynchronousMessage("a sync message with an external reference"). - AddExternalReference("Jira", "TICKET-789", "https://jira.example.com/browse/TICKET-789"). - WithRequest(func(r *SynchronousMessageWithRequestBuilder) { - r.WithJSONContent(map[string]string{"request": "ping"}) - }). - WithResponse(func(r *SynchronousMessageWithResponseBuilder) { - r.WithJSONContent(map[string]string{"response": "pong"}) - }). - ExecuteTest(t, func(m SynchronousMessage) error { - return nil - }) - - assert.NoError(t, err) -} - // Sync - with plugin, but no transport func TestSyncTypeSystem_CsvPlugin_Matcher(t *testing.T) { p, _ := NewSynchronousPact(Config{ diff --git a/message/verifier.go b/message/verifier.go index 679e6664e..eeecc25cf 100644 --- a/message/verifier.go +++ b/message/verifier.go @@ -59,9 +59,7 @@ func CreateMessageHandler(messageHandlers Handlers) proxy.Middleware { // Extract message var message messageVerificationHandlerRequest body, err := io.ReadAll(r.Body) - if closeErr := r.Body.Close(); closeErr != nil { - log.Println("[WARN] failed to close request body:", closeErr) - } + r.Body.Close() log.Printf("[TRACE] message verification handler received request: %+s, %s", body, r.URL.Path) if err != nil { diff --git a/provider/verifier.go b/provider/verifier.go index 5834cbbc9..4d7472077 100644 --- a/provider/verifier.go +++ b/provider/verifier.go @@ -9,7 +9,6 @@ import ( "net" "net/http" "net/url" - "strconv" "strings" "testing" "time" @@ -383,7 +382,7 @@ func WaitForPort(port int, network string, address string, timeoutDuration time. log.Printf("[ERROR] expected server to start < %s. %s", timeoutDuration, message) return fmt.Errorf("expected server to start < %s. %s", timeoutDuration, message) case <-time.After(50 * time.Millisecond): - _, err := net.Dial(network, net.JoinHostPort(address, strconv.Itoa(port))) + _, err := net.Dial(network, fmt.Sprintf("%s:%d", address, port)) if err == nil { return nil } diff --git a/provider/verify_request_test.go b/provider/verify_request_test.go index 33c81ccba..1cb282678 100644 --- a/provider/verify_request_test.go +++ b/provider/verify_request_test.go @@ -115,13 +115,9 @@ func TestVerifyRequest(t *testing.T) { const webhookURL, verificationUrl = "pact_changed_webhook_url", "http://localhost:1234/path/to/pact" enablePactUrlFunc := func() func() { const pactUrl = "PACT_URL" - if err := os.Setenv(pactUrl, webhookURL); err != nil { - panic(err) - } + os.Setenv(pactUrl, webhookURL) return func() { - if err := os.Unsetenv(pactUrl); err != nil { - panic(err) - } + defer os.Unsetenv(pactUrl) } } tests := []struct { diff --git a/utils/port.go b/utils/port.go index 7ec7d3ccf..0a8af8083 100644 --- a/utils/port.go +++ b/utils/port.go @@ -21,9 +21,7 @@ func GetFreePort() (int, error) { if err != nil { return 0, err } - defer func() { - _ = l.Close() - }() + defer l.Close() return l.Addr().(*net.TCPAddr).Port, nil } @@ -84,8 +82,6 @@ func checkPort(p int) error { if err != nil { return err } - defer func() { - _ = l.Close() - }() + defer l.Close() return nil } diff --git a/utils/port_test.go b/utils/port_test.go index a283444de..77e719613 100644 --- a/utils/port_test.go +++ b/utils/port_test.go @@ -123,9 +123,7 @@ func Test_FindPortInRangeWithUsedPorts(t *testing.T) { if err != nil { t.Fatalf("Could not bind to port %s in test", s) } - defer func() { - _ = l.Close() - }() + defer l.Close() p, err := FindPortInRange(c.s) if err != nil && err.Error() != c.errorMsg { t.Fatalf("unexpected error %s", err.Error())