Skip to content

Commit

Permalink
chore: refactor native package into smaller files
Browse files Browse the repository at this point in the history
  • Loading branch information
mefellows committed May 23, 2021
1 parent 8e5acba commit 60ed9b0
Show file tree
Hide file tree
Showing 10 changed files with 399 additions and 551 deletions.
197 changes: 197 additions & 0 deletions v3/internal/native/mockserver/message_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package mockserver

/*
// Library headers
#include <stdlib.h>
typedef int bool;
#define true 1
#define false 0
typedef struct MessageHandle MessageHandle;
struct MessageHandle {
uintptr_t pact;
uintptr_t message;
};
/// Wraps a PactMessage model struct
typedef struct MessagePactHandle MessagePactHandle;
struct MessagePactHandle {
uintptr_t pact;
};
MessagePactHandle new_message_pact(const char *consumer_name, const char *provider_name);
MessageHandle new_message(MessagePactHandle pact, const char *description);
void message_expects_to_receive(MessageHandle message, const char *description);
void message_given(MessageHandle message, const char *description);
void message_given_with_param(MessageHandle message, const char *description, const char *name, const char *value);
void message_with_contents(MessageHandle message, const char *content_type, const char *body, int size);
void message_with_metadata(MessageHandle message, const char *key, const char *value);
char* message_reify(MessageHandle message);
int write_message_pact_file(MessagePactHandle pact, const char *directory, bool overwrite);
void with_message_pact_metadata(MessagePactHandle pact, const char *namespace, const char *name, const char *value);
*/
import "C"

import (
"fmt"
"log"
"unsafe"
)

type MessagePact struct {
handle C.MessagePactHandle
}

type Message struct {
handle C.MessageHandle
}

// MessageServer is the public interface for managing the message based interface
type MessageServer struct {
messagePact *MessagePact
messages []*Message
}

// NewMessage initialises a new message for the current contract
func NewMessageServer(consumer string, provider string) *MessageServer {
cConsumer := C.CString(consumer)
cProvider := C.CString(provider)
defer free(cConsumer)
defer free(cProvider)

return &MessageServer{messagePact: &MessagePact{handle: C.new_message_pact(cConsumer, cProvider)}}
}

// 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 *MessageServer) WithMetadata(namespace, k, v string) *MessageServer {
cNamespace := C.CString(namespace)
defer free(cNamespace)
cName := C.CString(k)
defer free(cName)
cValue := C.CString(v)
defer free(cValue)

C.with_message_pact_metadata(m.messagePact.handle, cNamespace, cName, cValue)

return m
}

// NewMessage initialises a new message for the current contract
func (m *MessageServer) NewMessage() *Message {
cDescription := C.CString("")
defer free(cDescription)

i := &Message{
handle: C.new_message(m.messagePact.handle, cDescription),
}
m.messages = append(m.messages, i)

return i
}

func (i *Message) Given(state string) *Message {
cState := C.CString(state)
defer free(cState)

C.message_given(i.handle, cState)

return i
}

func (i *Message) GivenWithParameter(state string, params map[string]interface{}) *Message {
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)

C.message_given_with_param(i.handle, cState, cKey, cValue)

}

return i
}

func (i *Message) ExpectsToReceive(description string) *Message {
cDescription := C.CString(description)
defer free(cDescription)

C.message_expects_to_receive(i.handle, cDescription)

return i
}

func (i *Message) WithMetadata(valueOrMatcher map[string]string) *Message {
for k, v := range valueOrMatcher {

cName := C.CString(k)
defer free(cName)

// TODO: check if matching rules allowed here
// value := stringFromInterface(v)
// fmt.Printf("withheaders, sending: %+v \n\n", value)
// cValue := C.CString(value)
cValue := C.CString(v)
defer free(cValue)

C.message_with_metadata(i.handle, cName, cValue)
}

return i
}

func (i *Message) WithBinaryContents(body []byte) *Message {
return i.WithContents("application/octet-stream", body)
}

func (i *Message) WithJSONContents(body []byte) *Message {
return i.WithContents("application/json", body)
}

func (i *Message) WithContents(contentType string, body []byte) *Message {
cHeader := C.CString(contentType)
defer free(cHeader)

cBytes := C.CString(string(body))
defer free(cBytes)
C.message_with_contents(i.handle, cHeader, (*C.char)(unsafe.Pointer(&body[0])), C.int(len(body)))

return i
}

func (i *Message) ReifyMessage() string {
return C.GoString(C.message_reify(i.handle))
}

