Skip to content

Commit

Permalink
feat: add support for gRPC mock server
Browse files Browse the repository at this point in the history
  • Loading branch information
rholshausen committed May 24, 2022
1 parent 9ead32f commit a05c1bc
Show file tree
Hide file tree
Showing 6 changed files with 1,736 additions and 805 deletions.
22 changes: 11 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,21 @@ clean:
deps: download_plugins
@echo "--- 🐿 Fetching build dependencies "
cd /tmp; \
go get github.com/axw/gocov/gocov; \
go get github.com/mattn/goveralls; \
go get golang.org/x/tools/cmd/cover; \
go get github.com/modocache/gover; \
go get github.com/mitchellh/gox; \
go install github.com/axw/gocov/gocov@latest; \
go install github.com/mattn/goveralls@latest; \
go install golang.org/x/tools/cmd/cover@latest; \
go install github.com/modocache/gover@latest; \
go install github.com/mitchellh/gox@latest; \
cd -

download_plugins:
@if [ ! -d ~/.pact/plugins/protobuf-0.0.3 ]; then\
@if [ ! -d ~/.pact/plugins/protobuf-0.1.5 ]; then\
@echo "--- 🐿 Installing plugins"; \
mkdir -p ~/.pact/plugins/protobuf-0.0.3; \
wget https://github.com/pactflow/pact-protobuf-plugin/releases/download/v-0.0.3/pact-plugin.json -O ~/.pact/plugins/protobuf-0.0.3/pact-plugin.json; \
wget https://github.com/pactflow/pact-protobuf-plugin/releases/download/v-0.0.3/pact-protobuf-plugin-linux-x86_64.gz -O ~/.pact/plugins/protobuf-0.0.3/pact-protobuf-plugin-linux-x86_64.gz; \
gunzip -N ~/.pact/plugins/protobuf-0.0.3/pact-protobuf-plugin-linux-x86_64.gz; \
chmod +x ~/.pact/plugins/protobuf-0.0.3/pact-protobuf-plugin; \
mkdir -p ~/.pact/plugins/protobuf-0.1.5; \
wget https://github.com/pactflow/pact-protobuf-plugin/releases/download/v-0.1.5/pact-plugin.json -O ~/.pact/plugins/protobuf-0.1.5/pact-plugin.json; \
wget https://github.com/pactflow/pact-protobuf-plugin/releases/download/v-0.1.5/pact-protobuf-plugin-linux-x86_64.gz -O ~/.pact/plugins/protobuf-0.1.5/pact-protobuf-plugin-linux-x86_64.gz; \
gunzip -N ~/.pact/plugins/protobuf-0.1.5/pact-protobuf-plugin-linux-x86_64.gz; \
chmod +x ~/.pact/plugins/protobuf-0.1.5/pact-protobuf-plugin; \
fi

