Skip to content

Commit

Permalink
wip: recursively generate message content
Browse files Browse the repository at this point in the history
  • Loading branch information
mefellows committed Apr 2, 2018
1 parent d7f80b7 commit b5f4acd
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 48 deletions.
160 changes: 131 additions & 29 deletions dsl/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"log"
"reflect"
"strconv"
"time"
)

Expand Down Expand Up @@ -185,29 +186,40 @@ func (m Matcher) isMatcher() {}
// GetValue returns the raw generated value for the matcher
// without any of the matching detail context
func (m Matcher) GetValue() interface{} {
log.Println("GETTING VALUE!")
class, ok := m["json_class"]

if !ok {
log.Println("GETTING VALUE!- NOT OK")
return nil
}

// extract out the value
switch class {
case "Pact::ArrayLike":
log.Println("GETTING VALUE!- ARRAY")
contents := m["contents"]
min := m["min"].(int)
min, err := strconv.Atoi(fmt.Sprintf("%d", m["min"]))
if err != nil {
min = 1
}

data := make([]interface{}, min)

for i := 0; i < min; i++ {
data[i] = contents
}
return data

case "Pact::SomethingLike":
log.Println("GETTING VALUE!- something like")
return m["contents"]
case "Pact::Term":
log.Println("GETTING VALUE!- term")
data := m["data"].(map[string]interface{})
return data["generate"]
}
log.Println("GETTING VALUE!- MEH?!")

return nil
}
Expand Down Expand Up @@ -280,24 +292,37 @@ func getMatcher(obj interface{}) (Matcher, bool) {
return m, true
}

fmt.Println("NOT a matcher")
return nil, false
}

var loop int

