diff --git a/dsl/matcher.go b/dsl/matcher.go index 75c6bd971..2ae5577f4 100644 --- a/dsl/matcher.go +++ b/dsl/matcher.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "reflect" + "strconv" "time" ) @@ -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 } @@ -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 @@ -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 diff --git a/dsl/matcher_test.go b/dsl/matcher_test.go index ea3c53326..0bd4d144d 100644 --- a/dsl/matcher_test.go +++ b/dsl/matcher_test.go @@ -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{}{ @@ -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", }, }, } @@ -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)) diff --git a/dsl/pact.go b/dsl/pact.go index ed0954a02..1fc9ed8b9 100644 --- a/dsl/pact.go +++ b/dsl/pact.go @@ -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 } diff --git a/examples/messages/consumer/message_pact_consumer_test.go b/examples/messages/consumer/message_pact_consumer_test.go index 6898d076d..976864eed 100644 --- a/examples/messages/consumer/message_pact_consumer_test.go +++ b/examples/messages/consumer/message_pact_consumer_test.go @@ -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 }) @@ -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") })