goveralls:
Expand Down
4 changes: 2 additions & 2 deletions installer/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,8 @@ const (
var packages = map[string]packageInfo{
FFIPackage: {
libName: "libpact_ffi",
version: "0.2.3",
semverRange: ">= 0.2.3, < 1.0.0",
version: "0.3.0",
semverRange: ">= 0.3.0, < 1.0.0",
},
}

Expand Down
67 changes: 67 additions & 0 deletions internal/native/mock_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ int pactffi_with_multipart_file(InteractionHandle interaction, int interaction_p
// https://docs.rs/pact_mock_server_ffi/0.0.7/pact_mock_server_ffi/fn.response_status.html
void pactffi_response_status(InteractionHandle interaction, int status);
// Creates a new synchronous message interaction (request/response) and return a handle to it
InteractionHandle pactffi_new_sync_message_interaction(PactHandle pact, const char *description);
/// External interface to trigger a mock server to write out its pact file. This function should
/// be called if all the consumer tests have passed. The directory to write the file to is passed
/// as the second parameter. If a NULL pointer is passed, the current working directory is used.
Expand Down Expand Up @@ -156,6 +159,9 @@ int pactffi_using_plugin(PactHandle pact, const char *plugin_name, const char *p
void pactffi_cleanup_plugins(PactHandle pact);
int pactffi_interaction_contents(InteractionHandle interaction, int interaction_part, const char *content_type, const char *contents);
// Create a mock server for the provided Pact handle and transport.
int pactffi_create_mock_server_for_transport(PactHandle pact, const char *addr, int port, const char *transport, const char *transport_config);
*/
import "C"

Expand Down Expand Up @@ -443,6 +449,54 @@ func (m *MockServer) Start(address string, tls bool) (int, error) {
}
}

// StartTransport starts up a mock server on the given address:port for the given transport
// https://docs.rs/pact_ffi/latest/pact_ffi/mock_server/fn.pactffi_create_mock_server_for_transport.html
func (m *MockServer) StartTransport(transport string, address string, port int, config map[string][]interface{}) (int, error) {
if len(m.interactions) == 0 {
return 0, ErrNoInteractions
}

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

cTransport := C.CString(transport)
defer free(cTransport)

configJson := stringFromInterface(config)
cConfig := C.CString(configJson)
defer free(cConfig)

p := C.pactffi_create_mock_server_for_transport(m.pact.handle, cAddress, C.int(port), cTransport, cConfig)

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

// Sets the additional metadata on the Pact file. Common uses are to add the client library details such as the name and version
func (m *MockServer) WithMetadata(namespace, k, v string) *MockServer {
cNamespace := C.CString(namespace)
Expand Down Expand Up @@ -504,6 +558,19 @@ func (m *MockServer) NewInteraction(description string) *Interaction {
return i
}

// NewSyncMessageInteraction initialises a new synchronous message interaction for the current contract
func (m *MockServer) NewSyncMessageInteraction(description string) *Interaction {
cDescription := C.CString(description)
defer free(cDescription)

i := &Interaction{
handle: C.pactffi_new_sync_message_interaction(m.pact.handle, cDescription),
}
m.interactions = append(m.interactions, i)

return i
}

// NewInteraction initialises a new interaction for the current contract
// TODO: why specify the name and version twice?
func (i *Interaction) WithPluginInteractionContents(interactionPart interactionType, contentType string, contents string) error {
Expand Down
63 changes: 63 additions & 0 deletions internal/native/mock_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,66 @@ var pactComplex = `{
}
}]
}`

func TestGrpcPluginInteraction(t *testing.T) {
// tmpPactFolder, err := ioutil.TempDir("", "pact-go")
// assert.NoError(t, err)
// log.SetLogLevel("TRACE")

m := NewHTTPMockServer("test-grpc-consumer", "test-plugin-provider")

// Protobuf plugin test
m.UsingPlugin("protobuf", "0.1.5")
// m.WithSpecificationVersion(SPECIFICATION_VERSION_V4)

i := m.NewSyncMessageInteraction("grpc interaction")

dir, _ := os.Getwd()
path := fmt.Sprintf("%s/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"
}
]
}
}`

i.
Given("plugin state").
// For gRPC interactions we prpvide the config once for both the request and response parts
WithPluginInteractionContents(INTERACTION_PART_REQUEST, "application/protobuf", grpcInteraction)

// Start the gRPC mock server
port, err := m.StartTransport("grpc", "127.0.0.1", 0, make(map[string][]interface{}))
assert.NoError(t, err)
defer m.CleanupMockServer(port)

// Now we can make a normal gRPC request
// initPluginRequest := &InitPluginRequest{
// Implementation: "pact-go-test",
// Version: "1.0.0",
// }

// Need to make a gRPC call here

// TODO: once gRPC call is made
// mismatches := m.MockServerMismatchedRequests(port)
// if len(mismatches) != 0 {
// assert.Len(t, mismatches, 0)
// t.Log(mismatches)
// }

// err = m.WritePactFile(port, tmpPactFolder)
// assert.NoError(t, err)
}

0 comments on commit a05c1bc

Please sign in to comment.