// WritePactFile writes the Pact to file.
func (m *MessageServer) WritePactFile(dir string, overwrite bool) error {
log.Println("[DEBUG] writing pact file for message pact at dir:", dir)
cDir := C.CString(dir)
defer free(cDir)

overwritePact := 0
if overwrite {
overwritePact = 1
}

res := int(C.write_message_pact_file(m.messagePact.handle, cDir, C.int(overwritePact)))

/// | Error | Description |
/// |-------|-------------|
/// | 1 | The pact file was not able to be written |
/// | 2 | The message pact for the given handle was not found |
switch res {
case 0:
return nil
case 1:
return ErrUnableToWritePactFile
case 2:
return ErrHandleNotFound
default:
return fmt.Errorf("an unknown error ocurred when writing to pact file")
}
}
150 changes: 150 additions & 0 deletions v3/internal/native/mockserver/message_server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package mockserver

import (
"bytes"
"compress/gzip"
"encoding/base64"
"encoding/json"
"io/ioutil"
"log"
"testing"

"github.com/stretchr/testify/assert"
)

func TestHandleBasedMessageTestsWithString(t *testing.T) {
Init()
tmpPactFolder, err := ioutil.TempDir("", "pact-go")
assert.NoError(t, err)
s := NewMessageServer("test-message-consumer", "test-message-provider")

m := s.NewMessage().
Given("some state").
GivenWithParameter("param", map[string]interface{}{
"foo": "bar",
}).
ExpectsToReceive("some message").
WithMetadata(map[string]string{
"meta": "data",
}).
WithContents("text/plain", []byte("some string"))

body := m.ReifyMessage()

var res jsonMessage
err = json.Unmarshal([]byte(body), &res)
assert.NoError(t, err)

assert.Equal(t, res.Description, "some message")
assert.Len(t, res.ProviderStates, 2)
assert.NotEmpty(t, res.Contents)

// This is where you would invoke the real function with the message

err = s.WritePactFile(tmpPactFolder, false)
assert.NoError(t, err)
}

func TestHandleBasedMessageTestsWithJSON(t *testing.T) {
Init()
tmpPactFolder, err := ioutil.TempDir("", "pact-go")
assert.NoError(t, err)
s := NewMessageServer("test-message-consumer", "test-message-provider")

m := s.NewMessage().
Given("some state").
GivenWithParameter("param", map[string]interface{}{
"foo": "bar",
}).
ExpectsToReceive("some message").
WithMetadata(map[string]string{
"meta": "data",
}).
WithJSONContents([]byte(`{"some": "json"}`))

body := m.ReifyMessage()
log.Println(body) // TODO: JSON is not stringified - probably should be?

var res jsonMessage
err = json.Unmarshal([]byte(body), &res)
assert.NoError(t, err)

assert.Equal(t, res.Description, "some message")
assert.Len(t, res.ProviderStates, 2)
assert.NotEmpty(t, res.Contents)

// This is where you would invoke the real function with the message

err = s.WritePactFile(tmpPactFolder, false)
assert.NoError(t, err)
}

func TestHandleBasedMessageTestsWithBinary(t *testing.T) {
Init()
tmpPactFolder, err := ioutil.TempDir("", "pact-go")
assert.NoError(t, err)

s := NewMessageServer("test-binarymessage-consumer", "test-binarymessage-provider").
WithMetadata("some-namespace", "the-key", "the-value")

// generate some binary data
var buf bytes.Buffer
zw := gzip.NewWriter(&buf)

encodedMessage := "A long time ago in a galaxy far, far away..."
_, err = zw.Write([]byte(encodedMessage))
assert.NoError(t, err)

err = zw.Close()
assert.NoError(t, err)

m := s.NewMessage().
Given("some binary state").
GivenWithParameter("param", map[string]interface{}{
"foo": "bar",
}).
ExpectsToReceive("some binary message").
WithMetadata(map[string]string{
"meta": "data",
}).
WithBinaryContents(buf.Bytes())

body := m.ReifyMessage()

// Check the reified message is good

var res binaryMessage
err = json.Unmarshal([]byte(body), &res)
assert.NoError(t, err)

// Extract binary payload, base 64 decode it, unzip it
data, err := base64.RawStdEncoding.DecodeString(res.Contents)
assert.NoError(t, err)
r, err := gzip.NewReader(bytes.NewReader(data))
assert.NoError(t, err)
result, _ := ioutil.ReadAll(r)

assert.Equal(t, encodedMessage, string(result))
assert.Equal(t, "some binary message", res.Description)
assert.Len(t, res.ProviderStates, 2)
assert.NotEmpty(t, res.Contents)

// This is where you would invoke the real function with the message...

err = s.WritePactFile(tmpPactFolder, false)
assert.NoError(t, err)
}

type binaryMessage struct {
ProviderStates []map[string]interface{} `json:"providerStates"`
Description string `json:"description"`
Metadata map[string]string `json:"metadata"`
Contents string `json:"contents"` // base 64 encoded
// Contents []byte `json:"contents"`
}
type jsonMessage struct {
ProviderStates []map[string]interface{} `json:"providerStates"`
Description string `json:"description"`
Metadata map[string]string `json:"metadata"`
Contents interface{} `json:"contents"`
}
Loading

0 comments on commit 60ed9b0

Please sign in to comment.