func extractPayload(obj interface{}) interface{} {
fmt.Println("extractpaload")
loop = 0

// special case: top level matching object
// we need to strip the properties
matcher, ok := getMatcher(obj)
stack := make(map[string]interface{})

if ok {
fmt.Println("top level matcher", matcher, "returning value:", getMatcherValue(matcher))
return extractPayload(getMatcherValue(matcher))
// Convert to and from JSON to get a map[string]interface{}
data, err := json.Marshal(obj)
if err != nil {
return nil
}

fmt.Println("not a top level matcher", matcher, "returning value:", obj)
return extractPayloadRecursive(obj, make(map[string]interface{}))
// var newObj map[string]interface{}
var newObj interface{}
json.Unmarshal(data, &newObj)

// matcher, ok := getMatcher(obj)
// if ok {
// fmt.Println("top level matcher", matcher, "returning value:", getMatcherValue(matcher))
// return extractPayloadRecursive(getMatcherValue(matcher), stack)
// }

// fmt.Println("not a top level matcher, returning value:", obj)
return extractPayloadRecursive(newObj, stack)
}

// Recurse the object removing any underlying matching guff, returning
Expand All @@ -308,31 +333,108 @@ func extractPayload(obj interface{}) interface{} {
// for use here
// It will probably break custom, user-supplied types? e.g. a User{} or ShoppingCart{}?
// But then any enclosed Matchers will likely break them anyway
func extractPayloadRecursive(obj interface{}, stack map[string]interface{}) map[string]interface{} {
fmt.Println("extracting payload recursively")

objectMap, ok := obj.(map[string]interface{})
if !ok {
func extractPayloadRecursive(obj interface{}, stack interface{}) interface{} {
loop = loop + 1
if loop > 10 {
log.Println("oh oh, non terminating - bail!")
return nil
}
original := reflect.ValueOf(obj)

// recurse the (remaining) object, replacing Matchers with their
// actual contents
for k, rawValue := range objectMap {
fmt.Println(k, "=>", rawValue, "(raw)")
// v, ok := rawValue.(map[string]interface{})
// fmt.Println(k, "=>", v)

if ok && isMatcher(rawValue) {
fmt.Println("v is Matcher")
matcherValue := getMatcherValue(rawValue)
stack[k] = matcherValue
extractPayloadRecursive(matcherValue, stack)
} else {
fmt.Println("v is not Matcher but of type", reflect.TypeOf(rawValue))
stack[k] = rawValue
extractPayloadRecursive(rawValue, stack)
fmt.Println("------------------------------")
fmt.Println("extracting payload recursively")
fmt.Printf("obj: %+v\n", obj)
fmt.Printf("Stack: %+v\n", stack)

// switch obj.(type)
switch original.Kind() {
// The first cases handle nested structures and translate them recursively

// If it is a pointer we need to unwrap and call once again
case reflect.Ptr:
log.Println("[DEBUG] Pointer")
// To get the actual value of the original we have to call Elem()
// At the same time this unwraps the pointer so we don't end up in
// an infinite recursion
originalValue := original.Elem()

// Check if the pointer is nil
if !originalValue.IsValid() {
log.Println("[WARN] pointer not properly unmarshalled")
return nil
}

// Unwrap the newly created pointer
extractPayloadRecursive(originalValue, stack)

// If it is an interface (which is very similar to a pointer), do basically the
// same as for the pointer. Though a pointer is not the same as an interface so
// note that we have to call Elem() after creating a new object because otherwise
// we would end up with an actual pointer
case reflect.Interface:
log.Println("[DEBUG] Interface")

// Get rid of the wrapping interface
originalValue := original.Elem()

// Create a new object. Now new gives us a pointer, but we want the value it
// points to, so we have to call Elem() to unwrap it
copyValue := reflect.New(originalValue.Type()).Elem()
extractPayloadRecursive(copyValue, stack)

// If it is a struct we translate each field
// case reflect.Struct:
// log.Println("[DEBUG] Struct")
// _, ok := getMatcher(obj)
// if ok {
// fmt.Println("2. MATCHER!")
// }

// for i := 0; i < original.NumField(); i++ {
// extractPayloadRecursive(original.Field(i), stack)
// }

// If it is a slice we create a new slice and translate each element
case reflect.Slice:
log.Println("[DEBUG] Slice")
for i := 0; i < original.Len(); i++ {
extractPayloadRecursive(original.Index(i).Interface(), stack)
}

// If it is a map we create a new map and translate each value
case reflect.Map:
log.Println("[DEBUG] Map")
stackMap, ok := stack.(map[string]interface{})

if !ok {
log.Println("STACK is not a map[]")
stack = make(map[string]interface{})
stackMap, _ = stack.(map[string]interface{})
}

for k, v := range obj.(map[string]interface{}) {
matcher, ok := getMatcher(v)
fmt.Println(k, "=>", v)
if ok {
value := matcher.GetValue()
fmt.Println("3. Map is a MATCHER!", value)
stackMap[k] = value
extractPayloadRecursive(value, stackMap[k])
} else {
stackMap[k] = v
extractPayloadRecursive(v, stackMap[k])
}
}

// If it is a string translate it (yay finally we're doing what we came for)
case reflect.String:
fmt.Println("STRING")
return obj
// copy.SetString(original.Interface().(string))

// And everything else will simply be taken from the original
default:
fmt.Println("something else")
}

return stack
Expand Down
42 changes: 30 additions & 12 deletions dsl/matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,16 +472,41 @@ func TestMatcher_extractPayloadTopLevelMatcher(t *testing.T) {
if extractPayload(m) != "something" {
t.Fatal("want 'something', got", extractPayload(m))
}

}
func TestMatcher_extractPayloadSimpleType(t *testing.T) {
m := map[string]interface{}{
"value": "string",
}
if !cmp.Equal(extractPayload(m), m) {
t.Fatal("extract payload diff: ", cmp.Diff(m, extractPayload(m)))
}
}

func TestMatcher_extractPayloadEachLike(t *testing.T) {
m := map[string]interface{}{
"access": EachLike(map[string]interface{}{
"role": Term("admin", "admin|controller|user"),
}, 3),
}
want := map[string]interface{}{
"access": []interface{}{
map[string]interface{}{"role": "admin"},
},
}

got := extractPayload(m)
if !cmp.Equal(want, got) {
t.Fatalf("want '%v', got '%v'. Diff: \n %v", want, got, cmp.Diff(want, got))
}
}
func TestMatcher_extractPayloadComplex(t *testing.T) {
m := map[string]interface{}{
"foo": Like("bar"),
"bar": Term("baz", "baz|bat"),
"baz": EachLike(map[string]interface{}{
"bing": "bong",
"boing": 1,
"bing": Like("bong"),
"boing": Like(1),
"bop": "bop",
}, 2),
}
want := map[string]interface{}{
Expand All @@ -491,10 +516,12 @@ func TestMatcher_extractPayloadComplex(t *testing.T) {
map[string]interface{}{
"bing": "bong",
"boing": 1,
"bop": "bop",
},
map[string]interface{}{
"bing": "bong",
"boing": 1,
"bop": "bop",
},
},
}
Expand All @@ -505,15 +532,6 @@ func TestMatcher_extractPayloadComplex(t *testing.T) {
}
}

// func TestMatcher_getMatcher(t *testing.T) {
// m, ok := getMatcher(Matcher{
// "json_class": "Pact::SomethingLike",
// "contents": "something",
// })
// fmt.Println(m, ok)
// log.Println(m, ok)
// }

func ExampleLike_string() {
match := Like("myspecialvalue")
fmt.Println(formatJSON(match))
Expand Down
13 changes: 11 additions & 2 deletions dsl/pact.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,14 +419,23 @@ func (p *Pact) VerifyMessageProducer(t *testing.T, request types.VerifyRequest,
// 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) VerifyMessageConsumer(message *Message, handler func(...Message) error) error {
func (p *Pact) VerifyMessageConsumer(message *Message, handler func(Message) error) error {
log.Printf("[DEBUG] verify message")
p.Setup(false)

// Yield message, and send through handler function
// TODO: for now just call the handler
// TODO: unwrap the message back to its "generated" form
err := handler(*message)
generatedMessage :=
Message{
Content: extractPayload(message.Content),
Description: message.Description,
State: message.State,
Metadata: message.Metadata,
}

log.Println("[DEBUG] generated message from matcher:", generatedMessage.Content)
err := handler(generatedMessage)
if err != nil {
return err
}
Expand Down
22 changes: 17 additions & 5 deletions examples/messages/consumer/message_pact_consumer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,23 @@ func TestMessageConsumer_Success(t *testing.T) {
ExpectsToReceive("some test case").
WithMetadata(commonHeaders).
WithContent(map[string]interface{}{
"foo": "bar",
"id": like(127),
"name": "Baz",
"access": eachLike(map[string]interface{}{
"role": term("admin", "admin|controller|user"),
}, 3),
})

err := pact.VerifyMessageConsumer(message, func(i ...dsl.Message) error {
t.Logf("[DEBUG] calling message handler func with arguments: %v \n", i)
err := pact.VerifyMessageConsumer(message, func(m dsl.Message) error {
t.Logf("[DEBUG] calling message handler func with arguments: %v \n", m.Content)

body := m.Content.(map[string]interface{})

_, ok := body["id"]

if !ok {
return errors.New("invalid object supplied, missing fields (id)")
}

return nil
})
Expand All @@ -53,8 +65,8 @@ func TestMessageConsumer_Fail(t *testing.T) {
"foo": "bar",
})

err := pact.VerifyMessageConsumer(message, func(i ...dsl.Message) error {
t.Logf("[DEBUG] calling message handler func with arguments: %v \n", i)
err := pact.VerifyMessageConsumer(message, func(m dsl.Message) error {
t.Logf("[DEBUG] calling message handler func with arguments: %v \n", m)

return errors.New("something bad happened and I couldn't parse the message")
})
Expand Down

0 comments on commit b5f4acd

Please sign in to comment.