From 12025553b10cfec0b580b375e97416a7ff0a3c86 Mon Sep 17 00:00:00 2001 From: Matt Fellows Date: Mon, 27 Jun 2022 23:11:25 +1000 Subject: [PATCH] feat: further refactoring with better type state setup --- Makefile | 1 + docs/messages.md | 18 +- examples/consumer_v3_test.go | 2 +- internal/native/message_server.go | 21 +- internal/native/message_server_test.go | 2 +- log/log.go | 4 +- message/message.go | 22 -- message/v3/asynchronous_message.go | 51 ++-- message/v3/message.go | 29 +-- message/v4/asynchronous_message.go | 317 ++++++++++++++++++++++++ message/v4/asynchronous_message_test.go | 68 +++++ message/v4/message.go | 36 +-- message/v4/synchronous_message.go | 310 ++++++++++------------- message/v4/synchronous_message_test.go | 105 +++++++- message/v4/transport.go | 3 +- 15 files changed, 691 insertions(+), 298 deletions(-) create mode 100644 message/v4/asynchronous_message.go create mode 100644 message/v4/asynchronous_message_test.go diff --git a/Makefile b/Makefile index 7c4dbfd13..229de2518 100755 --- a/Makefile +++ b/Makefile @@ -48,6 +48,7 @@ download_plugins: @echo "--- 🐿 Installing plugins"; \ ./scripts/install-cli.sh ~/.pact/bin/pact-plugin-cli -y install https://github.com/pactflow/pact-protobuf-plugin/releases/tag/v-0.1.7 + ~/.pact/bin/pact-plugin-cli -y install https://github.com/pact-foundation/pact-plugins/releases/tag/csv-plugin-0.0.1 goveralls: goveralls -service="travis-ci" -coverprofile=coverage.txt -repotoken $(COVERALLS_TOKEN) diff --git a/docs/messages.md b/docs/messages.md index abde9feb4..c3e8130c5 100644 --- a/docs/messages.md +++ b/docs/messages.md @@ -30,7 +30,7 @@ The process looks like this on the provider (producer) side: In this document, we will cover steps 1-3. -## Consumer +### Consumer A Consumer is the system that will be reading a message from a queue or some intermediary - like a Kinesis stream, websocket or S3 bucket - and be able to handle it. @@ -102,7 +102,7 @@ func TestMessagePact(t *testing.T) { - All handlers to be tested must be of the shape `func(AsynchronousMessage) error` - that is, they must accept a `AsynchronousMessage` and return an `error`. This is how we get around all of the various protocols, and will often require a lightweight adapter function to convert it. - In this case, we wrap the actual `userHandler` with `userHandlerWrapper` provided by Pact. -## Provider (Producer) +### Provider (Producer) A Provider (Producer in messaging parlance) is the system that will be putting a message onto the queue. @@ -159,3 +159,17 @@ func TestV3MessageProvider(t *testing.T) { 1. We configure the function mappings. In this case, we have a function that generates `a user event` which is responsible for generating the `User` event that will be sent to the consumer via some message queue 1. We setup any provider states for the interaction (see [provider](./provider.md) for more on this). 1. We configure Pact to stand-in for the queue and run the verification process. Pact will read all of the interactions specified by its consumer, invokisc each function that is responsible for generating that message and inspecting their responses + + +## Contract Testing (Synchronous) + +In additional to "fire and forget", Pact supports bi-directional messaging protocols such as gRPC and websockets. + +[Diagram TBC] + +| Mode | Custom Transport | Method | +| ----- | ---------------- | ------ | +| Sync | Yes | b | +| Sync | No | c | +| Async | Yes | d | +| Async | No | e | \ No newline at end of file diff --git a/examples/consumer_v3_test.go b/examples/consumer_v3_test.go index 069e26e5c..1110e9b22 100644 --- a/examples/consumer_v3_test.go +++ b/examples/consumer_v3_test.go @@ -119,7 +119,7 @@ func TestMessagePact(t *testing.T) { } // Message Pact - wrapped handler extracts the message -var userHandlerWrapper = func(m message.AsynchronousMessage) error { +var userHandlerWrapper = func(m message.MessageContents) error { return userHandler(*m.Content.(*User)) } diff --git a/internal/native/message_server.go b/internal/native/message_server.go index 292cab946..20ba564e1 100644 --- a/internal/native/message_server.go +++ b/internal/native/message_server.go @@ -139,15 +139,22 @@ func (i *Message) GivenWithParameter(state string, params map[string]interface{} cState := C.CString(state) defer free(cState) - for k, v := range params { - cKey := C.CString(k) - defer free(cKey) - param := stringFromInterface(v) - cValue := C.CString(param) - defer free(cValue) + if len(params) == 0 { + cState := C.CString(state) + defer free(cState) + + C.pactffi_message_given(i.handle, cState) + } else { + for k, v := range params { + cKey := C.CString(k) + defer free(cKey) + param := stringFromInterface(v) + cValue := C.CString(param) + defer free(cValue) - C.pactffi_message_given_with_param(i.handle, cState, cKey, cValue) + C.pactffi_message_given_with_param(i.handle, cState, cKey, cValue) + } } return i diff --git a/internal/native/message_server_test.go b/internal/native/message_server_test.go index fbb2cbd4d..943203a11 100644 --- a/internal/native/message_server_test.go +++ b/internal/native/message_server_test.go @@ -166,7 +166,7 @@ func TestGrpcPluginInteraction(t *testing.T) { // m := NewPact("test-grpc-consumer", "test-plugin-provider") // Protobuf plugin test - m.UsingPlugin("protobuf", "0.1.5") + m.UsingPlugin("protobuf", "0.1.7") // m.WithSpecificationVersion(SPECIFICATION_VERSION_V4) i := m.NewSyncMessageInteraction("grpc interaction") diff --git a/log/log.go b/log/log.go index 215e1a8f2..4c7febc7d 100644 --- a/log/log.go +++ b/log/log.go @@ -27,13 +27,13 @@ func InitLogging() { Writer: os.Stderr, } log.SetOutput(logFilter) + log.Println("[DEBUG] initialised logging") } - log.Println("[DEBUG] initialised logging") - } // SetLogLevel sets the default log level for the Pact framework func SetLogLevel(level logutils.LogLevel) error { + InitLogging() switch level { case logLevelTrace, logLevelDebug, logLevelError, logLevelInfo, logLevelWarn: logFilter.SetMinLevel(level) diff --git a/message/message.go b/message/message.go index 5ea3dbc7e..3d4e94915 100644 --- a/message/message.go +++ b/message/message.go @@ -1,7 +1,6 @@ package message import ( - "github.com/pact-foundation/pact-go/v2/matchers" "github.com/pact-foundation/pact-go/v2/models" ) @@ -15,24 +14,3 @@ type Producer Handler // Handlers is a list of handlers ordered by description type Handlers map[string]Handler - -// AsynchronousConsumer receives a message and must be able to parse -// the content -type AsynchronousConsumer func(AsynchronousMessage) error - -// type SynchronousConsumer func(SynchronousMessage) error - -// V3 Message (Asynchronous only) -type AsynchronousMessage struct { - // Message Body - Content interface{} `json:"contents"` - - // Provider state to be written into the Pact file - States []models.V3ProviderState `json:"providerStates"` - - // Message metadata - Metadata matchers.MetadataMatcher `json:"metadata"` - - // Description to be written into the Pact file - Description string `json:"description"` -} diff --git a/message/v3/asynchronous_message.go b/message/v3/asynchronous_message.go index 865ad7d9e..998ec8c5f 100644 --- a/message/v3/asynchronous_message.go +++ b/message/v3/asynchronous_message.go @@ -14,12 +14,14 @@ import ( "github.com/pact-foundation/pact-go/v2/models" ) -// Message is a representation of a single, unidirectional message +// TODO: make a builder? + +// AsynchronousMessage is a representation of a single, unidirectional message // e.g. MQ, pub/sub, Websocket, Lambda -// Message is the main implementation of the Pact Message interface. -type Message struct { +// AsynchronousMessage is the main implementation of the Pact AsynchronousMessage interface. +type AsynchronousMessage struct { messageHandle *mockserver.Message - messagePactV3 *Pact + messagePactV3 *AsynchronousPact // Type to Marshal content into when sending back to the consumer // Defaults to interface{} @@ -30,7 +32,7 @@ type Message struct { } // Given specifies a provider state. Optional. -func (m *Message) Given(state models.V3ProviderState) *Message { +func (m *AsynchronousMessage) Given(state models.V3ProviderState) *AsynchronousMessage { m.messageHandle.GivenWithParameter(state.Name, state.Parameters) return m @@ -39,7 +41,7 @@ func (m *Message) Given(state models.V3ProviderState) *Message { // ExpectsToReceive specifies the content it is expecting to be // given from the Provider. The function must be able to handle this // message for the interaction to succeed. -func (m *Message) ExpectsToReceive(description string) *Message { +func (m *AsynchronousMessage) ExpectsToReceive(description string) *AsynchronousMessage { m.messageHandle.ExpectsToReceive(description) return m @@ -48,21 +50,21 @@ func (m *Message) ExpectsToReceive(description string) *Message { // WithMetadata specifies message-implementation specific metadata // to go with the content // func (m *Message) WithMetadata(metadata MapMatcher) *Message { -func (m *Message) WithMetadata(metadata map[string]string) *Message { +func (m *AsynchronousMessage) WithMetadata(metadata map[string]string) *AsynchronousMessage { m.messageHandle.WithMetadata(metadata) return m } // WithBinaryContent accepts a binary payload -func (m *Message) WithBinaryContent(contentType string, body []byte) *Message { +func (m *AsynchronousMessage) WithBinaryContent(contentType string, body []byte) *AsynchronousMessage { m.messageHandle.WithContents(contentType, body) return m } // WithContent specifies the payload in bytes that the consumer expects to receive -func (m *Message) WithContent(contentType string, body []byte) *Message { +func (m *AsynchronousMessage) WithContent(contentType string, body []byte) *AsynchronousMessage { m.messageHandle.WithContents(contentType, body) return m @@ -70,7 +72,7 @@ func (m *Message) WithContent(contentType string, body []byte) *Message { // WithJSONContent specifies the payload as an object (to be marshalled to WithJSONContent) that // is expected to be consumed -func (m *Message) WithJSONContent(content interface{}) *Message { +func (m *AsynchronousMessage) WithJSONContent(content interface{}) *AsynchronousMessage { m.messageHandle.WithJSONContents(content) return m @@ -78,7 +80,7 @@ func (m *Message) WithJSONContent(content interface{}) *Message { // // AsType specifies that the content sent through to the // consumer handler should be sent as the given type -func (m *Message) AsType(t interface{}) *Message { +func (m *AsynchronousMessage) AsType(t interface{}) *AsynchronousMessage { log.Println("[DEBUG] setting Message decoding to type:", reflect.TypeOf(t)) m.Type = t @@ -86,26 +88,29 @@ func (m *Message) AsType(t interface{}) *Message { } // The function that will consume the message -func (m *Message) ConsumedBy(handler AsynchronousConsumer) *Message { +func (m *AsynchronousMessage) ConsumedBy(handler AsynchronousConsumer) *AsynchronousMessage { m.handler = handler return m } // The function that will consume the message -func (m *Message) Verify(t *testing.T) error { +func (m *AsynchronousMessage) Verify(t *testing.T) error { return m.messagePactV3.Verify(t, m, m.handler) } -type Pact struct { +type AsynchronousPact struct { config Config // Reference to the native rust handle messageserver *mockserver.MessageServer } -func NewMessagePact(config Config) (*Pact, error) { - provider := &Pact{ +// Deprecated: use NewAsynchronousPact +var NewMessagePact = NewAsynchronousPact + +func NewAsynchronousPact(config Config) (*AsynchronousPact, error) { + provider := &AsynchronousPact{ config: config, } err := provider.validateConfig() @@ -120,7 +125,7 @@ func NewMessagePact(config Config) (*Pact, error) { } // validateConfig validates the configuration for the consumer test -func (p *Pact) validateConfig() error { +func (p *AsynchronousPact) validateConfig() error { log.Println("[DEBUG] pact message validate config") dir, _ := os.Getwd() @@ -135,17 +140,17 @@ func (p *Pact) validateConfig() error { // AddMessage creates a new asynchronous consumer expectation // Deprecated: use AddAsynchronousMessage() instead -func (p *Pact) AddMessage() *Message { +func (p *AsynchronousPact) AddMessage() *AsynchronousMessage { return p.AddAsynchronousMessage() } // AddMessage creates a new asynchronous consumer expectation -func (p *Pact) AddAsynchronousMessage() *Message { +func (p *AsynchronousPact) AddAsynchronousMessage() *AsynchronousMessage { log.Println("[DEBUG] add message") message := p.messageserver.NewMessage() - m := &Message{ + m := &AsynchronousMessage{ messageHandle: message, messagePactV3: p, } @@ -160,7 +165,7 @@ func (p *Pact) AddAsynchronousMessage() *Message { // A Message Consumer is analagous to a Provider in the HTTP Interaction model. // It is the receiver of an interaction, and needs to be able to handle whatever // request was provided. -func (p *Pact) verifyMessageConsumerRaw(messageToVerify *Message, handler AsynchronousConsumer) error { +func (p *AsynchronousPact) verifyMessageConsumerRaw(messageToVerify *AsynchronousMessage, handler AsynchronousConsumer) error { log.Printf("[DEBUG] verify message") // 1. Strip out the matchers @@ -169,7 +174,7 @@ func (p *Pact) verifyMessageConsumerRaw(messageToVerify *Message, handler Asynch log.Println("[DEBUG] reified message raw", body) - var m AsynchronousMessage + var m MessageContents err := json.Unmarshal([]byte(body), &m) if err != nil { return fmt.Errorf("unexpected response from message server, this is a bug in the framework") @@ -206,7 +211,7 @@ func (p *Pact) verifyMessageConsumerRaw(messageToVerify *Message, handler Asynch // VerifyMessageConsumer is a test convience function for VerifyMessageConsumerRaw, // accepting an instance of `*testing.T` -func (p *Pact) Verify(t *testing.T, message *Message, handler AsynchronousConsumer) error { +func (p *AsynchronousPact) Verify(t *testing.T, message *AsynchronousMessage, handler AsynchronousConsumer) error { err := p.verifyMessageConsumerRaw(message, handler) if err != nil { diff --git a/message/v3/message.go b/message/v3/message.go index d0221b0a1..d0d0e5a13 100644 --- a/message/v3/message.go +++ b/message/v3/message.go @@ -1,40 +1,19 @@ package v3 -import ( - "github.com/pact-foundation/pact-go/v2/matchers" - "github.com/pact-foundation/pact-go/v2/models" -) - type Body interface{} type Metadata map[string]interface{} -// Handler is a provider function that generates a -// message for a Consumer given a Message context (state, description etc.) -type Handler func([]models.V3ProviderState) (Body, Metadata, error) -type Producer Handler - -// Handlers is a list of handlers ordered by description -type Handlers map[string]Handler - // AsynchronousConsumer receives a message and must be able to parse // the content -type AsynchronousConsumer func(AsynchronousMessage) error - -// type SynchronousConsumer func(SynchronousMessage) error +type AsynchronousConsumer func(MessageContents) error // V3 Message (Asynchronous only) -type AsynchronousMessage struct { +type MessageContents struct { // Message Body - Content interface{} `json:"contents"` - - // Provider state to be written into the Pact file - States []models.V3ProviderState `json:"providerStates"` + Content Body `json:"contents"` // Message metadata - Metadata matchers.MetadataMatcher `json:"metadata"` - - // Description to be written into the Pact file - Description string `json:"description"` + Metadata Metadata `json:"metadata"` } type Config struct { diff --git a/message/v4/asynchronous_message.go b/message/v4/asynchronous_message.go new file mode 100644 index 000000000..3e9f3926e --- /dev/null +++ b/message/v4/asynchronous_message.go @@ -0,0 +1,317 @@ +package v4 + +import ( + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/pact-foundation/pact-go/v2/internal/native" + mockserver "github.com/pact-foundation/pact-go/v2/internal/native" + "github.com/pact-foundation/pact-go/v2/models" +) + +// AsynchronousMessage is a representation of a single, unidirectional message +// e.g. MQ, pub/sub, Websocket, Lambda +// AsynchronousMessage is the main implementation of the Pact AsynchronousMessage interface. +type AsynchronousMessage struct { +} + +// Builder 1: Async with no plugin/transport +// Builder 2: Async with plugin content no transport +// Builder 3: Async with plugin content + transport + +type AsynchronousMessageBuilder struct { + messageHandle *mockserver.Message + pact *AsynchronousPact + + // Type to Marshal content into when sending back to the consumer + // Defaults to interface{} + Type interface{} + + // The handler for this message + handler AsynchronousConsumer +} + +// Given specifies a provider state. Optional. +func (m *AsynchronousMessageBuilder) Given(state string) *AsynchronousMessageBuilder { + m.messageHandle.Given(state) + + return m +} + +// Given specifies a provider state. Optional. +func (m *AsynchronousMessageBuilder) GivenWithParameter(state models.V3ProviderState) *AsynchronousMessageBuilder { + m.messageHandle.GivenWithParameter(state.Name, state.Parameters) + + 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. +func (m *AsynchronousMessageBuilder) ExpectsToReceive(description string) *UnconfiguredAsynchronousMessageBuilder { + m.messageHandle.ExpectsToReceive(description) + + return &UnconfiguredAsynchronousMessageBuilder{ + rootBuilder: m, + } +} + +type UnconfiguredAsynchronousMessageBuilder struct { + rootBuilder *AsynchronousMessageBuilder +} + +// AddMessage creates a new asynchronous consumer expectation +func (m *UnconfiguredAsynchronousMessageBuilder) UsingPlugin(config PluginConfig) *AsynchronousMessageWithPlugin { + m.rootBuilder.pact.messageserver.UsingPlugin(config.Plugin, config.Version) + + return &AsynchronousMessageWithPlugin{ + rootBuilder: m.rootBuilder, + } +} + +type AsynchronousMessageWithPlugin struct { + rootBuilder *AsynchronousMessageBuilder +} + +func (s *AsynchronousMessageWithPlugin) WithContents(contents string, contentType string) *AsynchronousMessageWithPluginContents { + s.rootBuilder.messageHandle.WithPluginInteractionContents(native.INTERACTION_PART_REQUEST, contentType, contents) + + return &AsynchronousMessageWithPluginContents{ + rootBuilder: s.rootBuilder, + } +} + +type AsynchronousMessageWithPluginContents struct { + rootBuilder *AsynchronousMessageBuilder +} + +func (s *AsynchronousMessageWithPluginContents) StartTransport(transport string, address string, config map[string][]interface{}) *AsynchronousMessageWithTransport { + port, err := s.rootBuilder.pact.messageserver.StartTransport(transport, address, 0, make(map[string][]interface{})) + + if err != nil { + log.Fatalln("unable to start plugin transport:", err) + } + + return &AsynchronousMessageWithTransport{ + rootBuilder: s.rootBuilder, + transport: TransportConfig{ + Port: port, + Address: address, + }, + } +} + +type AsynchronousMessageWithTransport struct { + rootBuilder *AsynchronousMessageBuilder + transport TransportConfig +} + +func (s *AsynchronousMessageWithTransport) ExecuteTest(t *testing.T, integrationTest func(tc TransportConfig, m SynchronousMessage) error) error { + message := SynchronousMessage{} + + defer s.rootBuilder.pact.messageserver.CleanupMockServer(s.transport.Port) + + err := integrationTest(s.transport, message) + + if err != nil { + return err + } + + mismatches := s.rootBuilder.pact.messageserver.MockServerMismatchedRequests(s.transport.Port) + + if len(mismatches) > 0 { + return fmt.Errorf("pact validation failed: %+v", mismatches) + } + + return s.rootBuilder.pact.messageserver.WritePactFileForServer(s.transport.Port, s.rootBuilder.pact.config.PactDir, false) +} + +// WithMetadata specifies message-implementation specific metadata +// to go with the content +// func (m *Message) WithMetadata(metadata MapMatcher) *Message { +func (m *UnconfiguredAsynchronousMessageBuilder) WithMetadata(metadata map[string]string) *UnconfiguredAsynchronousMessageBuilder { + m.rootBuilder.messageHandle.WithMetadata(metadata) + + return m +} + +type AsynchronousMessageWithContents struct { + rootBuilder *AsynchronousMessageBuilder +} + +// WithBinaryContent accepts a binary payload +func (m *UnconfiguredAsynchronousMessageBuilder) WithBinaryContent(contentType string, body []byte) *AsynchronousMessageWithContents { + m.rootBuilder.messageHandle.WithContents(contentType, body) + + return &AsynchronousMessageWithContents{ + rootBuilder: m.rootBuilder, + } +} + +// 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(contentType, body) + + return &AsynchronousMessageWithContents{ + rootBuilder: m.rootBuilder, + } +} + +// WithJSONContent specifies the payload as an object (to be marshalled to WithJSONContent) that +// is expected to be consumed +func (m *UnconfiguredAsynchronousMessageBuilder) WithJSONContent(content interface{}) *AsynchronousMessageWithContents { + m.rootBuilder.messageHandle.WithJSONContents(content) + + return &AsynchronousMessageWithContents{ + rootBuilder: m.rootBuilder, + } +} + +// AsType specifies that the content sent through to the +// consumer handler should be sent as the given type +func (m *AsynchronousMessageWithContents) AsType(t interface{}) *AsynchronousMessageWithContents { + log.Println("[DEBUG] setting Message decoding to type:", reflect.TypeOf(t)) + m.rootBuilder.Type = t + + return m +} + +// The function that will consume the message +func (m *AsynchronousMessageWithContents) ConsumedBy(handler AsynchronousConsumer) *AsynchronousMessageWithConsumer { + m.rootBuilder.handler = handler + + return &AsynchronousMessageWithConsumer{ + rootBuilder: m.rootBuilder, + } +} + +type AsynchronousMessageWithConsumer struct { + rootBuilder *AsynchronousMessageBuilder +} + +// The function that will consume the message +func (m *AsynchronousMessageWithConsumer) Verify(t *testing.T) error { + return m.rootBuilder.pact.Verify(t, m.rootBuilder, m.rootBuilder.handler) +} + +type AsynchronousPact struct { + config Config + + // Reference to the native rust handle + messageserver *mockserver.MessageServer +} + +func NewAsynchronousPact(config Config) (*AsynchronousPact, error) { + provider := &AsynchronousPact{ + config: config, + } + err := provider.validateConfig() + + if err != nil { + return nil, err + } + + native.Init() + + return provider, err +} + +// validateConfig validates the configuration for the consumer test +func (p *AsynchronousPact) validateConfig() error { + log.Println("[DEBUG] pact message validate config") + dir, _ := os.Getwd() + + if p.config.PactDir == "" { + p.config.PactDir = filepath.Join(dir, "pacts") + } + + p.messageserver = mockserver.NewMessageServer(p.config.Consumer, p.config.Provider) + + return nil +} + +// AddMessage creates a new asynchronous consumer expectation +// Deprecated: use AddAsynchronousMessage() instead +func (p *AsynchronousPact) AddMessage() *AsynchronousMessageBuilder { + return p.AddAsynchronousMessage() +} + +// AddMessage creates a new asynchronous consumer expectation +func (p *AsynchronousPact) AddAsynchronousMessage() *AsynchronousMessageBuilder { + log.Println("[DEBUG] add message") + + message := p.messageserver.NewMessage() + + return &AsynchronousMessageBuilder{ + messageHandle: message, + pact: p, + } +} + +// VerifyMessageConsumerRaw creates a new Pact _message_ interaction to build a testable +// interaction. +// +// +// A Message Consumer is analagous to a Provider in the HTTP Interaction model. +// It is the receiver of an interaction, and needs to be able to handle whatever +// request was provided. +func (p *AsynchronousPact) verifyMessageConsumerRaw(messageToVerify *AsynchronousMessageBuilder, handler AsynchronousConsumer) error { + log.Printf("[DEBUG] verify message") + + // 1. Strip out the matchers + // Reify the message back to its "example/generated" form + body := messageToVerify.messageHandle.ReifyMessage() + + log.Println("[DEBUG] reified message raw", body) + + var m MessageContents + err := json.Unmarshal([]byte(body), &m) + if err != nil { + return fmt.Errorf("unexpected response from message server, this is a bug in the framework") + } + log.Println("[DEBUG] unmarshalled into an AsynchronousMessage", m) + + // 2. Convert to an actual type (to avoid wrapping if needed/requested) + // 3. Invoke the message handler + // 4. write the pact file + t := reflect.TypeOf(messageToVerify.Type) + if t != nil && t.Name() != "interface" { + s, err := json.Marshal(m.Content) + if err != nil { + return fmt.Errorf("unable to generate message for type: %+v", messageToVerify.Type) + } + err = json.Unmarshal(s, &messageToVerify.Type) + + if err != nil { + return fmt.Errorf("unable to narrow type to %v: %v", t.Name(), err) + } + + m.Content = messageToVerify.Type + } + + // Yield message, and send through handler function + err = handler(m) + + if err != nil { + return err + } + + return p.messageserver.WritePactFile(p.config.PactDir, false) +} + +// VerifyMessageConsumer is a test convience function for VerifyMessageConsumerRaw, +// accepting an instance of `*testing.T` +func (p *AsynchronousPact) Verify(t *testing.T, message *AsynchronousMessageBuilder, handler AsynchronousConsumer) error { + err := p.verifyMessageConsumerRaw(message, handler) + + if err != nil { + t.Errorf("VerifyMessageConsumer failed: %v", err) + } + + return err +} diff --git a/message/v4/asynchronous_message_test.go b/message/v4/asynchronous_message_test.go new file mode 100644 index 000000000..06779bcf4 --- /dev/null +++ b/message/v4/asynchronous_message_test.go @@ -0,0 +1,68 @@ +package v4 + +import ( + "testing" + + "github.com/pact-foundation/pact-go/v2/log" + "github.com/stretchr/testify/assert" +) + +func TestAsyncTypeSystem(t *testing.T) { + p, _ := NewAsynchronousPact(Config{ + Consumer: "asyncconsumer", + Provider: "asyncprovider", + PactDir: "/tmp/", + }) + log.SetLogLevel("TRACE") + + type foo struct { + Foo string `json:"foo"` + } + + // Sync - no plugin + err := p.AddAsynchronousMessage(). + Given("some state"). + Given("another state"). + ExpectsToReceive("an important json message"). + WithJSONContent(map[string]string{ + "foo": "bar", + }). + AsType(&foo{}). + ConsumedBy(func(mc MessageContents) error { + fooMessage := *mc.Content.(*foo) + assert.Equal(t, "bar", fooMessage.Foo) + 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 + // csvInteraction := `{ + // "request.path", "/reports/report002.csv", + // "response.status", "200", + // "response.contents": { + // "pact:content-type": "text/csv", // Set the content type to CSV + // "csvHeaders": true, // We have a header row + // "column:Name": "matching(type,'Name')", // Column with header Name must match by type (which is actually useless with CSV) + // "column:Number", "matching(number,100)", // Column with header Number must match a number format + // "column:Date", "matching(datetime, 'yyyy-MM-dd','2000-01-01')" // Column with header Date must match an ISO format yyyy-MM-dd + // } + // }` + + // TODO: enable when there is a transport for async to test! + // p.AddAsynchronousMessage(). + // Given("some state"). + // ExpectsToReceive("some csv content"). + // UsingPlugin(PluginConfig{ + // Plugin: "csv", + // Version: "0.0.1", + // }). + // WithContents(csvInteraction, "text/csv"). + // StartTransport("notarealtransport", "127.0.0.1", nil). + // ExecuteTest(t, func(tc TransportConfig, m SynchronousMessage) error { + // fmt.Println("Executing the CSV test") + // return nil + // }) +} diff --git a/message/v4/message.go b/message/v4/message.go index 5eba6ca20..988d025b1 100644 --- a/message/v4/message.go +++ b/message/v4/message.go @@ -1,40 +1,20 @@ package v4 -import ( - "github.com/pact-foundation/pact-go/v2/models" -) - type Body interface{} type Metadata map[string]interface{} -// Handler is a provider function that generates a -// message for a Consumer given a Message context (state, description etc.) -type Handler func([]models.V3ProviderState) (Body, Metadata, error) -type Producer Handler - -// Handlers is a list of handlers ordered by description -type Handlers map[string]Handler - // AsynchronousConsumer receives a message and must be able to parse // the content -// type AsynchronousConsumer func(AsynchronousMessage) error - -// // type SynchronousConsumer func(SynchronousMessage) error +type AsynchronousConsumer func(MessageContents) error -// // V3 Message (Asynchronous only) -// type AsynchronousMessage struct { -// // Message Body -// Content interface{} `json:"contents"` +// V3 Message (Asynchronous only) +type MessageContents struct { + // Message Body + Content Body `json:"contents"` -// // Provider state to be written into the Pact file -// States []models.V3ProviderState `json:"providerStates"` - -// // Message metadata -// Metadata matchers.MetadataMatcher `json:"metadata"` - -// // Description to be written into the Pact file -// Description string `json:"description"` -// } + // Message metadata + Metadata Metadata `json:"metadata"` +} type Config struct { Consumer string diff --git a/message/v4/synchronous_message.go b/message/v4/synchronous_message.go index f4e7414c8..b649f3cc1 100644 --- a/message/v4/synchronous_message.go +++ b/message/v4/synchronous_message.go @@ -1,13 +1,13 @@ package v4 import ( + "fmt" "log" "os" "path/filepath" "testing" "github.com/pact-foundation/pact-go/v2/internal/native" - "github.com/pact-foundation/pact-go/v2/matchers" "github.com/pact-foundation/pact-go/v2/models" ) @@ -15,72 +15,103 @@ type SynchronousPact struct { config Config // Reference to the native rust handle - // mockserver *native.MockServer mockserver *native.MessageServer } // SynchronousMessage contains a req/res message -type SynchronousMessageDetail struct { - // TODO: what goes here? - // Message Body - Content interface{} `json:"contents"` +// It is currently an empty struct to allow future expansion +type SynchronousMessage struct { + // TODO: should we pass this in? Probably need to be able to reify the message + // in these cases + // Request MessageContents + // Response []MessageContents +} - // Provider state to be written into the Pact file - States []models.V3ProviderState `json:"providerStates"` +// SynchronousMessageBuilder is a representation of a single, bidirectional message +type SynchronousMessageBuilder struct { + messageHandle *native.Message + pact *SynchronousPact +} - // Message metadata - Metadata matchers.MetadataMatcher `json:"metadata"` +// Given specifies a provider state +func (m *UnconfiguredSynchronousMessageBuilder) Given(state string) *UnconfiguredSynchronousMessageBuilder { + m.messageHandle.Given(state) - // Description to be written into the Pact file - Description string `json:"description"` + return &UnconfiguredSynchronousMessageBuilder{ + pact: m.pact, + messageHandle: m.messageHandle, + } } -type SynchronousConsumer func(SynchronousMessageDetail) error +// Given specifies a provider state +func (m *UnconfiguredSynchronousMessageBuilder) GivenWithParameter(state models.V3ProviderState) *UnconfiguredSynchronousMessageBuilder { + m.messageHandle.GivenWithParameter(state.Name, state.Parameters) + + return &UnconfiguredSynchronousMessageBuilder{ + pact: m.pact, + messageHandle: m.messageHandle, + } +} -type UnconfiguredSynchronousMessage struct { +type UnconfiguredSynchronousMessageBuilder struct { + messageHandle *native.Message + pact *SynchronousPact } // AddMessage creates a new asynchronous consumer expectation -func (m *UnconfiguredSynchronousMessage) UsingPlugin(config PluginConfig) *SynchronousMessageWithPlugin { - // m.Pact.mockserver.UsingPlugin(config.Plugin, config.Version) +func (m *UnconfiguredSynchronousMessageBuilder) UsingPlugin(config PluginConfig) *SynchronousMessageWithPlugin { + m.pact.mockserver.UsingPlugin(config.Plugin, config.Version) - return &SynchronousMessageWithPlugin{} + return &SynchronousMessageWithPlugin{ + pact: m.pact, + messageHandle: m.messageHandle, + } +} + +// AddMessage creates a new asynchronous consumer expectation +func (m *UnconfiguredSynchronousMessageBuilder) WithRequest(r RequestBuilder) *SynchronousMessageWithRequest { + r(&SynchronousMessageWithRequestBuilder{ + messageHandle: m.messageHandle, + pact: m.pact, + }) + + return &SynchronousMessageWithRequest{ + pact: m.pact, + messageHandle: m.messageHandle, + } } type SynchronousMessageWithRequest struct { + messageHandle *native.Message + pact *SynchronousPact } type RequestBuilder func(*SynchronousMessageWithRequestBuilder) -// AddMessage creates a new asynchronous consumer expectation -func (m *UnconfiguredSynchronousMessage) WithRequest(r RequestBuilder) *SynchronousMessageWithRequest { - // m.Pact.mockserver.UsingPlugin(config.Plugin, config.Version) - - return &SynchronousMessageWithRequest{} -} - type SynchronousMessageWithRequestBuilder struct { + messageHandle *native.Message + pact *SynchronousPact } // WithMetadata specifies message-implementation specific metadata // to go with the content // func (m *Message) WithMetadata(metadata MapMatcher) *Message { func (m *SynchronousMessageWithRequestBuilder) WithMetadata(metadata map[string]string) *SynchronousMessageWithRequestBuilder { - // m.messageHandle.WithMetadata(metadata) + m.messageHandle.WithMetadata(metadata) return m } // WithBinaryContent accepts a binary payload func (m *SynchronousMessageWithRequestBuilder) WithBinaryContent(contentType string, body []byte) *SynchronousMessageWithRequestBuilder { - // m.messageHandle.WithContents(contentType, body) + m.messageHandle.WithContents(contentType, body) return m } // WithContent specifies the payload in bytes that the consumer expects to receive func (m *SynchronousMessageWithRequestBuilder) WithContent(contentType string, body []byte) *SynchronousMessageWithRequestBuilder { - // m.messageHandle.WithContents(contentType, body) + m.messageHandle.WithContents(contentType, body) return m } @@ -88,54 +119,55 @@ func (m *SynchronousMessageWithRequestBuilder) WithContent(contentType string, b // WithJSONContent specifies the payload as an object (to be marshalled to WithJSONContent) that // is expected to be consumed func (m *SynchronousMessageWithRequestBuilder) WithJSONContent(content interface{}) *SynchronousMessageWithRequestBuilder { - // m.messageHandle.WithJSONContents(content) - - return m -} - -// AsType specifies that the content sent through to the -// consumer handler should be sent as the given type -func (m *SynchronousMessageWithRequestBuilder) AsType(t interface{}) *SynchronousMessageWithRequestBuilder { - // log.Println("[DEBUG] setting Message decoding to type:", reflect.TypeOf(t)) - // m.Type = t + m.messageHandle.WithJSONContents(content) return m } // AddMessage creates a new asynchronous consumer expectation func (m *SynchronousMessageWithRequest) WithResponse(builder ResponseBuilder) *SynchronousMessageWithResponse { - // m.Pact.mockserver.UsingPlugin(config.Plugin, config.Version) - - return &SynchronousMessageWithResponse{} + builder(&SynchronousMessageWithResponseBuilder{ + messageHandle: m.messageHandle, + pact: m.pact, + }) + + return &SynchronousMessageWithResponse{ + pact: m.pact, + messageHandle: m.messageHandle, + } } type SynchronousMessageWithResponse struct { + messageHandle *native.Message + pact *SynchronousPact } type ResponseBuilder func(*SynchronousMessageWithResponseBuilder) type SynchronousMessageWithResponseBuilder struct { + messageHandle *native.Message + pact *SynchronousPact } // WithMetadata specifies message-implementation specific metadata // to go with the content // func (m *Message) WithMetadata(metadata MapMatcher) *Message { func (m *SynchronousMessageWithResponseBuilder) WithMetadata(metadata map[string]string) *SynchronousMessageWithResponseBuilder { - // m.messageHandle.WithMetadata(metadata) + m.messageHandle.WithMetadata(metadata) return m } // WithBinaryContent accepts a binary payload func (m *SynchronousMessageWithResponseBuilder) WithBinaryContent(contentType string, body []byte) *SynchronousMessageWithResponseBuilder { - // m.messageHandle.WithContents(contentType, body) + m.messageHandle.WithContents(contentType, body) return m } // WithContent specifies the payload in bytes that the consumer expects to receive func (m *SynchronousMessageWithResponseBuilder) WithContent(contentType string, body []byte) *SynchronousMessageWithResponseBuilder { - // m.messageHandle.WithContents(contentType, body) + m.messageHandle.WithContents(contentType, body) return m } @@ -143,92 +175,89 @@ func (m *SynchronousMessageWithResponseBuilder) WithContent(contentType string, // WithJSONContent specifies the payload as an object (to be marshalled to WithJSONContent) that // is expected to be consumed func (m *SynchronousMessageWithResponseBuilder) WithJSONContent(content interface{}) *SynchronousMessageWithResponseBuilder { - // m.messageHandle.WithJSONContents(content) - - return m -} - -// AsType specifies that the content sent through to the -// consumer handler should be sent as the given type -func (m *SynchronousMessageWithResponseBuilder) AsType(t interface{}) *SynchronousMessageWithResponseBuilder { - // log.Println("[DEBUG] setting Message decoding to type:", reflect.TypeOf(t)) - // m.Type = t + m.messageHandle.WithJSONContents(content) return m } type SynchronousMessageWithPlugin struct { + messageHandle *native.Message + pact *SynchronousPact } -func (s *SynchronousMessageWithPlugin) WithContents() *SynchronousMessageWithPluginContents { - return &SynchronousMessageWithPluginContents{} +func (s *SynchronousMessageWithPlugin) WithContents(contents string, contentType string) *SynchronousMessageWithPluginContents { + s.messageHandle.WithPluginInteractionContents(native.INTERACTION_PART_REQUEST, contentType, contents) + + return &SynchronousMessageWithPluginContents{ + pact: s.pact, + messageHandle: s.messageHandle, + } } type SynchronousMessageWithPluginContents struct { + messageHandle *native.Message + pact *SynchronousPact } // ExecuteTest runs the current test case against a Mock Service. // Will cleanup interactions between tests within a suite // and write the pact file if successful -func (m *SynchronousMessageWithPluginContents) ExecuteTest(t *testing.T, integrationTest func(TransportConfig) error) error { - return nil -} +// NOTE: currently, this function is not very useful because without a transport, +// there is no useful way to test your actual code (because the message isn't passed back in) +// Use at your own risk ;) +func (m *SynchronousMessageWithPluginContents) ExecuteTest(t *testing.T, integrationTest func(m SynchronousMessage) error) error { + message := SynchronousMessage{} -func (s *SynchronousMessageWithPluginContents) StartTransport() *SynchronousMessageWithTransport { - return &SynchronousMessageWithTransport{} -} + err := integrationTest(message) -type SynchronousMessageWithPluginTransport struct { -} + if err != nil { + return err + } -func (s *SynchronousMessageWithPluginTransport) StartTransport() *SynchronousMessageWithTransport { - return &SynchronousMessageWithTransport{} + return m.pact.mockserver.WritePactFile(m.pact.config.PactDir, false) } -type SynchronousMessageWithTransport struct { -} +func (s *SynchronousMessageWithPluginContents) StartTransport(transport string, address string, config map[string][]interface{}) *SynchronousMessageWithTransport { + port, err := s.pact.mockserver.StartTransport(transport, address, 0, make(map[string][]interface{})) -func (s *SynchronousMessageWithTransport) ExecuteTest(t *testing.T, integrationTest func(TransportConfig) error) error { - return nil + if err != nil { + log.Fatalln("unable to start plugin transport:", err) + } + + return &SynchronousMessageWithTransport{ + pact: s.pact, + messageHandle: s.messageHandle, + transport: TransportConfig{ + Port: port, + Address: address, + }, + } } -// SynchronousMessage is a representation of a single, bidirectional message -type SynchronousMessage struct { +type SynchronousMessageWithTransport struct { messageHandle *native.Message - Pact *SynchronousPact - - // Type to Marshal content into when sending back to the consumer - // Defaults to interface{} - Type interface{} - - // The handler for this message - handler SynchronousConsumer + pact *SynchronousPact + transport TransportConfig } -// Given specifies a provider state. Optional. -func (m *SynchronousMessage) Given(state string) *UnconfiguredSynchronousMessage { - m.messageHandle.Given(state) +func (s *SynchronousMessageWithTransport) ExecuteTest(t *testing.T, integrationTest func(tc TransportConfig, m SynchronousMessage) error) error { + message := SynchronousMessage{} - return &UnconfiguredSynchronousMessage{} -} + defer s.pact.mockserver.CleanupMockServer(s.transport.Port) -// Given specifies a provider state. Optional. -func (m *SynchronousMessage) GivenWithParameter(state models.V3ProviderState) *UnconfiguredSynchronousMessage { - m.messageHandle.GivenWithParameter(state.Name, state.Parameters) + err := integrationTest(s.transport, message) - return &UnconfiguredSynchronousMessage{} -} + if err != nil { + return err + } -// The function that will consume the message -// func (m *SynchronousMessage) ConsumedBy(handler SynchronousConsumer) *SynchronousMessage { -// m.handler = handler + mismatches := s.pact.mockserver.MockServerMismatchedRequests(s.transport.Port) -// return m -// } + if len(mismatches) > 0 { + return fmt.Errorf("pact validation failed: %+v", mismatches) + } -// The function that will consume the message -func (m *SynchronousMessage) Verify(t *testing.T) error { - return m.Pact.Verify(t, m, m.handler) + return s.pact.mockserver.WritePactFileForServer(s.transport.Port, s.pact.config.PactDir, false) } type PluginConfig struct { @@ -253,7 +282,7 @@ func NewSynchronousPact(config Config) (*SynchronousPact, error) { // validateConfig validates the configuration for the consumer test func (m *SynchronousPact) validateConfig() error { - log.Println("[DEBUG] pact message validate config") + log.Println("[DEBUG] pact synchronous message validate config") dir, _ := os.Getwd() if m.config.PactDir == "" { @@ -265,97 +294,28 @@ func (m *SynchronousPact) validateConfig() error { return nil } -func (m *SynchronousPact) AddSynchronousMessage(description string) *SynchronousMessage { +func (m *SynchronousPact) AddSynchronousMessage(description string) *UnconfiguredSynchronousMessageBuilder { log.Println("[DEBUG] add sync message") message := m.mockserver.NewSyncMessageInteraction(description) - return &SynchronousMessage{ + return &UnconfiguredSynchronousMessageBuilder{ messageHandle: message, - Pact: m, + pact: m, } } -// TODO -// func (m *Pact) AddAsynchronousMessage(description string) *AsynchronousMessage { -// log.Println("[DEBUG] add async message") - -// message := m.mockserver.NewAsyncMessageInteraction("") - -// return &SynchronousMessage{ -// messageHandle: message, -// Pact: m, -// } -// } - // ExecuteTest runs the current test case against a Mock Service. // Will cleanup interactions between tests within a suite // and write the pact file if successful -func (m *SynchronousMessageWithResponse) ExecuteTest(t *testing.T, integrationTest func(TransportConfig) error) error { - // log.Println("[DEBUG] pact verify") - - // var err error - // if p.config.AllowedMockServerPorts != "" && p.config.Port <= 0 { - // p.config.Port, err = utils.FindPortInRange(p.config.AllowedMockServerPorts) - // } else if p.config.Port <= 0 { - // p.config.Port, err = 0, nil - // } - - // if err != nil { - // return fmt.Errorf("error: unable to find free port, mock server will fail to start") - // } - - // p.config.Port, err = p.mockserver.Start(fmt.Sprintf("%s:%d", p.config.Host, p.config.Port), p.config.TLS) - // defer p.reset() - // if err != nil { - // return err - // } - - // // Run the integration test - // err = integrationTest(MockServerConfig{ - // Port: p.config.Port, - // Host: p.config.Host, - // TLSConfig: GetTLSConfigForTLSMockServer(), - // }) - - // res, mismatches := p.mockserver.Verify(p.config.Port, p.config.PactDir) - // p.displayMismatches(t, mismatches) - - // if err != nil { - // return err - // } - - // if !res { - // return fmt.Errorf("pact validation failed: %+v %+v", res, mismatches) - // } - - // if len(mismatches) > 0 { - // return fmt.Errorf("pact validation failed: %+v", mismatches) - // } - - // return p.writePact() - return nil -} - -// Verifymessage.AsynchronousConsumerRaw creates a new Pact _message_ interaction to build a testable -// interaction. -// -// -// A Message Consumer is analagous to a Provider in the HTTP Interaction model. -// It is the receiver of an interaction, and needs to be able to handle whatever -// request was provided. -func (m *SynchronousPact) verifySynchronousConsumerRaw(message *SynchronousMessage, handler SynchronousConsumer) error { - return nil -} +func (m *SynchronousMessageWithResponse) ExecuteTest(t *testing.T, integrationTest func(md SynchronousMessage) error) error { + message := SynchronousMessage{} -// Verifymessage.AsynchronousConsumer is a test convience function for Verifymessage.AsynchronousConsumerRaw, -// accepting an instance of `*testing.T` -func (m *SynchronousPact) Verify(t *testing.T, message *SynchronousMessage, handler SynchronousConsumer) error { - err := m.verifySynchronousConsumerRaw(message, handler) + err := integrationTest(message) if err != nil { - t.Errorf("Verifymessage.AsynchronousConsumer failed: %v", err) + return err } - return err + return m.pact.mockserver.WritePactFile(m.pact.config.PactDir, false) } diff --git a/message/v4/synchronous_message_test.go b/message/v4/synchronous_message_test.go index fd54e7082..84c284863 100644 --- a/message/v4/synchronous_message_test.go +++ b/message/v4/synchronous_message_test.go @@ -1,42 +1,125 @@ package v4 import ( + "errors" + "fmt" + "os" "testing" + + "github.com/pact-foundation/pact-go/v2/log" + "github.com/stretchr/testify/assert" ) -func TestTypeSystem(t *testing.T) { +func TestSyncTypeSystem(t *testing.T) { p, _ := NewSynchronousPact(Config{ Consumer: "consumer", Provider: "provider", PactDir: "/tmp/", }) + log.SetLogLevel("TRACE") + + dir, _ := os.Getwd() + path := fmt.Sprintf("%s/../../internal/native/plugin.proto", dir) + + grpcInteraction := `{ + "pact:proto": "` + path + `", + "pact:proto-service": "PactPlugin/InitPlugin", + "pact:content-type": "application/protobuf", + "request": { + "implementation": "notEmpty('pact-go-driver')", + "version": "matching(semver, '0.0.0')" + }, + "response": { + "catalogue": [ + { + "type": "INTERACTION", + "key": "test" + } + ] + } + }` + // Sync - no plugin p.AddSynchronousMessage("some description"). Given("some state"). WithRequest(func(r *SynchronousMessageWithRequestBuilder) { - r.WithJSONContent("") - r.AsType("") + r.WithJSONContent(map[string]string{"foo": "bar"}) r.WithMetadata(map[string]string{}) }). WithResponse(func(r *SynchronousMessageWithResponseBuilder) { - r.WithJSONContent("") - r.AsType("") + r.WithJSONContent(map[string]string{"foo": "bar"}) r.WithMetadata(map[string]string{}) }). - ExecuteTest(t, func(t TransportConfig) error { + ExecuteTest(t, func(m SynchronousMessage) error { + // In this scenario, we have no real transport, so we need to mock/handle both directions + + // e.g. MQ use case -> write to a queue, get a response from another queue + + // What is the user expected to do here? + // m.Request. // inbound -> send to queue + // Poll the queue + // m.Response // response from queue + + // User consumes the request + return nil }) + // Sync - with plugin, but no transport + csvInteraction := `{ + "request.path", "/reports/report002.csv", + "response.status", "200", + "response.contents": { + "pact:content-type": "text/csv", // Set the content type to CSV + "csvHeaders": true, // We have a header row + "column:Name": "matching(type,'Name')", // Column with header Name must match by type (which is actually useless with CSV) + "column:Number", "matching(number,100)", // Column with header Number must match a number format + "column:Date", "matching(datetime, 'yyyy-MM-dd','2000-01-01')" // Column with header Date must match an ISO format yyyy-MM-dd + } + }` p.AddSynchronousMessage("some description"). Given("some state"). UsingPlugin(PluginConfig{ - Plugin: "some plugin", - Version: "1.0.0", + Plugin: "csv", + Version: "0.0.1", }). - WithContents(). - StartTransport(). // For plugin tests, we can't assume if a transport is needed, so this is optional - ExecuteTest(t, func(t TransportConfig) error { + WithContents(csvInteraction, "text/csv"). + ExecuteTest(t, func(m SynchronousMessage) error { + fmt.Println("Executing the CSV test") return nil }) + // Sync - with plugin + transport (pass) + err := p.AddSynchronousMessage("some description"). + Given("some state"). + UsingPlugin(PluginConfig{ + Plugin: "protobuf", + Version: "0.1.7", + }). + WithContents(grpcInteraction, "application/protobuf"). + StartTransport("grpc", "127.0.0.1", nil). // For plugin tests, we can't assume if a transport is needed, so this is optional + ExecuteTest(t, func(t TransportConfig, m SynchronousMessage) error { + fmt.Println("Executing a test - this is where you would normally make the gRPC call") + + return nil + }) + + assert.NoError(t, err) + + // Sync - with plugin + transport (fail) + err = p.AddSynchronousMessage("some description"). + Given("some state"). + UsingPlugin(PluginConfig{ + Plugin: "protobuf", + Version: "0.1.7", + }). + WithContents(grpcInteraction, "application/protobuf"). + StartTransport("grpc", "127.0.0.1", nil). // For plugin tests, we can't assume if a transport is needed, so this is optional + ExecuteTest(t, func(t TransportConfig, m SynchronousMessage) error { + fmt.Println("Executing a test - this is where you would normally make the gRPC call") + + return errors.New("bad thing") + }) + + assert.Error(t, err) } diff --git a/message/v4/transport.go b/message/v4/transport.go index dd608143a..b94d6f6c1 100644 --- a/message/v4/transport.go +++ b/message/v4/transport.go @@ -1,5 +1,6 @@ package v4 type TransportConfig struct { - Port int + Port int + Address string }